Changeset 0.37.15 (#227)
This commit is contained in:
@@ -45,7 +45,7 @@ const NAV_ITEMS = [
|
||||
{ key: 'personas', label: 'Personas', gate: 'personas' },
|
||||
{ key: 'profile', label: 'Profile' },
|
||||
{ key: 'teams', label: 'Teams' },
|
||||
{ key: 'workflows', label: 'Workflows' },
|
||||
{ key: 'workflows', label: 'Assignments' },
|
||||
{ key: 'tasks', label: 'Tasks' },
|
||||
{ key: 'gitkeys', label: 'Git Keys' },
|
||||
{ key: 'knowledge', label: 'Knowledge Bases' },
|
||||
@@ -64,7 +64,7 @@ const BYOK_ITEMS = [
|
||||
const SECTION_TITLES = {
|
||||
general: 'General', appearance: 'Appearance', models: 'Models',
|
||||
personas: 'Personas', profile: 'Profile', teams: 'Teams',
|
||||
workflows: 'Workflows', tasks: 'Tasks', gitkeys: 'Git Keys',
|
||||
workflows: 'Assignments', tasks: 'Tasks', gitkeys: 'Git Keys',
|
||||
data: 'Data & Privacy', providers: 'My Providers', roles: 'Model Roles',
|
||||
usage: 'My Usage', knowledge: 'Knowledge Bases', memory: 'Memory',
|
||||
notifications: 'Notifications',
|
||||
|
||||
@@ -1,87 +1,137 @@
|
||||
/**
|
||||
* Settings > Workflows — read-only view of user's visible workflows
|
||||
* Settings > Workflows — v0.37.15 rewrite
|
||||
*
|
||||
* Replaces read-only workflow list with MyAssignments (E4).
|
||||
* Shows the user's personal assignment queue across all teams.
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useEffect, useCallback } = hooks;
|
||||
|
||||
function _timeAgo(ts) {
|
||||
if (!ts) return '';
|
||||
const diff = Date.now() - new Date(ts).getTime();
|
||||
const mins = Math.floor(diff / 60000);
|
||||
if (mins < 1) return 'just now';
|
||||
if (mins < 60) return `${mins}m ago`;
|
||||
const hrs = Math.floor(mins / 60);
|
||||
if (hrs < 24) return `${hrs}h ago`;
|
||||
return `${Math.floor(hrs / 24)}d ago`;
|
||||
}
|
||||
|
||||
export default function WorkflowsSection() {
|
||||
const [workflows, setWorkflows] = useState([]);
|
||||
const [assignments, setAssignments] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [detail, setDetail] = useState(null);
|
||||
const [stages, setStages] = useState([]);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
const data = await sw.api.workflows.list();
|
||||
setWorkflows(data || []);
|
||||
const data = await sw.api.workflowAssignments.mine();
|
||||
setAssignments(data || []);
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
finally { setLoading(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, []);
|
||||
|
||||
async function openDetail(wf) {
|
||||
setDetail(wf);
|
||||
const userId = sw.auth?.user?.id;
|
||||
const mine = assignments.filter(a => a.status === 'claimed' && a.assigned_to === userId);
|
||||
const available = assignments.filter(a => a.status === 'unassigned');
|
||||
|
||||
async function claim(id) {
|
||||
try {
|
||||
const d = await sw.api.workflows.get(wf.id);
|
||||
setStages(d.stages || []);
|
||||
} catch (e) { sw.toast(e.message, 'error'); setStages([]); }
|
||||
await sw.api.workflowAssignments.claim(id);
|
||||
load();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function unclaim(id) {
|
||||
try {
|
||||
await sw.api.workflowAssignments.unclaim(id);
|
||||
load();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function complete(id) {
|
||||
try {
|
||||
await sw.api.workflowAssignments.complete(id);
|
||||
sw.toast('Assignment completed', 'success');
|
||||
load();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
if (loading) return html`<div class="settings-placeholder">Loading\u2026</div>`;
|
||||
|
||||
if (detail) return html`
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px;">
|
||||
<button class="btn-small" onClick=${() => { setDetail(null); setStages([]); }}>\u2190 Back</button>
|
||||
<h4 style="margin:0;">${detail.name}</h4>
|
||||
<span class="badge ${detail.is_active ? 'badge-active' : 'badge-inactive'}">
|
||||
${detail.is_active ? 'active' : 'inactive'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
${detail.description && html`
|
||||
<p class="text-muted" style="font-size:13px;margin:0 0 12px;">${detail.description}</p>
|
||||
`}
|
||||
|
||||
<div style="display:flex;gap:12px;margin-bottom:16px;font-size:12px;color:var(--text-3);">
|
||||
<span>Slug: <strong>/${detail.slug}</strong></span>
|
||||
<span>Entry: <strong>${detail.entry_mode}</strong></span>
|
||||
<span>Version: <strong>${detail.version || 0}</strong></span>
|
||||
</div>
|
||||
|
||||
<h5 style="margin:0 0 8px;">Stages (${stages.length})</h5>
|
||||
<div class="admin-list">
|
||||
${stages.length === 0 && html`<div class="empty-hint">No stages defined</div>`}
|
||||
${stages.map((s, i) => html`
|
||||
<div class="admin-surface-row" key=${s.id}>
|
||||
<span class="text-muted" style="font-size:11px;width:24px;">#${i + 1}</span>
|
||||
<strong style="flex:1;">${s.name || `Stage ${i + 1}`}</strong>
|
||||
<span class="badge">${s.stage_mode}</span>
|
||||
${s.persona_id && html`<span class="badge badge-active">persona</span>`}
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html`
|
||||
<div>
|
||||
<div class="admin-list">
|
||||
${workflows.length === 0 && html`<div class="empty-hint">No workflows available.</div>`}
|
||||
${workflows.map(w => html`
|
||||
<div class="admin-surface-row" key=${w.id} style="cursor:pointer;" onClick=${() => openDetail(w)}>
|
||||
<div style="flex:1;min-width:0;">
|
||||
<strong>${w.name}</strong>
|
||||
<span class="text-muted" style="margin-left:8px;font-size:11px;">/${w.slug}</span>
|
||||
</div>
|
||||
<span class="badge">${w.entry_mode}</span>
|
||||
<span class="badge ${w.is_active ? 'badge-active' : 'badge-inactive'}">
|
||||
${w.is_active ? 'active' : 'inactive'}
|
||||
</span>
|
||||
<span class="text-muted" style="font-size:11px;">v${w.version || 0}</span>
|
||||
</div>
|
||||
`)}
|
||||
${mine.length > 0 && html`
|
||||
<h4 style="margin:0 0 8px;">Claimed (${mine.length})</h4>
|
||||
<div class="admin-list">
|
||||
${mine.map(a => html`
|
||||
<${AssignmentRow} key=${a.id} assignment=${a} onAction=${load} showTeam />
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
|
||||
${available.length > 0 && html`
|
||||
<h4 style="margin:${mine.length > 0 ? '16px' : '0'} 0 8px;">Available (${available.length})</h4>
|
||||
<div class="admin-list">
|
||||
${available.map(a => html`
|
||||
<${AssignmentRow} key=${a.id} assignment=${a} onAction=${load} showTeam />
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
|
||||
${mine.length === 0 && available.length === 0 && html`
|
||||
<div class="empty-hint">No assignments</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function AssignmentRow({ assignment: a, onAction, showTeam }) {
|
||||
const userId = sw.auth?.user?.id;
|
||||
const isMine = a.assigned_to === userId;
|
||||
|
||||
async function claim() {
|
||||
try {
|
||||
await sw.api.workflowAssignments.claim(a.id);
|
||||
onAction();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function unclaim() {
|
||||
try {
|
||||
await sw.api.workflowAssignments.unclaim(a.id);
|
||||
onAction();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
async function complete() {
|
||||
try {
|
||||
await sw.api.workflowAssignments.complete(a.id);
|
||||
sw.toast('Assignment completed', 'success');
|
||||
onAction();
|
||||
} catch (e) { sw.toast(e.message, 'error'); }
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="admin-surface-row" style="${isMine ? 'border-left:3px solid var(--accent-1);' : ''}">
|
||||
<div style="flex:1;min-width:0;">
|
||||
<strong>${a.workflow_name || 'Workflow'}</strong>
|
||||
${showTeam && a.team_name && html`<span class="badge">${a.team_name}</span>`}
|
||||
<span class="badge">${a.stage_name || `Stage ${a.stage}`}</span>
|
||||
${a.sla_breached && html`<span class="badge badge-danger">SLA</span>`}
|
||||
<span class="text-muted" style="font-size:11px;">${_timeAgo(a.created_at)}</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:4px;">
|
||||
${a.status === 'unassigned' && html`
|
||||
<button class="btn-small btn-primary" onClick=${claim}>Claim</button>
|
||||
`}
|
||||
${isMine && html`
|
||||
<button class="btn-small" onClick=${unclaim}>Release</button>
|
||||
<button class="btn-small btn-primary"
|
||||
onClick=${() => { location.href = sw.base + '/w/' + a.channel_id; }}>Open</button>
|
||||
<button class="btn-small btn-success" onClick=${complete}>Complete</button>
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user