Files
L-Ami-Fiduciaire/resources/js/pages/Dashboard.vue

538 lines
24 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import {
Briefcase,
Building2,
Users,
FolderOpen,
AlertTriangle,
Clock,
FileCheck,
MessageSquareWarning,
ArrowRight,
FileStack,
} from 'lucide-vue-next';
import { computed } from 'vue';
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import AppLayout from '@/layouts/AppLayout.vue';
import { dashboard } from '@/routes';
import type { BreadcrumbItem } 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;
};
const props = defineProps<Props>();
const breadcrumbs: BreadcrumbItem[] = [
{
title: 'Dashboard',
href: dashboard().url,
},
];
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',
};
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 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,
};
return steps[status] ?? 50;
}
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,
);
</script>
<template>
<Head title="Dashboard" />
<AppLayout :breadcrumbs="breadcrumbs">
<div
class="flex h-full flex-1 flex-col gap-6 overflow-x-auto rounded-xl p-4"
>
<!-- Quick links when no workspace -->
<div
v-if="!hasWorkspace"
class="grid auto-rows-min gap-4 md:grid-cols-3"
>
<Link
href="/users"
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" />
<span class="font-medium">Users</span>
<span class="text-xs text-muted-foreground"
>Manage users</span
>
</Link>
<Link
href="/workspaces"
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" />
<span class="font-medium">Workspaces</span>
<span class="text-xs text-muted-foreground"
>Cabinets comptables</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>
<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>
</div>
<!-- My assigned declarations -->
<div class="space-y-4">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">
Mes déclarations {{ workspaceName }}
</h2>
<Button
v-if="declarationsUrl"
variant="outline"
as-child
>
<Link :href="declarationsUrl">
Toutes les déclarations
<ArrowRight class="ml-1 h-4 w-4" />
</Link>
</Button>
</div>
<Card
v-if="assignedDeclarations.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"
:key="declaration.id"
class="border-b border-sidebar-border/50 transition-colors last:border-0 hover:bg-muted/30"
>
<td class="px-4 py-3">
<Link
:href="declaration.showUrl"
class="block font-medium hover:underline"
>
{{ declaration.title }}
</Link>
<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'
"
>
{{
statusLabel(
declaration.status,
)
}}
</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"
>
Voir
<ArrowRight
class="ml-1 h-3 w-3"
/>
</Link>
</Button>
</td>
</tr>
</tbody>
</table>
</div>
</Card>
<Card v-else>
<CardContent
class="flex flex-col items-center justify-center py-12"
>
<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>
<Button v-if="declarationsUrl" as-child>
<Link :href="declarationsUrl"
>Voir toutes les déclarations</Link
>
</Button>
</CardContent>
</Card>
</div>
</template>
</div>
</AppLayout>
</template>