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

394 lines
19 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed } from 'vue';
import { Head, Link } from '@inertiajs/vue3';
import {
Briefcase,
Building2,
Users,
FolderOpen,
AlertTriangle,
Clock,
FileCheck,
MessageSquareWarning,
ArrowRight,
Folder,
} from 'lucide-vue-next';
import AppLayout from '@/layouts/AppLayout.vue';
import type { BreadcrumbItem } from '@/types';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { dashboard } from '@/routes';
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
type AssignedFolder = {
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 = {
assignedFolders: AssignedFolder[];
notifications: {
overdue: NotificationItem[];
due_soon: NotificationItem[];
documents_received: NotificationItem[];
awaiting_validation: NotificationItem[];
};
workspaceName: string | null;
foldersUrl: 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 href="/folders"
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">
<Folder class="h-8 w-8" />
<span class="font-medium">Dossiers</span>
<span class="text-xs text-muted-foreground">Manage folders</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 dossiers -->
<div class="space-y-4">
<div class="flex items-center justify-between">
<h2 class="text-lg font-semibold">
Mes dossiers {{ workspaceName }}
</h2>
<Button v-if="foldersUrl" variant="outline" as-child>
<Link :href="foldersUrl">
Tous les dossiers
<ArrowRight class="ml-1 h-4 w-4" />
</Link>
</Button>
</div>
<Card v-if="assignedFolders.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">
Dossier / 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="folder in assignedFolders" :key="folder.id"
class="border-b border-sidebar-border/50 last:border-0 transition-colors hover:bg-muted/30">
<td class="px-4 py-3">
<Link :href="folder.showUrl" class="block font-medium hover:underline">
{{ folder.title }}
</Link>
<span class="block text-xs text-muted-foreground">
{{ folder.client_name }}
</span>
</td>
<td class="px-4 py-3 text-muted-foreground">
{{ typeLabel(folder.type) }}
</td>
<td class="px-4 py-3">
<Badge :variant="statusVariant[folder.status] ?? 'secondary'
">
{{
statusLabel(folder.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(folder.status)}%`,
}" />
</div>
<span class="text-xs text-muted-foreground">
{{ progressPercent(folder.status) }}%
</span>
</td>
<td class="px-4 py-3">
<span :class="{
'text-destructive font-medium':
folder.due_date &&
folder.due_date <
new Date()
.toISOString()
.slice(0, 10),
}">
{{ folder.due_date || '—' }}
</span>
</td>
<td class="px-4 py-3">
<Button variant="ghost" size="sm" as-child>
<Link :href="folder.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">
Aucun dossier ne vous est assigné pour le moment.
</p>
<Button v-if="foldersUrl" as-child>
<Link :href="foldersUrl">Voir tous les dossiers</Link>
</Button>
</CardContent>
</Card>
</div>
</template>
</div>
</AppLayout>
</template>