таски готовы
This commit is contained in:
63
frontend/src/lib/components/reports/ReportCard.svelte
Normal file
63
frontend/src/lib/components/reports/ReportCard.svelte
Normal file
@@ -0,0 +1,63 @@
|
||||
<!-- [DEF:ReportCard:Component] -->
|
||||
<script>
|
||||
/**
|
||||
* @TIER: CRITICAL
|
||||
* @SEMANTICS: reports, card, type-profile, accessibility, fallback
|
||||
* @PURPOSE: Render one report with explicit textual type label and profile-driven visual variant.
|
||||
* @LAYER: UI
|
||||
* @RELATION: DEPENDS_ON -> frontend/src/lib/components/reports/reportTypeProfiles.js
|
||||
* @RELATION: DEPENDS_ON -> frontend/src/lib/i18n/index.ts
|
||||
* @INVARIANT: Unknown task type always uses fallback profile.
|
||||
*
|
||||
* @UX_STATE: Ready -> Card displays summary/status/type.
|
||||
* @UX_RECOVERY: Missing fields are rendered with explicit placeholder text.
|
||||
*/
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { t } from '$lib/i18n';
|
||||
import { getReportTypeProfile } from './reportTypeProfiles.js';
|
||||
|
||||
let { report, selected = false } = $props();
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const profile = $derived(getReportTypeProfile(report?.task_type));
|
||||
const profileLabel = $derived(typeof profile?.label === 'function' ? profile.label() : profile?.label);
|
||||
|
||||
function getStatusClass(status) {
|
||||
if (status === 'success') return 'bg-green-100 text-green-700';
|
||||
if (status === 'failed') return 'bg-red-100 text-red-700';
|
||||
if (status === 'in_progress') return 'bg-blue-100 text-blue-700';
|
||||
if (status === 'partial') return 'bg-amber-100 text-amber-700';
|
||||
return 'bg-slate-100 text-slate-700';
|
||||
}
|
||||
|
||||
function formatDate(value) {
|
||||
if (!value) return $t.reports?.not_provided || 'Not provided';
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return $t.reports?.not_provided || 'Not provided';
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function onSelect() {
|
||||
dispatch('select', { report });
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="w-full rounded-lg border p-3 text-left transition hover:bg-slate-50 {selected ? 'border-blue-400 bg-blue-50' : 'border-slate-200 bg-white'}"
|
||||
on:click={onSelect}
|
||||
aria-label={`Report ${report?.report_id || ''} type ${profileLabel || ($t.reports?.unknown_type || 'Other / Unknown Type')}`}
|
||||
>
|
||||
<div class="mb-2 flex items-center justify-between gap-2">
|
||||
<span class="rounded px-2 py-0.5 text-xs font-semibold {profile?.variant || 'bg-slate-100 text-slate-700'}">
|
||||
{profileLabel || ($t.reports?.unknown_type || 'Other / Unknown Type')}
|
||||
</span>
|
||||
<span class="rounded px-2 py-0.5 text-xs font-semibold {getStatusClass(report?.status)}">
|
||||
{report?.status || ($t.reports?.not_provided || 'Not provided')}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm font-medium text-slate-800">{report?.summary || ($t.reports?.not_provided || 'Not provided')}</p>
|
||||
<p class="mt-1 text-xs text-slate-500">{formatDate(report?.updated_at)}</p>
|
||||
</button>
|
||||
|
||||
<!-- [/DEF:ReportCard:Component] -->
|
||||
Reference in New Issue
Block a user