таски готовы

This commit is contained in:
2026-02-23 10:18:56 +03:00
parent 2946ee9b42
commit 066ef5eab5
48 changed files with 3559 additions and 72 deletions

View File

@@ -58,6 +58,13 @@
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "M4 5h16M4 12h16M4 19h10",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",
@@ -118,6 +125,13 @@
},
],
},
{
id: "reports",
label: $t.nav?.reports || "REPORTS",
icon: "M4 5h16M4 12h16M4 19h10",
path: "/reports",
subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }],
},
{
id: "admin",
label: $t.nav?.admin || "ADMIN",

View File

@@ -54,6 +54,11 @@
closeDrawer();
}
function goToTasksPage() {
closeDrawer();
window.location.href = "/tasks";
}
// Handle overlay click
function handleOverlayClick(event) {
if (event.target === event.currentTarget) {
@@ -239,23 +244,31 @@
>
{/if}
</div>
<button
class="p-1.5 rounded-md text-slate-500 bg-transparent border-none cursor-pointer transition-all hover:text-slate-100 hover:bg-slate-800"
on:click={handleClose}
aria-label="Close drawer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
<div class="flex items-center gap-2">
<button
class="px-2.5 py-1 text-xs font-semibold rounded-md border border-slate-700 text-slate-300 bg-slate-800/60 hover:bg-slate-800 transition-colors"
on:click={goToTasksPage}
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
{$t.nav?.tasks || "Tasks"}
</button>
<button
class="p-1.5 rounded-md text-slate-500 bg-transparent border-none cursor-pointer transition-all hover:text-slate-100 hover:bg-slate-800"
on:click={handleClose}
aria-label="Close drawer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- Content -->
@@ -318,4 +331,3 @@
<!-- [/DEF:TaskDrawer:Component] -->

View File

@@ -0,0 +1,105 @@
// [DEF:__tests__/test_breadcrumbs:Module]
// @TIER: STANDARD
// @PURPOSE: Contract-focused unit tests for Breadcrumbs.svelte logic and UX annotations
// @LAYER: UI
// @RELATION: VERIFIES -> frontend/src/lib/components/layout/Breadcrumbs.svelte
import { describe, it, expect } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
const COMPONENT_PATH = path.resolve(
process.cwd(),
'src/lib/components/layout/Breadcrumbs.svelte'
);
function getBreadcrumbs(pathname, maxVisible = 3) {
const segments = pathname.split('/').filter(Boolean);
const allItems = [{ label: 'Home', path: '/' }];
let currentPath = '';
segments.forEach((segment, index) => {
currentPath += `/${segment}`;
const label = formatBreadcrumbLabel(segment);
allItems.push({
label,
path: currentPath,
isLast: index === segments.length - 1
});
});
if (allItems.length > maxVisible) {
const firstItem = allItems[0];
const itemsToShow = [];
itemsToShow.push(firstItem);
itemsToShow.push({ isEllipsis: true });
const startFromIndex = allItems.length - (maxVisible - 1);
for (let i = startFromIndex; i < allItems.length; i++) {
itemsToShow.push(allItems[i]);
}
return itemsToShow;
}
return allItems;
}
function formatBreadcrumbLabel(segment) {
const specialCases = {
dashboards: 'Dashboards',
datasets: 'Datasets',
storage: 'Storage',
admin: 'Admin',
settings: 'Settings',
git: 'Git'
};
if (specialCases[segment]) {
return specialCases[segment];
}
return segment
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
describe('Breadcrumbs Component Contract & Logic', () => {
it('contains required UX tags and semantic header for STANDARD module', () => {
const source = fs.readFileSync(COMPONENT_PATH, 'utf-8');
expect(source).toContain('@TIER: STANDARD');
expect(source).toContain('@UX_STATE: Idle');
expect(source).toContain('@UX_FEEDBACK');
expect(source).toContain('@UX_RECOVERY');
expect(source).toContain('@RELATION: DEPENDS_ON -> page store');
});
it('returns Home for root path (Short-Path UX state)', () => {
const result = getBreadcrumbs('/', 3);
expect(result).toEqual([{ label: 'Home', path: '/' }]);
});
it('maps known segments to expected labels', () => {
expect(formatBreadcrumbLabel('dashboards')).toBe('Dashboards');
expect(formatBreadcrumbLabel('datasets')).toBe('Datasets');
expect(formatBreadcrumbLabel('settings')).toBe('Settings');
});
it('formats unknown kebab-case segment to title case', () => {
expect(formatBreadcrumbLabel('data-quality-rules')).toBe('Data Quality Rules');
});
it('truncates long paths with ellipsis and keeps tail segments', () => {
const result = getBreadcrumbs('/dashboards/segment-a/segment-b/segment-c', 3);
expect(result[0]).toEqual({ label: 'Home', path: '/' });
expect(result[1]).toEqual({ isEllipsis: true });
const lastItem = result[result.length - 1];
expect('label' in lastItem && lastItem.label).toBe('Segment C');
expect(result.length).toBe(4);
});
});
// [/DEF:__tests__/test_breadcrumbs:Module]