2026-03-11 23:33:10 +00:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { Form, Head, Link, useForm } from '@inertiajs/vue3';
|
2026-03-12 18:25:32 +00:00
|
|
|
|
import { CheckCircle2, Download, Paperclip, Send } from 'lucide-vue-next';
|
|
|
|
|
|
import { computed, ref, watch, nextTick } from 'vue';
|
|
|
|
|
|
import MessageBubble from '@/components/declarations/MessageBubble.vue';
|
2026-03-11 23:33:10 +00:00
|
|
|
|
import Heading from '@/components/Heading.vue';
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
|
|
import { Label } from '@/components/ui/label';
|
|
|
|
|
|
import { Spinner } from '@/components/ui/spinner';
|
2026-03-12 18:25:32 +00:00
|
|
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
|
|
|
|
import { Timeline } from '@/components/ui/timeline';
|
|
|
|
|
|
import AppLayout from '@/layouts/AppLayout.vue';
|
2026-03-11 23:33:10 +00:00
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
|
type Declaration = {
|
2026-03-11 23:33:10 +00:00
|
|
|
|
id: number;
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
client_id: number;
|
|
|
|
|
|
client_name: string;
|
|
|
|
|
|
period_year: number | null;
|
|
|
|
|
|
period_month: number | null;
|
|
|
|
|
|
period_quarter: number | null;
|
|
|
|
|
|
due_date: string | null;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
priority: string | null;
|
|
|
|
|
|
assigned_to: number | null;
|
|
|
|
|
|
assignee_name: string | null;
|
|
|
|
|
|
validated_at: string | null;
|
|
|
|
|
|
closed_at: string | null;
|
|
|
|
|
|
notes_internal: string | null;
|
|
|
|
|
|
notes_client: string | null;
|
|
|
|
|
|
created_at: string | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type Message = {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
body: string;
|
|
|
|
|
|
sent_by_type: string;
|
|
|
|
|
|
sender_name: string;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
attachments?: Array<{
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
file_name: string;
|
|
|
|
|
|
mime_type: string;
|
|
|
|
|
|
size: string;
|
|
|
|
|
|
downloadUrl: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
confirmation_status?: 'pending' | 'confirmed' | 'refused' | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type Document = {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
file_name: string;
|
|
|
|
|
|
size: string;
|
|
|
|
|
|
created_at: string;
|
|
|
|
|
|
uploaded_by: string;
|
|
|
|
|
|
downloadUrl: string;
|
|
|
|
|
|
is_downloaded: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type WorkspaceUser = {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type Props = {
|
2026-03-12 18:25:32 +00:00
|
|
|
|
declaration: Declaration;
|
2026-03-11 23:33:10 +00:00
|
|
|
|
messages: Message[];
|
|
|
|
|
|
documents: Document[];
|
|
|
|
|
|
messagesStoreUrl: string;
|
|
|
|
|
|
mediaStoreUrl: string;
|
|
|
|
|
|
messageTypeLabels: Record<string, string>;
|
|
|
|
|
|
indexUrl: string;
|
|
|
|
|
|
editUrl: string;
|
|
|
|
|
|
workspaceUsers: WorkspaceUser[];
|
|
|
|
|
|
mentionStoreUrl: string;
|
|
|
|
|
|
canMention: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
|
|
|
|
|
|
|
|
const reactiveDocuments = ref(props.documents.map((d) => ({ ...d })));
|
2026-03-12 18:25:32 +00:00
|
|
|
|
watch(
|
|
|
|
|
|
() => props.documents,
|
|
|
|
|
|
(newDocs) => {
|
|
|
|
|
|
reactiveDocuments.value = newDocs.map((d) => ({ ...d }));
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
|
|
|
|
|
|
function onDocumentDownload(doc: Document & { is_downloaded: boolean }) {
|
|
|
|
|
|
doc.is_downloaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const mentionForm = useForm({
|
|
|
|
|
|
user_id: '',
|
|
|
|
|
|
message: '',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function submitMention() {
|
|
|
|
|
|
mentionForm.post(props.mentionStoreUrl, {
|
|
|
|
|
|
preserveScroll: true,
|
|
|
|
|
|
onSuccess: () => mentionForm.reset(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tabFromUrl = () => {
|
|
|
|
|
|
const params = new URLSearchParams(window.location.search);
|
2026-03-12 18:25:32 +00:00
|
|
|
|
return params.get('tab') === 'messages'
|
|
|
|
|
|
? 'messages'
|
|
|
|
|
|
: params.get('tab') === 'documents'
|
|
|
|
|
|
? 'documents'
|
|
|
|
|
|
: 'overview';
|
2026-03-11 23:33:10 +00:00
|
|
|
|
};
|
|
|
|
|
|
const tab = ref(tabFromUrl());
|
|
|
|
|
|
watch(tab, (t) => {
|
|
|
|
|
|
const url = new URL(window.location.href);
|
|
|
|
|
|
url.searchParams.set('tab', t);
|
|
|
|
|
|
window.history.replaceState({}, '', url.toString());
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
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 de traitement',
|
|
|
|
|
|
additional_documents_requested: 'Pièces complémentaires demandées',
|
|
|
|
|
|
waiting_client_validation: 'En attente validation client',
|
|
|
|
|
|
validated: 'Validé',
|
|
|
|
|
|
closed: 'Clôturé',
|
|
|
|
|
|
cancelled: 'Annulé',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const priorityLabels: Record<string, string> = {
|
|
|
|
|
|
low: 'Basse',
|
|
|
|
|
|
medium: 'Normale',
|
|
|
|
|
|
high: 'Haute',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
|
function formatPeriod(declaration: Declaration): string {
|
2026-03-11 23:33:10 +00:00
|
|
|
|
const parts: string[] = [];
|
2026-03-12 18:25:32 +00:00
|
|
|
|
if (declaration.period_year) parts.push(String(declaration.period_year));
|
|
|
|
|
|
if (declaration.period_quarter)
|
|
|
|
|
|
parts.push(`T${declaration.period_quarter}`);
|
|
|
|
|
|
if (declaration.period_month) parts.push(`M${declaration.period_month}`);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
return parts.join(' - ') || '—';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
|
function formatDateTime(
|
|
|
|
|
|
iso: string | null,
|
|
|
|
|
|
): { date: string; time: string } | null {
|
2026-03-11 23:33:10 +00:00
|
|
|
|
if (!iso) return null;
|
|
|
|
|
|
const d = new Date(iso);
|
|
|
|
|
|
return {
|
|
|
|
|
|
date: d.toLocaleDateString('fr-FR', {
|
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
|
month: 'long',
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
}),
|
|
|
|
|
|
time: d.toLocaleTimeString('fr-FR', {
|
|
|
|
|
|
hour: '2-digit',
|
|
|
|
|
|
minute: '2-digit',
|
|
|
|
|
|
}),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const messagesContainerRef = ref<HTMLElement | null>(null);
|
|
|
|
|
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
|
|
|
|
|
const selectedFiles = ref<File[]>([]);
|
|
|
|
|
|
const documentsFileInputRef = ref<HTMLInputElement | null>(null);
|
|
|
|
|
|
const selectedDocuments = ref<File[]>([]);
|
|
|
|
|
|
|
|
|
|
|
|
function scrollToBottom() {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
const el = messagesContainerRef.value;
|
|
|
|
|
|
if (el) el.scrollTop = el.scrollHeight;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.messages.length,
|
|
|
|
|
|
() => scrollToBottom(),
|
|
|
|
|
|
{ immediate: true },
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
watch(tab, (newTab) => {
|
|
|
|
|
|
if (newTab === 'messages') scrollToBottom();
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function triggerFileSelect() {
|
|
|
|
|
|
fileInputRef.value?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onFilesChanged(e: Event) {
|
|
|
|
|
|
const input = e.target as HTMLInputElement;
|
|
|
|
|
|
selectedFiles.value = input.files ? Array.from(input.files) : [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeFile(index: number) {
|
|
|
|
|
|
selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index);
|
|
|
|
|
|
if (fileInputRef.value) {
|
|
|
|
|
|
fileInputRef.value.value = '';
|
|
|
|
|
|
const dt = new DataTransfer();
|
|
|
|
|
|
selectedFiles.value.forEach((f) => dt.items.add(f));
|
|
|
|
|
|
fileInputRef.value.files = dt.files;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function triggerDocumentsFileSelect() {
|
|
|
|
|
|
documentsFileInputRef.value?.click();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onDocumentsFilesChanged(e: Event) {
|
|
|
|
|
|
const input = e.target as HTMLInputElement;
|
|
|
|
|
|
if (input.files?.length) {
|
2026-03-12 18:25:32 +00:00
|
|
|
|
selectedDocuments.value = [
|
|
|
|
|
|
...selectedDocuments.value,
|
|
|
|
|
|
...Array.from(input.files),
|
|
|
|
|
|
];
|
2026-03-11 23:33:10 +00:00
|
|
|
|
syncDocumentsToInput();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeDocument(index: number) {
|
2026-03-12 18:25:32 +00:00
|
|
|
|
selectedDocuments.value = selectedDocuments.value.filter(
|
|
|
|
|
|
(_, i) => i !== index,
|
|
|
|
|
|
);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
syncDocumentsToInput();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function syncDocumentsToInput() {
|
|
|
|
|
|
if (!documentsFileInputRef.value) return;
|
|
|
|
|
|
const dt = new DataTransfer();
|
|
|
|
|
|
selectedDocuments.value.forEach((f) => dt.items.add(f));
|
|
|
|
|
|
documentsFileInputRef.value.files = dt.files;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatFileSize(bytes: number): string {
|
|
|
|
|
|
if (bytes < 1024) return `${bytes} o`;
|
|
|
|
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} Ko`;
|
|
|
|
|
|
return `${(bytes / (1024 * 1024)).toFixed(1)} Mo`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function autoResizeTextarea(e: Event) {
|
|
|
|
|
|
const ta = e.target as HTMLTextAreaElement;
|
|
|
|
|
|
ta.style.height = 'auto';
|
|
|
|
|
|
ta.style.height = `${Math.min(ta.scrollHeight, 200)}px`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const messagesChronological = computed(() => [...props.messages].reverse());
|
|
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
|
const declarationTimelineItems = computed(() => {
|
|
|
|
|
|
const declaration = props.declaration;
|
2026-03-11 23:33:10 +00:00
|
|
|
|
const items: Array<{
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
date?: string;
|
|
|
|
|
|
time?: string;
|
|
|
|
|
|
state: 'completed' | 'pending' | 'current';
|
|
|
|
|
|
}> = [];
|
|
|
|
|
|
|
|
|
|
|
|
// Documents reçus
|
2026-03-12 18:25:32 +00:00
|
|
|
|
const docsReceived = [
|
|
|
|
|
|
'documents_received',
|
|
|
|
|
|
'processing',
|
|
|
|
|
|
'additional_documents_requested',
|
|
|
|
|
|
'waiting_client_validation',
|
|
|
|
|
|
'validated',
|
|
|
|
|
|
'closed',
|
|
|
|
|
|
].includes(declaration.status);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
items.push({
|
|
|
|
|
|
title: docsReceived ? 'Documents reçus' : 'En attente des documents',
|
2026-03-12 18:25:32 +00:00
|
|
|
|
state: docsReceived
|
|
|
|
|
|
? 'completed'
|
|
|
|
|
|
: declaration.status === 'waiting_documents'
|
|
|
|
|
|
? 'current'
|
|
|
|
|
|
: 'pending',
|
2026-03-11 23:33:10 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Validation client
|
2026-03-12 18:25:32 +00:00
|
|
|
|
const validatedFmt = formatDateTime(declaration.validated_at);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
items.push({
|
2026-03-12 18:25:32 +00:00
|
|
|
|
title: declaration.validated_at
|
|
|
|
|
|
? 'Validé par le client'
|
|
|
|
|
|
: 'Validation client',
|
2026-03-11 23:33:10 +00:00
|
|
|
|
date: validatedFmt?.date,
|
|
|
|
|
|
time: validatedFmt?.time,
|
2026-03-12 18:25:32 +00:00
|
|
|
|
state: declaration.validated_at
|
|
|
|
|
|
? 'completed'
|
|
|
|
|
|
: declaration.status === 'waiting_client_validation'
|
|
|
|
|
|
? 'current'
|
|
|
|
|
|
: 'pending',
|
2026-03-11 23:33:10 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Clôture
|
2026-03-12 18:25:32 +00:00
|
|
|
|
const closedFmt = formatDateTime(declaration.closed_at);
|
2026-03-11 23:33:10 +00:00
|
|
|
|
items.push({
|
2026-03-12 18:25:32 +00:00
|
|
|
|
title: declaration.closed_at
|
|
|
|
|
|
? 'Déclaration clôturée'
|
|
|
|
|
|
: 'Clôture de la déclaration',
|
2026-03-11 23:33:10 +00:00
|
|
|
|
date: closedFmt?.date,
|
|
|
|
|
|
time: closedFmt?.time,
|
2026-03-12 18:25:32 +00:00
|
|
|
|
state: declaration.closed_at
|
|
|
|
|
|
? 'completed'
|
|
|
|
|
|
: declaration.status === 'closed'
|
|
|
|
|
|
? 'current'
|
|
|
|
|
|
: 'pending',
|
2026-03-11 23:33:10 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return items;
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<AppLayout
|
|
|
|
|
|
:breadcrumbs="[
|
|
|
|
|
|
{ title: 'Déclarations', href: props.indexUrl },
|
|
|
|
|
|
{ title: props.declaration.title },
|
|
|
|
|
|
]"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Head :title="props.declaration.title" />
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex h-full flex-col">
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<div
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="flex items-center justify-between border-b border-sidebar-border/70 p-4 dark:border-sidebar-border"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Heading
|
|
|
|
|
|
variant="small"
|
|
|
|
|
|
:title="props.declaration.title"
|
|
|
|
|
|
:description="
|
|
|
|
|
|
typeLabels[declaration.type] ?? declaration.type
|
|
|
|
|
|
"
|
|
|
|
|
|
/>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<Button variant="outline" as-child>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Link :href="editUrl">Modifier la déclaration</Link>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Tabs
|
|
|
|
|
|
v-model="tab"
|
|
|
|
|
|
class="h-full w-full flex-grow gap-0 overflow-auto"
|
|
|
|
|
|
>
|
|
|
|
|
|
<TabsList
|
|
|
|
|
|
class="h-auto w-full rounded-none !bg-background px-0 py-0"
|
|
|
|
|
|
>
|
|
|
|
|
|
<TabsTrigger
|
|
|
|
|
|
value="overview"
|
|
|
|
|
|
class="rounded-none border-0 border-b-2 border-sidebar-border/70 py-2 !shadow-none transition-all data-[state=active]:border-primary dark:border-sidebar-border"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Aperçu
|
|
|
|
|
|
</TabsTrigger>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<TabsTrigger
|
|
|
|
|
|
value="messages"
|
|
|
|
|
|
class="rounded-none border-0 border-b-2 border-sidebar-border/70 py-2 !shadow-none transition-all data-[state=active]:border-primary dark:border-sidebar-border"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Messages
|
|
|
|
|
|
</TabsTrigger>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<TabsTrigger
|
|
|
|
|
|
value="documents"
|
|
|
|
|
|
class="rounded-none border-0 border-b-2 border-sidebar-border/70 py-2 !shadow-none transition-all data-[state=active]:border-primary dark:border-sidebar-border"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Documents
|
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
|
</TabsList>
|
|
|
|
|
|
<TabsContent value="overview" class="p-4">
|
|
|
|
|
|
<div class="grid grid-cols-12 gap-4">
|
|
|
|
|
|
<div class="col-span-8">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<dl class="divide-y divide-sidebar-border/70">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Client
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{ declaration.client_name || '—' }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Type
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
typeLabels[declaration.type] ??
|
|
|
|
|
|
declaration.type
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Période
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{ formatPeriod(declaration) }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Date ouverture
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
declaration.created_at
|
|
|
|
|
|
? new Date(
|
|
|
|
|
|
declaration.created_at,
|
|
|
|
|
|
).toLocaleDateString(
|
|
|
|
|
|
'fr-FR',
|
|
|
|
|
|
{
|
|
|
|
|
|
day: 'numeric',
|
|
|
|
|
|
month: 'long',
|
|
|
|
|
|
year: 'numeric',
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
: '—'
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Date limite
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{ declaration.due_date || '—' }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Statut
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
statusLabels[
|
|
|
|
|
|
declaration.status
|
|
|
|
|
|
] ?? declaration.status
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Priorité
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
declaration.priority
|
|
|
|
|
|
? (priorityLabels[
|
|
|
|
|
|
declaration.priority
|
|
|
|
|
|
] ?? declaration.priority)
|
|
|
|
|
|
: '—'
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Assigné à
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
declaration.assignee_name || '—'
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Validé le
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{
|
|
|
|
|
|
declaration.validated_at || '—'
|
|
|
|
|
|
}}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Clôturé le
|
|
|
|
|
|
</dt>
|
|
|
|
|
|
<dd class="text-sm sm:col-span-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{ declaration.closed_at || '—' }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="declaration.notes_internal"
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Notes internes
|
|
|
|
|
|
</dt>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<dd
|
|
|
|
|
|
class="text-sm whitespace-pre-wrap sm:col-span-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ declaration.notes_internal }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="declaration.notes_client"
|
|
|
|
|
|
class="flex flex-col gap-1 px-4 py-3 sm:grid sm:grid-cols-3 sm:gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<dt
|
|
|
|
|
|
class="text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Notes client
|
|
|
|
|
|
</dt>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<dd
|
|
|
|
|
|
class="text-sm whitespace-pre-wrap sm:col-span-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ declaration.notes_client }}
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</dd>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</dl>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="canMention"
|
|
|
|
|
|
class="mt-4 rounded-xl border border-sidebar-border/70 p-4 dark:border-sidebar-border"
|
|
|
|
|
|
>
|
|
|
|
|
|
<h3 class="mb-3 text-sm font-medium">
|
|
|
|
|
|
Notifier un collaborateur
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<form
|
|
|
|
|
|
@submit.prevent="submitMention"
|
|
|
|
|
|
class="space-y-3"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Label
|
|
|
|
|
|
for="mention-user"
|
|
|
|
|
|
class="text-sm"
|
|
|
|
|
|
>Collaborateur</Label
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<select
|
|
|
|
|
|
id="mention-user"
|
|
|
|
|
|
v-model="mentionForm.user_id"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus:ring-2 focus:ring-ring focus:outline-none"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
required
|
|
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<option value="" disabled>
|
|
|
|
|
|
Sélectionner...
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="u in workspaceUsers"
|
|
|
|
|
|
:key="u.id"
|
|
|
|
|
|
:value="u.id"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
{{ u.name }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<p
|
|
|
|
|
|
v-if="mentionForm.errors.user_id"
|
|
|
|
|
|
class="mt-1 text-xs text-destructive"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ mentionForm.errors.user_id }}
|
|
|
|
|
|
</p>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
<div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Label
|
|
|
|
|
|
for="mention-message"
|
|
|
|
|
|
class="text-sm"
|
|
|
|
|
|
>Message</Label
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<textarea
|
|
|
|
|
|
id="mention-message"
|
|
|
|
|
|
v-model="mentionForm.message"
|
|
|
|
|
|
rows="2"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="mt-1 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus:outline-none"
|
|
|
|
|
|
placeholder="Ex : Merci de traiter cette déclaration en priorité"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
required
|
|
|
|
|
|
maxlength="500"
|
|
|
|
|
|
/>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<p
|
|
|
|
|
|
v-if="mentionForm.errors.message"
|
|
|
|
|
|
class="mt-1 text-xs text-destructive"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ mentionForm.errors.message }}
|
|
|
|
|
|
</p>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
:disabled="mentionForm.processing"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Envoyer la notification
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="col-span-4">
|
|
|
|
|
|
<Timeline
|
2026-03-12 18:25:32 +00:00
|
|
|
|
:items="declarationTimelineItems"
|
|
|
|
|
|
class="rounded-xl border border-sidebar-border/70 p-4 dark:border-sidebar-border"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TabsContent>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<TabsContent
|
|
|
|
|
|
value="messages"
|
|
|
|
|
|
class="relative flex h-full max-h-full min-h-0 flex-col p-0"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<div
|
|
|
|
|
|
ref="messagesContainerRef"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="absolute top-0 right-0 bottom-0 left-0 min-h-0 flex-1 overflow-hidden overflow-y-auto overscroll-contain px-4 py-6 pb-24"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="messages.length"
|
|
|
|
|
|
class="mx-auto max-w-3xl space-y-4"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<MessageBubble
|
|
|
|
|
|
v-for="msg in messagesChronological"
|
|
|
|
|
|
:key="msg.id"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
:message="{
|
|
|
|
|
|
...msg,
|
|
|
|
|
|
attachments: msg.attachments ?? [],
|
|
|
|
|
|
}"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:message-type-labels="messageTypeLabels"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-else
|
|
|
|
|
|
class="flex min-h-[200px] items-center justify-center text-center text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<p>
|
|
|
|
|
|
Aucun message. Envoyez une invitation ou un
|
|
|
|
|
|
message pour commencer.
|
|
|
|
|
|
</p>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
class="absolute bottom-0 w-full shrink-0 bg-gradient-to-t from-background to-transparent px-4 pb-4"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<Form
|
|
|
|
|
|
:action="messagesStoreUrl"
|
|
|
|
|
|
method="post"
|
|
|
|
|
|
enctype="multipart/form-data"
|
|
|
|
|
|
:force-form-data="true"
|
|
|
|
|
|
class="mx-auto max-w-3xl"
|
|
|
|
|
|
v-slot="{ processing }"
|
|
|
|
|
|
@submit="selectedFiles = []"
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref="fileInputRef"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
name="files[]"
|
|
|
|
|
|
multiple
|
|
|
|
|
|
accept="*/*"
|
|
|
|
|
|
class="hidden"
|
|
|
|
|
|
@change="onFilesChanged"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div class="flex flex-col gap-2">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="selectedFiles.length"
|
|
|
|
|
|
class="flex flex-wrap gap-1.5"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="(f, i) in selectedFiles"
|
|
|
|
|
|
:key="i"
|
|
|
|
|
|
class="inline-flex items-center gap-1 rounded-md bg-muted px-2 py-1 text-xs"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ f.name }}
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="rounded p-0.5 hover:bg-muted-foreground/20"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
@click.prevent="removeFile(i)"
|
|
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="flex items-end gap-2 rounded-xl border border-input bg-background px-3 py-2 focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<select
|
|
|
|
|
|
name="type"
|
|
|
|
|
|
required
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="mr-2 shrink-0 border-0 bg-transparent py-2 pr-6 text-sm text-muted-foreground focus:ring-0 focus:outline-none"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<option value="invite">
|
|
|
|
|
|
Invitation
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value="situation">
|
|
|
|
|
|
Situation
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value="file_request">
|
|
|
|
|
|
Demande de pièces
|
|
|
|
|
|
</option>
|
|
|
|
|
|
<option value="confirmation">
|
|
|
|
|
|
Validation
|
|
|
|
|
|
</option>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<option value="text">Message</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
name="body"
|
|
|
|
|
|
required
|
|
|
|
|
|
rows="1"
|
|
|
|
|
|
placeholder="Écrire un message..."
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="max-h-[200px] min-h-[24px] flex-1 resize-none overflow-hidden border-0 bg-transparent py-2 text-sm placeholder:text-muted-foreground focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:disabled="processing"
|
|
|
|
|
|
@input="autoResizeTextarea"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
class="shrink-0 text-muted-foreground"
|
|
|
|
|
|
@click="triggerFileSelect"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Paperclip class="size-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
size="icon"
|
|
|
|
|
|
class="shrink-0"
|
|
|
|
|
|
:disabled="processing"
|
|
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Spinner
|
|
|
|
|
|
v-if="processing"
|
|
|
|
|
|
class="size-4"
|
|
|
|
|
|
/>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<Send v-else class="size-4" />
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
<TabsContent value="documents" class="p-4">
|
|
|
|
|
|
<div class="space-y-4">
|
|
|
|
|
|
<Form
|
|
|
|
|
|
:action="mediaStoreUrl"
|
|
|
|
|
|
method="post"
|
|
|
|
|
|
enctype="multipart/form-data"
|
|
|
|
|
|
:force-form-data="true"
|
|
|
|
|
|
class="space-y-4 rounded-xl border border-sidebar-border/70 p-4"
|
|
|
|
|
|
v-slot="{ processing }"
|
|
|
|
|
|
@submit="selectedDocuments = []"
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
ref="documentsFileInputRef"
|
|
|
|
|
|
type="file"
|
|
|
|
|
|
name="files[]"
|
|
|
|
|
|
multiple
|
|
|
|
|
|
accept="*/*"
|
|
|
|
|
|
class="hidden"
|
|
|
|
|
|
@change="onDocumentsFilesChanged"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div class="flex flex-wrap items-end gap-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div class="min-w-[200px] flex-1 space-y-2">
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<Label>Ajouter des fichiers</Label>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
variant="outline"
|
|
|
|
|
|
class="w-full justify-start"
|
|
|
|
|
|
:disabled="processing"
|
|
|
|
|
|
@click="triggerDocumentsFileSelect"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Paperclip class="mr-2 size-4" />
|
|
|
|
|
|
Choisir des fichiers
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
:disabled="
|
|
|
|
|
|
processing ||
|
|
|
|
|
|
selectedDocuments.length === 0
|
|
|
|
|
|
"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<Spinner
|
|
|
|
|
|
v-if="processing"
|
|
|
|
|
|
class="mr-2 size-4"
|
|
|
|
|
|
/>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Télécharger
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="selectedDocuments.length"
|
|
|
|
|
|
class="rounded-lg border border-sidebar-border/70 bg-muted/30 p-2"
|
|
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<p
|
|
|
|
|
|
class="mb-2 text-sm font-medium text-muted-foreground"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ selectedDocuments.length }} fichier(s) à
|
|
|
|
|
|
déposer
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
<ul class="space-y-1.5">
|
|
|
|
|
|
<li
|
|
|
|
|
|
v-for="(file, i) in selectedDocuments"
|
|
|
|
|
|
:key="`${file.name}-${i}`"
|
|
|
|
|
|
class="flex items-center justify-between gap-2 rounded-md bg-background px-2 py-1.5 text-sm"
|
|
|
|
|
|
>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<span class="truncate">{{
|
|
|
|
|
|
file.name
|
|
|
|
|
|
}}</span>
|
|
|
|
|
|
<span
|
|
|
|
|
|
class="flex shrink-0 items-center gap-2"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<span class="text-muted-foreground">
|
|
|
|
|
|
{{ formatFileSize(file.size) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
class="rounded p-0.5 hover:bg-muted-foreground/20"
|
|
|
|
|
|
aria-label="Retirer"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
@click.prevent="
|
|
|
|
|
|
removeDocument(i)
|
|
|
|
|
|
"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</li>
|
|
|
|
|
|
</ul>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="reactiveDocuments.length"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="overflow-hidden rounded-xl border border-sidebar-border/70"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
<table class="w-full text-sm">
|
|
|
|
|
|
<thead class="bg-muted/50">
|
|
|
|
|
|
<tr>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<th
|
|
|
|
|
|
class="px-4 py-2 text-left font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
Nom
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th
|
|
|
|
|
|
class="px-4 py-2 text-left font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
Taille
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th
|
|
|
|
|
|
class="px-4 py-2 text-left font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
Déposé par
|
|
|
|
|
|
</th>
|
|
|
|
|
|
<th
|
|
|
|
|
|
class="px-4 py-2 text-left font-medium"
|
|
|
|
|
|
>
|
|
|
|
|
|
Date
|
|
|
|
|
|
</th>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<th class="px-4 py-2"></th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<tbody
|
|
|
|
|
|
class="divide-y divide-sidebar-border/70"
|
|
|
|
|
|
>
|
|
|
|
|
|
<tr
|
|
|
|
|
|
v-for="doc in reactiveDocuments"
|
|
|
|
|
|
:key="doc.id"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<td class="px-4 py-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<span
|
|
|
|
|
|
class="inline-flex items-center gap-1.5"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
{{ doc.file_name }}
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<CheckCircle2
|
|
|
|
|
|
v-if="doc.is_downloaded"
|
|
|
|
|
|
class="size-3.5 text-green-500"
|
|
|
|
|
|
/>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
</span>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-4 py-2">
|
2026-03-12 18:25:32 +00:00
|
|
|
|
{{ doc.size }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-4 py-2">
|
|
|
|
|
|
{{ doc.uploaded_by }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-4 py-2">
|
|
|
|
|
|
{{ doc.created_at }}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td class="px-4 py-2">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
variant="ghost"
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
as-child
|
|
|
|
|
|
>
|
|
|
|
|
|
<a
|
|
|
|
|
|
:href="doc.downloadUrl"
|
|
|
|
|
|
download
|
|
|
|
|
|
@click="
|
|
|
|
|
|
onDocumentDownload(doc)
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
<Download class="size-4" />
|
|
|
|
|
|
</a>
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
2026-03-12 18:25:32 +00:00
|
|
|
|
<div
|
|
|
|
|
|
v-if="!reactiveDocuments.length"
|
|
|
|
|
|
class="rounded-xl border border-sidebar-border/70 p-8 text-center text-muted-foreground"
|
|
|
|
|
|
>
|
2026-03-11 23:33:10 +00:00
|
|
|
|
Aucun document. Ajoutez des fichiers ci-dessus.
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</TabsContent>
|
|
|
|
|
|
</Tabs>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</AppLayout>
|
|
|
|
|
|
</template>
|