feat: implement Story 2.1 — Owner/Manager Command Center Dashboard
- Rewrite DashboardController with cached role-scoped KPI aggregation (Cache::remember, 5-min TTL, Declaration::forUser scope) - Create StatCard.vue component with CVA status variants and a11y - Rewrite Dashboard.vue with 4-column KPI grid + urgent declarations table - Add mise_en_demeure status to DeclarationStatus enum with transitions - Exclude termine, mise_en_demeure, ferme from dashboard queries - Set deadline proximity red threshold to ≤5 days - Add abort(404) for non-member workspace access per architecture - Fix null-safe client access for soft-deleted clients - Fix hardcoded routes with Wayfinder type-safe imports - Fix DashboardProps.stats type to allow null - Add aria-pressed to StatCard for accessibility - Install shadcn-vue table component (11 files) - Add 11 Pest feature tests + 3 mise_en_demeure transition tests - Fix DeclarationFactory eager workspace creation causing slug collisions - 196 tests pass, 836 assertions, zero regressions
This commit is contained in:
@@ -1,57 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { Head, Link, router } from '@inertiajs/vue3';
|
||||
import {
|
||||
Briefcase,
|
||||
Building2,
|
||||
Users,
|
||||
EllipsisVertical,
|
||||
Eye,
|
||||
FolderOpen,
|
||||
AlertTriangle,
|
||||
Clock,
|
||||
FileCheck,
|
||||
MessageSquareWarning,
|
||||
ArrowRight,
|
||||
FileStack,
|
||||
Send,
|
||||
UserRoundCog,
|
||||
Users,
|
||||
} from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
|
||||
import StatCard from '@/components/dashboard/StatCard.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { dashboard } from '@/routes';
|
||||
import type { BreadcrumbItem } from '@/types';
|
||||
import { index as usersIndex } from '@/routes/users';
|
||||
import { index as workspacesIndex } from '@/routes/workspaces';
|
||||
import type {
|
||||
BreadcrumbItem,
|
||||
DashboardDeclaration,
|
||||
DashboardProps,
|
||||
StatCardLink,
|
||||
} from '@/types';
|
||||
|
||||
type AssignedDeclaration = {
|
||||
id: number;
|
||||
title: string;
|
||||
type: string;
|
||||
client_name: string;
|
||||
status: string;
|
||||
due_date: string | null;
|
||||
priority: string | null;
|
||||
showUrl: string;
|
||||
};
|
||||
|
||||
type NotificationItem = {
|
||||
id: number;
|
||||
title: string;
|
||||
client_name: string;
|
||||
due_date?: string;
|
||||
showUrl: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
assignedDeclarations: AssignedDeclaration[];
|
||||
notifications: {
|
||||
overdue: NotificationItem[];
|
||||
due_soon: NotificationItem[];
|
||||
documents_received: NotificationItem[];
|
||||
awaiting_validation: NotificationItem[];
|
||||
};
|
||||
workspaceName: string | null;
|
||||
declarationsUrl: string | null;
|
||||
clientsUrl: string | null;
|
||||
};
|
||||
type Props = DashboardProps;
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
@@ -64,74 +53,53 @@ const breadcrumbs: BreadcrumbItem[] = [
|
||||
|
||||
const hasWorkspace = computed(() => !!props.workspaceName);
|
||||
|
||||
const typeLabels: Record<string, string> = {
|
||||
vat: 'TVA',
|
||||
vat_monthly: 'TVA mensuelle',
|
||||
vat_quarterly: 'TVA trimestrielle',
|
||||
corporate_tax: 'IS',
|
||||
income_tax: 'IR',
|
||||
cnss: 'CNSS',
|
||||
annual_balance: 'Bilan',
|
||||
other: 'Autre',
|
||||
};
|
||||
type DeadlineProximity = 'safe' | 'approaching' | 'urgent' | 'overdue' | 'none';
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
draft: 'Brouillon',
|
||||
waiting_documents: 'En attente documents',
|
||||
documents_received: 'Documents reçus',
|
||||
processing: 'En cours',
|
||||
additional_documents_requested: 'Pièces complémentaires',
|
||||
waiting_client_validation: 'En attente validation',
|
||||
validated: 'Validé',
|
||||
closed: 'Clôturé',
|
||||
cancelled: 'Annulé',
|
||||
};
|
||||
|
||||
const statusVariant: Record<
|
||||
string,
|
||||
'default' | 'secondary' | 'destructive' | 'outline'
|
||||
> = {
|
||||
draft: 'secondary',
|
||||
waiting_documents: 'outline',
|
||||
documents_received: 'default',
|
||||
processing: 'default',
|
||||
additional_documents_requested: 'default',
|
||||
waiting_client_validation: 'outline',
|
||||
validated: 'secondary',
|
||||
closed: 'secondary',
|
||||
cancelled: 'secondary',
|
||||
};
|
||||
|
||||
function statusLabel(s: string): string {
|
||||
return statusLabels[s] ?? s;
|
||||
function deadlineProximity(dueDate: string | null): DeadlineProximity {
|
||||
if (!dueDate) return 'none';
|
||||
const now = new Date();
|
||||
const deadline = new Date(dueDate);
|
||||
const diffDays = Math.ceil(
|
||||
(deadline.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
|
||||
);
|
||||
if (diffDays < 0) return 'overdue';
|
||||
if (diffDays <= 5) return 'urgent';
|
||||
if (diffDays <= 7) return 'approaching';
|
||||
return 'safe';
|
||||
}
|
||||
|
||||
function typeLabel(t: string): string {
|
||||
return typeLabels[t] ?? t;
|
||||
}
|
||||
|
||||
function progressPercent(status: string): number {
|
||||
const steps: Record<string, number> = {
|
||||
draft: 0,
|
||||
waiting_documents: 10,
|
||||
documents_received: 30,
|
||||
processing: 50,
|
||||
additional_documents_requested: 45,
|
||||
waiting_client_validation: 80,
|
||||
validated: 100,
|
||||
closed: 100,
|
||||
cancelled: 0,
|
||||
function deadlineClass(dueDate: string | null): string {
|
||||
const proximity = deadlineProximity(dueDate);
|
||||
const map: Record<DeadlineProximity, string> = {
|
||||
safe: 'text-green-600',
|
||||
approaching: 'text-amber-600',
|
||||
urgent: 'text-red-600',
|
||||
overdue: 'text-red-600 animate-pulse',
|
||||
none: 'text-muted-foreground',
|
||||
};
|
||||
return steps[status] ?? 50;
|
||||
return map[proximity];
|
||||
}
|
||||
|
||||
const hasAnyNotifications = computed(
|
||||
() =>
|
||||
props.notifications.overdue.length > 0 ||
|
||||
props.notifications.due_soon.length > 0 ||
|
||||
props.notifications.documents_received.length > 0 ||
|
||||
props.notifications.awaiting_validation.length > 0,
|
||||
);
|
||||
function statusBadgeVariant(
|
||||
status: string,
|
||||
): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
const map: Record<
|
||||
string,
|
||||
'default' | 'secondary' | 'destructive' | 'outline'
|
||||
> = {
|
||||
created: 'secondary',
|
||||
en_cours: 'default',
|
||||
en_attente_client: 'outline',
|
||||
termine: 'secondary',
|
||||
mise_en_demeure: 'destructive',
|
||||
ferme: 'secondary',
|
||||
};
|
||||
return map[status] ?? 'secondary';
|
||||
}
|
||||
|
||||
function navigateToDeclaration(declaration: DashboardDeclaration): void {
|
||||
router.get(declaration.showUrl);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -141,13 +109,13 @@ const hasAnyNotifications = computed(
|
||||
<div
|
||||
class="flex h-full flex-1 flex-col gap-6 overflow-x-auto rounded-xl p-4"
|
||||
>
|
||||
<!-- Quick links when no workspace -->
|
||||
<!-- Quick links when no workspace (admin view) -->
|
||||
<div
|
||||
v-if="!hasWorkspace"
|
||||
class="grid auto-rows-min gap-4 md:grid-cols-3"
|
||||
>
|
||||
<Link
|
||||
href="/users"
|
||||
:href="usersIndex().url"
|
||||
class="relative flex aspect-video flex-col items-center justify-center gap-2 overflow-hidden rounded-xl border border-sidebar-border/70 transition-colors hover:bg-muted/50 dark:border-sidebar-border"
|
||||
>
|
||||
<Users class="h-8 w-8" />
|
||||
@@ -157,7 +125,7 @@ const hasAnyNotifications = computed(
|
||||
>
|
||||
</Link>
|
||||
<Link
|
||||
href="/workspaces"
|
||||
:href="workspacesIndex().url"
|
||||
class="relative flex aspect-video flex-col items-center justify-center gap-2 overflow-hidden rounded-xl border border-sidebar-border/70 transition-colors hover:bg-muted/50 dark:border-sidebar-border"
|
||||
>
|
||||
<Building2 class="h-8 w-8" />
|
||||
@@ -179,190 +147,27 @@ const hasAnyNotifications = computed(
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="hasWorkspace"
|
||||
class="grid auto-rows-min gap-4 md:grid-cols-3"
|
||||
>
|
||||
<Link
|
||||
v-if="declarationsUrl"
|
||||
:href="declarationsUrl"
|
||||
class="relative flex aspect-video flex-col items-center justify-center gap-2 overflow-hidden rounded-xl border border-sidebar-border/70 transition-colors hover:bg-muted/50 dark:border-sidebar-border"
|
||||
>
|
||||
<FileStack class="h-8 w-8" />
|
||||
<span class="font-medium">Déclarations</span>
|
||||
<span class="text-xs text-muted-foreground"
|
||||
>Gérer les déclarations</span
|
||||
>
|
||||
</Link>
|
||||
<Link
|
||||
v-if="clientsUrl"
|
||||
:href="clientsUrl"
|
||||
class="relative flex aspect-video flex-col items-center justify-center gap-2 overflow-hidden rounded-xl border border-sidebar-border/70 transition-colors hover:bg-muted/50 dark:border-sidebar-border"
|
||||
>
|
||||
<Briefcase class="h-8 w-8" />
|
||||
<span class="font-medium">Clients</span>
|
||||
<span class="text-xs text-muted-foreground"
|
||||
>Manage clients</span
|
||||
>
|
||||
</Link>
|
||||
<div
|
||||
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
|
||||
>
|
||||
<PlaceholderPattern />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Workspace dashboard -->
|
||||
<template v-if="hasWorkspace">
|
||||
<!-- Notifications -->
|
||||
<div v-if="hasAnyNotifications" class="space-y-4">
|
||||
<h2 class="text-lg font-semibold">À traiter</h2>
|
||||
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card
|
||||
v-if="notifications.overdue.length > 0"
|
||||
class="border-destructive/50 bg-destructive/5"
|
||||
>
|
||||
<CardHeader class="pb-2">
|
||||
<CardTitle
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<AlertTriangle
|
||||
class="h-4 w-4 text-destructive"
|
||||
/>
|
||||
En retard
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<Link
|
||||
v-for="item in notifications.overdue"
|
||||
:key="item.id"
|
||||
:href="item.showUrl"
|
||||
class="flex items-center justify-between rounded-md p-2 text-sm transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div class="truncate">
|
||||
<span class="font-medium">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<span
|
||||
class="ml-1 text-muted-foreground"
|
||||
>
|
||||
{{ item.client_name }}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 shrink-0" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card
|
||||
v-if="notifications.due_soon.length > 0"
|
||||
class="border-amber-500/50 bg-amber-500/5"
|
||||
>
|
||||
<CardHeader class="pb-2">
|
||||
<CardTitle
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<Clock class="h-4 w-4 text-amber-600" />
|
||||
Échéance proche
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<Link
|
||||
v-for="item in notifications.due_soon"
|
||||
:key="item.id"
|
||||
:href="item.showUrl"
|
||||
class="flex items-center justify-between rounded-md p-2 text-sm transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div class="truncate">
|
||||
<span class="font-medium">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<span
|
||||
class="ml-1 text-muted-foreground"
|
||||
>
|
||||
{{ item.client_name }} —
|
||||
{{ item.due_date }}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 shrink-0" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card
|
||||
v-if="notifications.documents_received.length > 0"
|
||||
class="border-primary/50 bg-primary/5"
|
||||
>
|
||||
<CardHeader class="pb-2">
|
||||
<CardTitle
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<FileCheck class="h-4 w-4 text-primary" />
|
||||
Documents reçus
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<Link
|
||||
v-for="item in notifications.documents_received"
|
||||
:key="item.id"
|
||||
:href="item.showUrl"
|
||||
class="flex items-center justify-between rounded-md p-2 text-sm transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div class="truncate">
|
||||
<span class="font-medium">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<span
|
||||
class="ml-1 text-muted-foreground"
|
||||
>
|
||||
{{ item.client_name }}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 shrink-0" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card
|
||||
v-if="notifications.awaiting_validation.length > 0"
|
||||
class="border-blue-500/50 bg-blue-500/5"
|
||||
>
|
||||
<CardHeader class="pb-2">
|
||||
<CardTitle
|
||||
class="flex items-center gap-2 text-base"
|
||||
>
|
||||
<MessageSquareWarning
|
||||
class="h-4 w-4 text-blue-600"
|
||||
/>
|
||||
En attente validation client
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<Link
|
||||
v-for="item in notifications.awaiting_validation"
|
||||
:key="item.id"
|
||||
:href="item.showUrl"
|
||||
class="flex items-center justify-between rounded-md p-2 text-sm transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<div class="truncate">
|
||||
<span class="font-medium">{{
|
||||
item.title
|
||||
}}</span>
|
||||
<span
|
||||
class="ml-1 text-muted-foreground"
|
||||
>
|
||||
{{ item.client_name }}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight class="h-4 w-4 shrink-0" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<!-- KPI StatCards -->
|
||||
<div
|
||||
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"
|
||||
>
|
||||
<StatCard
|
||||
v-for="card in statCards"
|
||||
:key="card.label"
|
||||
:label="card.label"
|
||||
:count="card.count"
|
||||
:status="card.status as StatCardLink['status']"
|
||||
:href="card.href"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- My assigned declarations -->
|
||||
<!-- Urgent Declarations Table -->
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold">
|
||||
Mes déclarations — {{ workspaceName }}
|
||||
Déclarations urgentes
|
||||
</h2>
|
||||
<Button
|
||||
v-if="declarationsUrl"
|
||||
@@ -371,144 +176,117 @@ const hasAnyNotifications = computed(
|
||||
>
|
||||
<Link :href="declarationsUrl">
|
||||
Toutes les déclarations
|
||||
<ArrowRight class="ml-1 h-4 w-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card
|
||||
v-if="assignedDeclarations.length > 0"
|
||||
v-if="declarations.length > 0"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
<thead
|
||||
class="border-b border-sidebar-border/70 bg-muted/50"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
class="h-10 px-4 text-left font-medium"
|
||||
>
|
||||
Déclaration / Client
|
||||
</th>
|
||||
<th
|
||||
class="h-10 px-4 text-left font-medium"
|
||||
>
|
||||
Type
|
||||
</th>
|
||||
<th
|
||||
class="h-10 px-4 text-left font-medium"
|
||||
>
|
||||
Statut
|
||||
</th>
|
||||
<th
|
||||
class="h-10 px-4 text-left font-medium"
|
||||
>
|
||||
Progression
|
||||
</th>
|
||||
<th
|
||||
class="h-10 px-4 text-left font-medium"
|
||||
>
|
||||
Date limite
|
||||
</th>
|
||||
<th class="h-10 w-10 px-4"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="declaration in assignedDeclarations"
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Client</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Date limite</TableHead>
|
||||
<TableHead>Assigné à</TableHead>
|
||||
<TableHead>Statut</TableHead>
|
||||
<TableHead class="w-10" />
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow
|
||||
v-for="declaration in declarations"
|
||||
:key="declaration.id"
|
||||
class="border-b border-sidebar-border/50 transition-colors last:border-0 hover:bg-muted/30"
|
||||
class="cursor-pointer"
|
||||
@click="
|
||||
navigateToDeclaration(declaration)
|
||||
"
|
||||
>
|
||||
<td class="px-4 py-3">
|
||||
<Link
|
||||
:href="declaration.showUrl"
|
||||
class="block font-medium hover:underline"
|
||||
>
|
||||
{{ declaration.title }}
|
||||
</Link>
|
||||
<TableCell class="font-medium">
|
||||
{{ declaration.clientName }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{{ declaration.typeLabel }}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
class="block text-xs text-muted-foreground"
|
||||
>
|
||||
{{ declaration.client_name }}
|
||||
</span>
|
||||
</td>
|
||||
<td
|
||||
class="px-4 py-3 text-muted-foreground"
|
||||
>
|
||||
{{ typeLabel(declaration.type) }}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<Badge
|
||||
:variant="
|
||||
statusVariant[
|
||||
declaration.status
|
||||
] ?? 'secondary'
|
||||
:class="
|
||||
deadlineClass(
|
||||
declaration.dueDate,
|
||||
)
|
||||
"
|
||||
>
|
||||
{{
|
||||
statusLabel(
|
||||
{{ declaration.dueDate ?? '—' }}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{{
|
||||
declaration.assigneeName ?? '—'
|
||||
}}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
:variant="
|
||||
statusBadgeVariant(
|
||||
declaration.status,
|
||||
)
|
||||
}}
|
||||
"
|
||||
>
|
||||
{{ declaration.statusLabel }}
|
||||
</Badge>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div
|
||||
class="flex h-2 w-24 overflow-hidden rounded-full bg-muted"
|
||||
>
|
||||
<div
|
||||
class="h-full bg-primary transition-all"
|
||||
:style="{
|
||||
width: `${progressPercent(declaration.status)}%`,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
class="text-xs text-muted-foreground"
|
||||
>
|
||||
{{
|
||||
progressPercent(
|
||||
declaration.status,
|
||||
)
|
||||
}}%
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<span
|
||||
:class="{
|
||||
'font-medium text-destructive':
|
||||
declaration.due_date &&
|
||||
declaration.due_date <
|
||||
new Date()
|
||||
.toISOString()
|
||||
.slice(0, 10),
|
||||
}"
|
||||
>
|
||||
{{
|
||||
declaration.due_date || '—'
|
||||
}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
as-child
|
||||
>
|
||||
<Link
|
||||
:href="declaration.showUrl"
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
as-child
|
||||
@click.stop
|
||||
>
|
||||
Voir
|
||||
<ArrowRight
|
||||
class="ml-1 h-3 w-3"
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
>
|
||||
<EllipsisVertical
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
>
|
||||
<DropdownMenuItem
|
||||
@click.stop="
|
||||
navigateToDeclaration(
|
||||
declaration,
|
||||
)
|
||||
"
|
||||
>
|
||||
<Eye
|
||||
class="mr-2 h-4 w-4"
|
||||
/>
|
||||
Voir
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>
|
||||
<Send
|
||||
class="mr-2 h-4 w-4"
|
||||
/>
|
||||
Relancer
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem disabled>
|
||||
<UserRoundCog
|
||||
class="mr-2 h-4 w-4"
|
||||
/>
|
||||
Réassigner
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -519,15 +297,9 @@ const hasAnyNotifications = computed(
|
||||
<FolderOpen
|
||||
class="mb-3 h-12 w-12 text-muted-foreground"
|
||||
/>
|
||||
<p class="mb-2 text-muted-foreground">
|
||||
Aucune déclaration ne vous est assignée pour le
|
||||
moment.
|
||||
<p class="text-muted-foreground">
|
||||
Aucune déclaration urgente pour le moment.
|
||||
</p>
|
||||
<Button v-if="declarationsUrl" as-child>
|
||||
<Link :href="declarationsUrl"
|
||||
>Voir toutes les déclarations</Link
|
||||
>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user