Feat v0.7.10 workflow handoff (#64)
All checks were successful
CI/CD / test-go-pg (push) Successful in 2m54s
CI/CD / test-sqlite (push) Successful in 3m1s
CI/CD / build-and-deploy (push) Successful in 1m47s
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
All checks were successful
CI/CD / test-go-pg (push) Successful in 2m54s
CI/CD / test-sqlite (push) Successful in 3m1s
CI/CD / build-and-deploy (push) Successful in 1m47s
CI/CD / e2e-smoke (push) Has been skipped
CI/CD / test-frontend (push) Successful in 6s
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-runners (push) Has been skipped
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #64.
This commit is contained in:
@@ -140,6 +140,11 @@ export function createDomains(restClient) {
|
||||
// Adopt global workflows
|
||||
availableWorkflows: (id) => rc.get(`/api/v1/teams/${id}/workflows/available`),
|
||||
adoptWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/adopt`, {}),
|
||||
// Team assignments
|
||||
assignments: (id, opts) => rc.get(`/api/v1/teams/${id}/assignments` + _qs(opts)),
|
||||
// Team workflow instances (cross-workflow)
|
||||
workflowInstances: (id, opts) => rc.get(`/api/v1/teams/${id}/workflow-instances` + _qs(opts)),
|
||||
cancelWorkflowInstance: (id, iid) => rc.post(`/api/v1/teams/${id}/workflow-instances/${iid}/cancel`, {}),
|
||||
},
|
||||
|
||||
// ── 9. Workflows ───────────────────────
|
||||
@@ -154,7 +159,17 @@ export function createDomains(restClient) {
|
||||
instances: (id, opts) => rc.get(`/api/v1/workflows/${id}/instances` + _qs(opts)),
|
||||
advance: (id, data) => rc.post(`/api/v1/workflows/${id}/advance`, data),
|
||||
reject: (id, data) => rc.post(`/api/v1/workflows/${id}/reject`, data),
|
||||
cancel: (channelId) => rc.post(`/api/v1/channels/${channelId}/workflow/cancel`, {}),
|
||||
cancel: (wfId, instanceId) => rc.post(`/api/v1/workflows/${wfId}/instances/${instanceId}/cancel`, {}),
|
||||
},
|
||||
|
||||
// ── 15. Workflow Assignments ─────────
|
||||
workflowAssignments: {
|
||||
mine: (opts) => rc.get('/api/v1/assignments/mine' + _qs(opts)),
|
||||
claim: (id) => rc.post(`/api/v1/assignments/${id}/claim`, {}),
|
||||
unclaim: (id) => rc.post(`/api/v1/assignments/${id}/unclaim`, {}),
|
||||
complete: (id, data) => rc.post(`/api/v1/assignments/${id}/complete`, data || {}),
|
||||
cancel: (id) => rc.post(`/api/v1/assignments/${id}/cancel`, {}),
|
||||
assign: (id, userId) => rc.post(`/api/v1/assignments/${id}/assign`, { user_id: userId }),
|
||||
},
|
||||
|
||||
// ── 10. Surfaces ──────────────────────
|
||||
|
||||
@@ -33,7 +33,9 @@ function _formatDuration(seconds) {
|
||||
export function AssignmentsTab({ teamId }) {
|
||||
const [claimed, setClaimed] = useState([]);
|
||||
const [unassigned, setUnassigned] = useState([]);
|
||||
const [members, setMembers] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [assigningId, setAssigningId] = useState(null);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
@@ -47,7 +49,15 @@ export function AssignmentsTab({ teamId }) {
|
||||
finally { setLoading(false); }
|
||||
}, [teamId]);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
// Load team members for the assign dropdown
|
||||
const loadMembers = useCallback(async () => {
|
||||
try {
|
||||
const data = await sw.api.teams.members(teamId);
|
||||
setMembers(data || []);
|
||||
} catch { setMembers([]); }
|
||||
}, [teamId]);
|
||||
|
||||
useEffect(() => { load(); loadMembers(); }, [load, loadMembers]);
|
||||
|
||||
// WS live updates
|
||||
useEffect(() => {
|
||||
@@ -79,9 +89,16 @@ export function AssignmentsTab({ teamId }) {
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
||||
async function assignTo(assignmentId, userId) {
|
||||
try {
|
||||
await sw.api.workflowAssignments.assign(assignmentId, userId);
|
||||
sw.toast('Assigned', 'success');
|
||||
setAssigningId(null);
|
||||
load();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
const userId = sw.auth?.user?.id;
|
||||
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
||||
|
||||
return html`
|
||||
<div>
|
||||
@@ -97,7 +114,7 @@ export function AssignmentsTab({ teamId }) {
|
||||
<div style="display:flex;gap:4px;">
|
||||
<button class="sw-btn sw-btn--secondary sw-btn--sm" onClick=${() => unclaim(a.id)}>Release</button>
|
||||
<button class="sw-btn sw-btn--primary sw-btn--sm"
|
||||
onClick=${() => { location.href = sw.base + '/w/' + a.channel_id; }}>Open</button>
|
||||
onClick=${() => { location.href = sw.base + '/w/' + a.instance_id; }}>Work</button>
|
||||
<button class="sw-btn sw-btn--success sw-btn--sm" onClick=${() => complete(a.id)}>Complete</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +131,28 @@ export function AssignmentsTab({ teamId }) {
|
||||
<span class="badge">${a.stage_name || `Stage ${a.stage}`}</span>
|
||||
<span class="text-muted" style="font-size:11px;">${_timeAgo(a.created_at)}</span>
|
||||
</div>
|
||||
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => claim(a.id)}>Claim</button>
|
||||
<div style="display:flex;gap:4px;">
|
||||
<button class="sw-btn sw-btn--primary sw-btn--sm" onClick=${() => claim(a.id)}>Claim</button>
|
||||
<button class="sw-btn sw-btn--secondary sw-btn--sm"
|
||||
onClick=${() => setAssigningId(assigningId === a.id ? null : a.id)}>Assign</button>
|
||||
</div>
|
||||
${assigningId === a.id && html`
|
||||
<div style="margin-top:6px;display:flex;gap:4px;align-items:center;font-size:12px;">
|
||||
<select id=${'assign_' + a.id} style="flex:1;font-size:12px;">
|
||||
<option value="">Select member\u2026</option>
|
||||
${members.map(m => html`
|
||||
<option key=${m.user_id} value=${m.user_id}>
|
||||
${sw.users?.displayName?.(m.user_id) || m.display_name || m.username || m.user_id}
|
||||
</option>
|
||||
`)}
|
||||
</select>
|
||||
<button class="sw-btn sw-btn--primary sw-btn--sm"
|
||||
onClick=${() => {
|
||||
const sel = document.getElementById('assign_' + a.id);
|
||||
if (sel?.value) assignTo(a.id, sel.value);
|
||||
}}>Confirm</button>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`)}
|
||||
${unassigned.length === 0 && html`<div class="empty-hint">No available assignments</div>`}
|
||||
@@ -157,7 +195,7 @@ export function MonitorTab({ teamId }) {
|
||||
<h4 style="margin:0 0 8px;">Active Instances (${instances.length})</h4>
|
||||
<div class="admin-list">
|
||||
${instances.map(inst => html`
|
||||
<div class="admin-surface-row" key=${inst.channel_id}>
|
||||
<div class="admin-surface-row" key=${inst.id}>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<strong>${inst.workflow_name}</strong>
|
||||
<span class="badge">${inst.stage_name || `Stage ${inst.current_stage}`}</span>
|
||||
@@ -168,8 +206,8 @@ export function MonitorTab({ teamId }) {
|
||||
<button class="sw-btn sw-btn--secondary sw-btn--sm"
|
||||
onClick=${() => setExpandedSignoff(expandedSignoff === inst.id ? null : inst.id)}>Signoffs</button>
|
||||
<button class="sw-btn sw-btn--secondary sw-btn--sm"
|
||||
onClick=${() => { location.href = sw.base + '/w/' + inst.channel_id; }}>Open</button>
|
||||
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => cancelInstance(inst.channel_id)}>Cancel</button>
|
||||
onClick=${() => { location.href = sw.base + '/w/' + inst.id; }}>Open</button>
|
||||
<button class="sw-btn sw-btn--danger sw-btn--sm" onClick=${() => cancelInstance(inst.id)}>Cancel</button>
|
||||
</div>
|
||||
${expandedSignoff === inst.id && html`<${SignoffPanel} instanceId=${inst.id} teamId=${teamId} />`}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user