2026-03-11 23:33:10 +00:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { Form } from '@inertiajs/vue3';
|
|
|
|
|
|
import { watch } from 'vue';
|
|
|
|
|
|
import InputError from '@/components/InputError.vue';
|
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
|
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
|
|
|
|
export type DeclarationFormData = {
|
2026-03-11 23:33:10 +00:00
|
|
|
|
client_id: number | '';
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
period_year: number | string;
|
|
|
|
|
|
period_month: number | string;
|
|
|
|
|
|
period_quarter: number | string;
|
|
|
|
|
|
due_date: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
priority: string;
|
|
|
|
|
|
assigned_to: number | '';
|
|
|
|
|
|
notes_internal: string;
|
|
|
|
|
|
notes_client: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type Client = {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
company_name: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type WorkspaceUser = {
|
|
|
|
|
|
id: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
type Props = {
|
2026-03-12 18:25:32 +00:00
|
|
|
|
form: Form<DeclarationFormData>;
|
|
|
|
|
|
declarationTypeLabels: Record<string, string>;
|
|
|
|
|
|
declarationStatusLabels: Record<string, string>;
|
|
|
|
|
|
declarationPriorityLabels: Record<string, string>;
|
2026-03-11 23:33:10 +00:00
|
|
|
|
clients: Client[];
|
|
|
|
|
|
workspaceUsers: WorkspaceUser[];
|
|
|
|
|
|
submitLabel?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
|
|
submitLabel: 'Enregistrer',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
submit: [];
|
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
|
|
const years = Array.from({ length: 10 }, (_, i) => currentYear - 2 + i);
|
|
|
|
|
|
const months = [
|
|
|
|
|
|
{ value: '', label: '—' },
|
|
|
|
|
|
...Array.from({ length: 12 }, (_, i) => ({
|
|
|
|
|
|
value: (i + 1).toString(),
|
|
|
|
|
|
label: `${i + 1}`,
|
|
|
|
|
|
})),
|
|
|
|
|
|
];
|
|
|
|
|
|
const quarters = [
|
|
|
|
|
|
{ value: '', label: '—' },
|
|
|
|
|
|
{ value: '1', label: 'T1 (Jan–Mar)' },
|
|
|
|
|
|
{ value: '2', label: 'T2 (Avr–Juin)' },
|
|
|
|
|
|
{ value: '3', label: 'T3 (Juil–Sep)' },
|
|
|
|
|
|
{ value: '4', label: 'T4 (Oct–Déc)' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const isVatMonthly = () => props.form.type === 'vat_monthly';
|
|
|
|
|
|
const isVatQuarterly = () => props.form.type === 'vat_quarterly';
|
|
|
|
|
|
|
|
|
|
|
|
// Clear both period fields when type changes
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.form.type,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
props.form.period_month = '';
|
|
|
|
|
|
props.form.period_quarter = '';
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<form @submit.prevent="emit('submit')" class="flex flex-col space-y-6">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="client_id">Client</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="client_id"
|
|
|
|
|
|
v-model="form.client_id"
|
|
|
|
|
|
required
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.client_id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="" disabled>Sélectionner un client</option>
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="client in clients"
|
|
|
|
|
|
:key="client.id"
|
|
|
|
|
|
:value="client.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ client.company_name }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.client_id" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="title">Titre</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
id="title"
|
|
|
|
|
|
v-model="form.title"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
required
|
|
|
|
|
|
placeholder="Ex. Déclaration TVA - T1 2026"
|
|
|
|
|
|
aria-invalid="!!form.errors.title"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<InputError :message="form.errors.title" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="type">Type</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="type"
|
|
|
|
|
|
v-model="form.type"
|
|
|
|
|
|
required
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.type"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="" disabled>Sélectionner un type</option>
|
|
|
|
|
|
<option
|
2026-03-12 18:25:32 +00:00
|
|
|
|
v-for="(label, value) in declarationTypeLabels"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:key="value"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.type" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2 sm:grid-cols-3">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="period_year">Année</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="period_year"
|
|
|
|
|
|
v-model="form.period_year"
|
|
|
|
|
|
required
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.period_year"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option v-for="y in years" :key="y" :value="y">
|
|
|
|
|
|
{{ y }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.period_year" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<template v-if="isVatMonthly()">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="period_month">Mois</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="period_month"
|
|
|
|
|
|
v-model="form.period_month"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.period_month"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="m in months"
|
|
|
|
|
|
:key="m.value"
|
|
|
|
|
|
:value="m.value"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ m.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.period_month" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-if="isVatQuarterly()">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="period_quarter">Trimestre</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="period_quarter"
|
|
|
|
|
|
v-model="form.period_quarter"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.period_quarter"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="q in quarters"
|
|
|
|
|
|
:key="q.value"
|
|
|
|
|
|
:value="q.value"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ q.label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.period_quarter" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2 sm:grid-cols-2">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="due_date">Date limite</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
id="due_date"
|
|
|
|
|
|
v-model="form.due_date"
|
|
|
|
|
|
type="date"
|
|
|
|
|
|
aria-invalid="!!form.errors.due_date"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<InputError :message="form.errors.due_date" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="status">Statut</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="status"
|
|
|
|
|
|
v-model="form.status"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.status"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="" disabled>Sélectionner un statut</option>
|
|
|
|
|
|
<option
|
2026-03-12 18:25:32 +00:00
|
|
|
|
v-for="(label, value) in declarationStatusLabels"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:key="value"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.status" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2 sm:grid-cols-2">
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="priority">Priorité</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="priority"
|
|
|
|
|
|
v-model="form.priority"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.priority"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="">—</option>
|
|
|
|
|
|
<option
|
2026-03-12 18:25:32 +00:00
|
|
|
|
v-for="(label, value) in declarationPriorityLabels"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:key="value"
|
|
|
|
|
|
:value="value"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ label }}
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.priority" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="assigned_to">Assigné à</Label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
id="assigned_to"
|
|
|
|
|
|
v-model="form.assigned_to"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
:aria-invalid="!!form.errors.assigned_to"
|
|
|
|
|
|
>
|
|
|
|
|
|
<option :value="''">—</option>
|
|
|
|
|
|
<option
|
|
|
|
|
|
v-for="user in workspaceUsers"
|
|
|
|
|
|
:key="user.id"
|
|
|
|
|
|
:value="user.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ user.name }} ({{ user.email }})
|
|
|
|
|
|
</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
<InputError :message="form.errors.assigned_to" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="notes_internal">Notes internes</Label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
id="notes_internal"
|
|
|
|
|
|
v-model="form.notes_internal"
|
|
|
|
|
|
rows="3"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
placeholder="Notes confidentielles"
|
|
|
|
|
|
:aria-invalid="!!form.errors.notes_internal"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<InputError :message="form.errors.notes_internal" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="grid gap-2">
|
|
|
|
|
|
<Label for="notes_client">Notes client</Label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
id="notes_client"
|
|
|
|
|
|
v-model="form.notes_client"
|
|
|
|
|
|
rows="3"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-ring"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
placeholder="Notes partagées avec le client"
|
|
|
|
|
|
:aria-invalid="!!form.errors.notes_client"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<InputError :message="form.errors.notes_client" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="flex items-center gap-4">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
:disabled="form.processing"
|
2026-03-12 18:25:32 +00:00
|
|
|
|
data-test="declaration-form-submit"
|
2026-03-11 23:33:10 +00:00
|
|
|
|
>
|
|
|
|
|
|
<Spinner v-if="form.processing" />
|
|
|
|
|
|
{{ submitLabel }}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</form>
|
|
|
|
|
|
</template>
|