Initial commit of the L'Ami Fiduciaire SaaS platform built on Laravel 12, Vue 3, Inertia.js 2, and Tailwind CSS 4. Story 0.1 (rename folders to declarations in database) is implemented and code-reviewed: migration, rollback, and 6 Pest tests all passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
137 lines
5.0 KiB
Vue
137 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { Button } from '@/components/ui/button';
|
|
import { CheckCircle2, Download, FileText } from 'lucide-vue-next';
|
|
|
|
type Props = {
|
|
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;
|
|
is_downloaded?: boolean;
|
|
}>;
|
|
confirmation_status?: 'pending' | 'confirmed' | 'refused' | null;
|
|
};
|
|
messageTypeLabels: Record<string, string>;
|
|
};
|
|
|
|
const props = defineProps<Props>();
|
|
|
|
const typeColors: Record<string, string> = {
|
|
invite: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300',
|
|
situation: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300',
|
|
file_request: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300',
|
|
confirmation: 'bg-violet-100 text-violet-800 dark:bg-violet-900/30 dark:text-violet-300',
|
|
text: 'bg-slate-100 text-slate-700 dark:bg-slate-800/50 dark:text-slate-300',
|
|
};
|
|
|
|
const confirmationStatusLabels: Record<string, { label: string; class: string }> = {
|
|
pending: {
|
|
label: 'En attente',
|
|
class: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300',
|
|
},
|
|
confirmed: {
|
|
label: 'Validé',
|
|
class: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300',
|
|
},
|
|
refused: {
|
|
label: 'Refusé',
|
|
class: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300',
|
|
},
|
|
};
|
|
|
|
function isImageMime(mime?: string | null): boolean {
|
|
return mime?.startsWith('image/') ?? false;
|
|
}
|
|
|
|
function getTypeColor(type: string): string {
|
|
return typeColors[type] ?? 'bg-muted text-muted-foreground';
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
class="rounded-2xl px-4 py-3"
|
|
:class="
|
|
message.sent_by_type === 'user'
|
|
? 'ml-auto max-w-[85%] bg-muted'
|
|
: 'mr-auto max-w-[85%] bg-muted/70'
|
|
"
|
|
>
|
|
<div class="flex flex-wrap items-center justify-between gap-2 text-sm">
|
|
<span class="font-medium">{{ message.sender_name }}</span>
|
|
<span class="text-muted-foreground">{{ message.created_at }}</span>
|
|
</div>
|
|
<div class="mt-1 flex flex-wrap items-center gap-2">
|
|
<span
|
|
class="inline-flex rounded px-2 py-0.5 text-xs font-medium"
|
|
:class="getTypeColor(message.type)"
|
|
>
|
|
{{ messageTypeLabels[message.type] ?? message.type }}
|
|
</span>
|
|
<span
|
|
v-if="message.confirmation_status"
|
|
class="inline-flex rounded px-2 py-0.5 text-xs font-medium"
|
|
:class="confirmationStatusLabels[message.confirmation_status]?.class ?? ''"
|
|
>
|
|
{{ confirmationStatusLabels[message.confirmation_status]?.label ?? message.confirmation_status }}
|
|
</span>
|
|
</div>
|
|
<p class="mt-2 whitespace-pre-wrap text-sm">{{ message.body }}</p>
|
|
<div
|
|
v-if="message.attachments?.length"
|
|
class="mt-3 flex flex-wrap gap-2"
|
|
>
|
|
<div
|
|
v-for="att in message.attachments"
|
|
:key="att.id"
|
|
class="flex items-center gap-2 rounded-lg border border-sidebar-border/70 bg-background/50 p-2"
|
|
>
|
|
<div class="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded bg-muted">
|
|
<img
|
|
v-if="isImageMime(att.mime_type)"
|
|
:src="att.downloadUrl"
|
|
:alt="att.file_name"
|
|
class="size-10 object-cover"
|
|
/>
|
|
<FileText
|
|
v-else
|
|
class="size-5 text-muted-foreground"
|
|
/>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<p class="inline-flex items-center gap-1.5 truncate text-xs font-medium">
|
|
{{ att.file_name }}
|
|
<CheckCircle2 v-if="att.is_downloaded" class="size-3.5 text-green-500" />
|
|
</p>
|
|
<p class="text-xs text-muted-foreground">{{ att.size }}</p>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
as-child
|
|
class="shrink-0"
|
|
>
|
|
<a
|
|
:href="att.downloadUrl"
|
|
target="_blank"
|
|
rel="noopener"
|
|
download
|
|
@click="att.is_downloaded = true"
|
|
>
|
|
<Download class="size-4" />
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|