handleItemUpdate('status_id', statusId)}
+ />
{/if}
{:else if column.field_identifier === 'priority'}
diff --git a/frontend/src/lib/features/items/ItemDetail.svelte b/frontend/src/lib/features/items/ItemDetail.svelte
index 8179aaf78..c16e77ed7 100644
--- a/frontend/src/lib/features/items/ItemDetail.svelte
+++ b/frontend/src/lib/features/items/ItemDetail.svelte
@@ -726,6 +726,16 @@ import NativeSelect from '../../components/NativeSelect.svelte';
}
}
+ // A linked or child item's status changed from the detail view; refresh
+ // only the list that row came from.
+ async function handleRelatedStatusChanged({ kind }) {
+ if (kind === 'child-item') {
+ await itemDetailStore.loadChildItems();
+ } else {
+ await itemDetailStore.loadLinks();
+ }
+ }
+
async function handleParentChanged() {
// Reload the item data to get updated parent hierarchy
try {
@@ -1431,6 +1441,7 @@ import NativeSelect from '../../components/NativeSelect.svelte';
ondiagramSaved={handleDiagramSaved}
onexecuteAction={handleExecuteAction}
onreorderChildren={handleReorderChildren}
+ onrelatedStatusChanged={handleRelatedStatusChanged}
onclose={closeModal}
{recurrenceRule}
onsetupRecurrence={handleSetupRecurrence}
diff --git a/frontend/src/lib/features/items/ItemDetailContent.svelte b/frontend/src/lib/features/items/ItemDetailContent.svelte
index 0daeaedd9..e860a32e7 100644
--- a/frontend/src/lib/features/items/ItemDetailContent.svelte
+++ b/frontend/src/lib/features/items/ItemDetailContent.svelte
@@ -110,6 +110,7 @@
onexecuteAction = null,
onaiAction = null,
onreorderChildren = null,
+ onrelatedStatusChanged = null,
canCreate = false,
onclose = null,
recurrenceRule = null,
@@ -302,6 +303,10 @@
onreorderChildren?.();
}
+ function handleRelatedStatusChanged(data) {
+ return onrelatedStatusChanged?.(data);
+ }
+
function handleAIAction(data) {
onaiAction?.(data);
}
@@ -421,6 +426,7 @@
onviewtestcase={handleViewTestCase}
onshowlinkmodal={handleShowLinkModal}
onreorderchildren={handleReorderChildren}
+ onstatuschanged={handleRelatedStatusChanged}
/>
diff --git a/frontend/src/lib/features/items/ItemDetailLinks.svelte b/frontend/src/lib/features/items/ItemDetailLinks.svelte
index 1e579a12d..1a744b91c 100644
--- a/frontend/src/lib/features/items/ItemDetailLinks.svelte
+++ b/frontend/src/lib/features/items/ItemDetailLinks.svelte
@@ -8,10 +8,12 @@
import { attachClosestEdge, extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
import DropIndicator from '../../layout/DropIndicator.svelte';
import { api } from '../../api.js';
- import { objectDisplayName } from '../../utils/systemLabels.js';
import { t } from '../../stores/i18n.svelte.js';
import ItemKey from '../../components/ItemKey.svelte';
import StatusBadge from '../../components/StatusBadge.svelte';
+ import ItemStatusPicker from '../../pickers/ItemStatusPicker.svelte';
+ import { workspacePermissions } from '../../stores/workspacePermissions.svelte.js';
+ import { errorToast } from '../../stores/toasts.svelte.js';
let {
item,
@@ -33,6 +35,7 @@
onremovelink = undefined,
onshowlinkmodal = undefined,
onreorderchildren = undefined,
+ onstatuschanged = undefined,
} = $props();
const TEST_LINK_TYPE_ID = 1;
@@ -90,6 +93,25 @@
onshowlinkmodal?.({ preselectLinkTypeId });
}
+ // Rows with a status transition in flight; their picker stays locked until
+ // the refreshed list arrives.
+ let pendingStatusItemIds = $state(new Set());
+
+ async function changeStatus(rowItemId, statusId, kind) {
+ if (pendingStatusItemIds.has(rowItemId)) return;
+ pendingStatusItemIds = new Set(pendingStatusItemIds).add(rowItemId);
+ try {
+ await api.items.transition(rowItemId, statusId);
+ await onstatuschanged?.({ itemId: rowItemId, kind });
+ } catch (error) {
+ errorToast(t('dialogs.alerts.failedToUpdate', { error: `status: ${error.message || error}` }));
+ } finally {
+ const next = new Set(pendingStatusItemIds);
+ next.delete(rowItemId);
+ pendingStatusItemIds = next;
+ }
+ }
+
// Drag and drop state for child items
let dragState = $state(new Map());
let setupElements = new Map();
@@ -251,6 +273,24 @@
});
+
+{#snippet statusControl(rowItemId, rowWorkspaceId, statusId, statusName, statusBuiltinKey, statusColor, kind)}
+ {#if statusId && rowWorkspaceId}
+
+ changeStatus(rowItemId, nextStatusId, kind)}
+ />
+
+ {/if}
+{/snippet}
+
{#if workItemLinks.length > 0}
@@ -299,6 +339,7 @@
{@const linkedItemTypeColor = isCurrentSource ? link.target_item_type_color : link.source_item_type_color}
{@const linkedItemStatusName = isCurrentSource ? link.target_status_name : link.source_status_name}
{@const linkedItemStatusColor = isCurrentSource ? link.target_status_color : link.source_status_color}
+ {@const linkedItemStatusId = isCurrentSource ? link.target_status_id : link.source_status_id}
{@const linkTypeName = link.link_type_name}
{@const linkTypeLabel = getLinkLabel(link)}
{@const linkTypeColor = link.link_type_color || 'var(--ds-text-subtle)'}
@@ -352,7 +393,9 @@
{linkTypeLabel}
{/if}
- {#if linkedItemStatusName}
+ {#if linkedItemType === 'item'}
+ {@render statusControl(linkedItemId, linkedItemWorkspaceId, linkedItemStatusId, linkedItemStatusName, '', linkedItemStatusColor, 'linked-item')}
+ {:else if linkedItemStatusName}
-
+
- {#if childItem.status_name}
-
- {/if}
+ {@render statusControl(childItem.id, childItem.workspace_id || workspaceId, childItem.status_id, childItem.status_name, childItem.status_builtin_key, childItem.status_color, 'child-item')}
{/each}
diff --git a/frontend/src/lib/pickers/ItemStatusPicker.svelte b/frontend/src/lib/pickers/ItemStatusPicker.svelte
new file mode 100644
index 000000000..ff983344d
--- /dev/null
+++ b/frontend/src/lib/pickers/ItemStatusPicker.svelte
@@ -0,0 +1,65 @@
+
+
+{#if canEdit}
+ collectionEditorOptions.load(workspaceId, 'statuses')}
+ onSelect={async (selected) => {
+ const nextStatusId = selected?.id;
+ if (nextStatusId && nextStatusId !== statusId) {
+ await onselect?.(nextStatusId);
+ }
+ }}
+ >
+ {#snippet children()}
+
+
+
+ {/snippet}
+
+{:else}
+
+{/if}
diff --git a/frontend/src/lib/pickers/pickerConfigs.js b/frontend/src/lib/pickers/pickerConfigs.js
index 7036bc7a5..b7f5a9286 100644
--- a/frontend/src/lib/pickers/pickerConfigs.js
+++ b/frontend/src/lib/pickers/pickerConfigs.js
@@ -42,7 +42,7 @@ export function createStatusPickerConfig(statusCategories) {
type: 'color-dot',
source: (status) => {
const category = statusCategories.find((sc) => sc.id === status.category_id);
- return category?.color || '#6b7280';
+ return category?.color || status.category_color || '#6b7280';
},
size: 'w-2 h-2',
},