Страница /reports трансформирована в Центр статусов задач:
Backend:
- GET /api/reports/summary — агрегированные счётчики тип×статус (5 корзин)
- GET/PUT /api/settings/reports — глобальные настройки отчётов
- _filter_tasks_by_rbac() — row-level фильтрация по роли
- normalize_task_report: LLM-валидация с ошибками → FAILED/PARTIAL
- get_summary(): 5 корзин pending/running/awaiting_input/success/failed
Frontend:
- TaskCenterModel.svelte.ts (400 строк) — Screen Model
- SummaryPanel — сводная панель с цветовым кодированием и active filter
- ReportCard — humanized labels, duration, task_id, failed border
- FilterBar — search + sort + time range с label'ами
- Pagination — showingText, уникальные id для select
- Quick views: «Упавшие», «В работе», «Успешные»
- TaskDrawer: scroll-to-error, footer скрыт для terminal, «Н/Д» fix
Тесты: 48 backend + 38 frontend (2521 всего)
Build: ✅ Console errors: 0
135 lines
3.8 KiB
TypeScript
135 lines
3.8 KiB
TypeScript
// #region TaskDrawerStore [C:3] [TYPE Store] [SEMANTICS task, drawer, store, resource, mapping]
|
|
// @ingroup Stores
|
|
// @BRIEF Manage Task Drawer visibility, active task binding, and resource-to-task mapping.
|
|
// @LAYER UI
|
|
// @RELATION BINDS_TO -> [ActivityStore]
|
|
// @UX_STATE Closed -> Drawer hidden, no active task
|
|
// @UX_STATE Open -> Drawer visible with task logs or task list
|
|
// @RATIONALE Migrated from Svelte 4 writable to Svelte 5 $state runes.
|
|
// Backward-compatible .subscribe() retained for $taskDrawerStore syntax.
|
|
|
|
interface TaskInfo {
|
|
taskId: string;
|
|
status: string;
|
|
}
|
|
|
|
interface ResourceTaskMap {
|
|
[k: string]: TaskInfo;
|
|
}
|
|
|
|
interface TaskDrawerState {
|
|
isOpen: boolean;
|
|
activeTaskId: string | null;
|
|
scrollToHint: string | null;
|
|
resourceTaskMap: ResourceTaskMap;
|
|
}
|
|
|
|
const TASK_DRAWER_AUTO_OPEN_STORAGE_KEY = 'ss_tools.profile.auto_open_task_drawer';
|
|
|
|
function readAutoOpenTaskDrawerPreference(): boolean {
|
|
if (typeof window === 'undefined') return true;
|
|
const rawValue = window.localStorage.getItem(TASK_DRAWER_AUTO_OPEN_STORAGE_KEY);
|
|
if (rawValue === 'false') return false;
|
|
return true;
|
|
}
|
|
|
|
let autoOpenTaskDrawerPreference: boolean = readAutoOpenTaskDrawerPreference();
|
|
|
|
const initialState: TaskDrawerState = {
|
|
isOpen: false,
|
|
activeTaskId: null,
|
|
scrollToHint: null,
|
|
resourceTaskMap: {},
|
|
};
|
|
|
|
// Reactive state (Svelte 5 rune)
|
|
let _state: TaskDrawerState = $state(initialState);
|
|
|
|
// Subscriber list for backward compat with $taskDrawerStore syntax
|
|
let _subscribers: Array<(_value: TaskDrawerState) => void> = [];
|
|
|
|
function _notify(): void {
|
|
const current = _state;
|
|
_subscribers.forEach((fn) => fn(current));
|
|
}
|
|
|
|
export const taskDrawerStore = {
|
|
subscribe(run: (_value: TaskDrawerState) => void): () => void {
|
|
_subscribers.push(run);
|
|
run(_state);
|
|
return () => {
|
|
_subscribers = _subscribers.filter((fn) => fn !== run);
|
|
};
|
|
},
|
|
get value(): TaskDrawerState {
|
|
return _state;
|
|
},
|
|
set(newState: TaskDrawerState) {
|
|
_state = newState;
|
|
_notify();
|
|
},
|
|
update(fn: (_state: TaskDrawerState) => TaskDrawerState) {
|
|
_state = fn(_state);
|
|
_notify();
|
|
},
|
|
};
|
|
|
|
export function openDrawerForTask(taskId: string, scrollToHint?: string): boolean {
|
|
if (!taskId) {
|
|
return false;
|
|
}
|
|
if (_state.isOpen && _state.activeTaskId === taskId) return true; // Already open for this task
|
|
_state = { ..._state, isOpen: true, activeTaskId: taskId, scrollToHint: scrollToHint ?? null };
|
|
_notify();
|
|
return true;
|
|
}
|
|
|
|
export function setTaskDrawerAutoOpenPreference(enabled: boolean): void {
|
|
autoOpenTaskDrawerPreference = enabled !== false;
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(
|
|
TASK_DRAWER_AUTO_OPEN_STORAGE_KEY,
|
|
autoOpenTaskDrawerPreference ? 'true' : 'false',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function getTaskDrawerAutoOpenPreference(): boolean {
|
|
return autoOpenTaskDrawerPreference;
|
|
}
|
|
|
|
export function openDrawerForTaskIfPreferred(taskId: string): boolean {
|
|
if (autoOpenTaskDrawerPreference !== true) {
|
|
return false;
|
|
}
|
|
return openDrawerForTask(taskId);
|
|
}
|
|
|
|
export function openDrawer(): void {
|
|
if (_state.isOpen && _state.activeTaskId === null) return;
|
|
_state = { ..._state, isOpen: true, activeTaskId: null };
|
|
_notify();
|
|
}
|
|
|
|
export function closeDrawer(): void {
|
|
if (!_state.isOpen) return;
|
|
_state = { ..._state, isOpen: false, activeTaskId: null };
|
|
_notify();
|
|
}
|
|
|
|
export function updateResourceTask(resourceId: string, taskId: string, status: string): void {
|
|
const newMap: ResourceTaskMap = { ..._state.resourceTaskMap };
|
|
if (status === 'IDLE' || status === 'SUCCESS' || status === 'ERROR') {
|
|
delete newMap[resourceId];
|
|
} else {
|
|
newMap[resourceId] = { taskId, status };
|
|
}
|
|
_state = { ..._state, resourceTaskMap: newMap };
|
|
_notify();
|
|
}
|
|
|
|
export function getTaskForResource(resourceId: string): TaskInfo | null {
|
|
return _state.resourceTaskMap[resourceId] || null;
|
|
}
|
|
// #endregion TaskDrawerStore
|