diff --git a/.opencode/package-lock.json b/.opencode/package-lock.json new file mode 100644 index 0000000..ea27a2d --- /dev/null +++ b/.opencode/package-lock.json @@ -0,0 +1,128 @@ +{ + "name": ".opencode", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@kilocode/plugin": "7.2.10", + "@opencode-ai/plugin": "1.1.31" + } + }, + "node_modules/@kilocode/plugin": { + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.10.tgz", + "integrity": "sha512-VJPhJC+E5WWu7XgEJzrVOxKJlwJ+OATwxEzgjqEPj8KN5N38YxUPBY/rzUTjv90x7nkzyk1rFGfCVqXdA/Koug==", + "license": "MIT", + "dependencies": { + "@kilocode/sdk": "7.2.10", + "zod": "4.1.8" + }, + "peerDependencies": { + "@opentui/core": ">=0.1.97", + "@opentui/solid": ">=0.1.97" + }, + "peerDependenciesMeta": { + "@opentui/core": { + "optional": true + }, + "@opentui/solid": { + "optional": true + } + } + }, + "node_modules/@kilocode/sdk": { + "version": "7.2.10", + "resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.10.tgz", + "integrity": "sha512-H6jGXYAhN/yjOGX3MRZ0OxyEAuRGY3VOwDbLTh4O6ljpgutFHaLvomDZ82qNVy7gl7AjJgi3SAQAt9UQpeGl/w==", + "license": "MIT", + "dependencies": { + "cross-spawn": "7.0.6" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.1.31", + "license": "MIT", + "dependencies": { + "@opencode-ai/sdk": "1.1.31", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.1.31", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/zod": { + "version": "4.1.8", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/thanasoft-front/src/components/Organism/Convoys/AddConvoyPresentation.vue b/thanasoft-front/src/components/Organism/Convoys/AddConvoyPresentation.vue new file mode 100644 index 0000000..371e6eb --- /dev/null +++ b/thanasoft-front/src/components/Organism/Convoys/AddConvoyPresentation.vue @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/thanasoft-front/src/components/Organism/Convoys/ConvoyListPresentation.vue b/thanasoft-front/src/components/Organism/Convoys/ConvoyListPresentation.vue new file mode 100644 index 0000000..17930cf --- /dev/null +++ b/thanasoft-front/src/components/Organism/Convoys/ConvoyListPresentation.vue @@ -0,0 +1,107 @@ + + + + + Ajouter convoi + + + + + + + + + + + + + + + + Filtrer + + + Planifiés + En cours + Terminés + + + + + + Chargement... + Chargement des convois... + + + + {{ error }} + Réessayer + + + + + + Aucun convoi trouvé + Créez votre premier convoi pour commencer. + + + + + + + + + + + + + diff --git a/thanasoft-front/src/components/molecules/Convoy/ConvoyEventCard.vue b/thanasoft-front/src/components/molecules/Convoy/ConvoyEventCard.vue new file mode 100644 index 0000000..b7cd12f --- /dev/null +++ b/thanasoft-front/src/components/molecules/Convoy/ConvoyEventCard.vue @@ -0,0 +1,105 @@ + + + + + + Convoi + {{ convoy.mission_title || defaultTitle }} + + {{ deceasedName }} + + + {{ statusLabel }} + + + + + + {{ formattedDate }} + + + + {{ transportLabel }} + + + + {{ departureLabel }} + + + + {{ vehicleLabel }} + + + + + + {{ convoyTypeLabel }} + • + {{ notificationLabel }} + + + Voir + + + + + + + + + diff --git a/thanasoft-front/src/components/molecules/form/NewConvoyForm.vue b/thanasoft-front/src/components/molecules/form/NewConvoyForm.vue new file mode 100644 index 0000000..bde3ecc --- /dev/null +++ b/thanasoft-front/src/components/molecules/form/NewConvoyForm.vue @@ -0,0 +1,113 @@ + + + Nouveau convoi + Informations générales de la mission + + + + + ID défunt * + + + + Titre de mission + + + + + + + Type de convoi + + Local + National + International + + + + Mode de transport + + Route + Aérien + Maritime + Ferroviaire + + + + + + + Début prévu * + + + + Fin estimée + + + + + + + Ville de départ + + + + Email famille + + + + + + + Réinitialiser + + + {{ loading ? "Création..." : "Créer le convoi" }} + + + + + + + diff --git a/thanasoft-front/src/components/templates/Convoys/ConvoyTemplate.vue b/thanasoft-front/src/components/templates/Convoys/ConvoyTemplate.vue new file mode 100644 index 0000000..489934c --- /dev/null +++ b/thanasoft-front/src/components/templates/Convoys/ConvoyTemplate.vue @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/thanasoft-front/src/components/templates/Convoys/NewConvoyTemplate.vue b/thanasoft-front/src/components/templates/Convoys/NewConvoyTemplate.vue new file mode 100644 index 0000000..36c575f --- /dev/null +++ b/thanasoft-front/src/components/templates/Convoys/NewConvoyTemplate.vue @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/thanasoft-front/src/examples/Sidenav/SidenavList.vue b/thanasoft-front/src/examples/Sidenav/SidenavList.vue index 69763aa..98eb922 100644 --- a/thanasoft-front/src/examples/Sidenav/SidenavList.vue +++ b/thanasoft-front/src/examples/Sidenav/SidenavList.vue @@ -381,6 +381,28 @@ export default { }, ], }, + { + id: "convois", + type: "collapse", + text: "Convois", + icon: "Office", + collapseRef: "convoisMenu", + routeKey: "convois", + children: [ + { + id: "convois-list", + route: { name: "Liste convois" }, + miniIcon: "L", + text: "Liste convois", + }, + { + id: "convois-add", + route: { name: "Ajouter convoi" }, + miniIcon: "A", + text: "Ajouter convoi", + }, + ], + }, { id: "parametrage", type: "collapse", diff --git a/thanasoft-front/src/router/index.js b/thanasoft-front/src/router/index.js index 3c85735..2ff449b 100644 --- a/thanasoft-front/src/router/index.js +++ b/thanasoft-front/src/router/index.js @@ -406,6 +406,16 @@ const routes = [ name: "Agenda", component: () => import("@/views/pages/Agenda.vue"), }, + { + path: "/convois", + name: "Liste convois", + component: () => import("@/views/pages/Convoys/Convoys.vue"), + }, + { + path: "/convois/new", + name: "Ajouter convoi", + component: () => import("@/views/pages/Convoys/AddConvoy.vue"), + }, // Planning { path: "/planning", diff --git a/thanasoft-front/src/services/convoy.ts b/thanasoft-front/src/services/convoy.ts new file mode 100644 index 0000000..d0e6d58 --- /dev/null +++ b/thanasoft-front/src/services/convoy.ts @@ -0,0 +1,148 @@ +import { request } from "./http"; + +export interface ConvoyDepartureLocation { + id: number; + client_id: number; + name: string; + address_line1: string | null; + address_line2: string | null; + postal_code: string | null; + city: string | null; + country_code: string | null; + gps_lat: string | null; + gps_lng: string | null; +} + +export interface ConvoyDeparture { + location_selection_mode: "place" | "manual"; + location_id: number | null; + location?: ConvoyDepartureLocation | null; + name: string | null; + address: string | null; + city: string | null; + postal_code: string | null; + country_code: string | null; + latitude: string | null; + longitude: string | null; + additional_details: string | null; +} + +export interface Convoy { + id: number; + deceased_id: number; + client_id: number | null; + vehicle_id: number | null; + mission_title: string | null; + convoy_type: "local" | "national" | "international"; + transport_mode: "road" | "air" | "sea" | "rail"; + status: "planned" | "in_progress" | "completed" | "cancelled"; + planned_start_at: string; + estimated_end_at: string | null; + family_email: string | null; + automatic_notifications: boolean; + departure: ConvoyDeparture; + tabs: Record; + deceased?: any; + client?: any; + vehicle?: any; + created_at: string; + updated_at: string; +} + +export interface ConvoyListResponse { + data: Convoy[]; + meta: { + current_page: number; + last_page: number; + per_page: number; + total: number; + }; + status: string; +} + +export interface ConvoyResponse { + data: Convoy; + message?: string; + status?: string; +} + +export interface CreateConvoyPayload { + deceased_id: number; + client_id?: number | null; + vehicle_id?: number | null; + mission_title?: string | null; + convoy_type?: "local" | "national" | "international"; + transport_mode?: "road" | "air" | "sea" | "rail"; + status?: "planned" | "in_progress" | "completed" | "cancelled"; + planned_start_at: string; + estimated_end_at?: string | null; + family_email?: string | null; + automatic_notifications?: boolean; + departure_location_selection_mode?: "place" | "manual"; + departure_location_id?: number | null; + departure_name?: string | null; + departure_address?: string | null; + departure_city?: string | null; + departure_postal_code?: string | null; + departure_country_code?: string | null; + departure_latitude?: number | null; + departure_longitude?: number | null; + departure_additional_details?: string | null; +} + +export interface UpdateConvoyPayload extends Partial { + id: number; +} + +export const ConvoyService = { + async getAllConvoys(params?: { + page?: number; + per_page?: number; + search?: string; + status?: string; + convoy_type?: string; + vehicle_id?: number; + deceased_id?: number; + sort_by?: string; + sort_direction?: string; + }): Promise { + return await request({ + url: "/api/convoys", + method: "get", + params, + }); + }, + + async getConvoy(id: number): Promise { + return await request({ + url: `/api/convoys/${id}`, + method: "get", + }); + }, + + async createConvoy(payload: CreateConvoyPayload): Promise { + return await request({ + url: "/api/convoys", + method: "post", + data: payload, + }); + }, + + async updateConvoy(payload: UpdateConvoyPayload): Promise { + const { id, ...updateData } = payload; + return await request({ + url: `/api/convoys/${id}`, + method: "put", + data: updateData, + }); + }, + + async deleteConvoy(id: number): Promise<{ message: string; status: string }> { + return await request<{ message: string; status: string }>({ + url: `/api/convoys/${id}`, + method: "delete", + }); + }, +}; + +export default ConvoyService; diff --git a/thanasoft-front/src/stores/convoyStore.ts b/thanasoft-front/src/stores/convoyStore.ts new file mode 100644 index 0000000..d0c2b66 --- /dev/null +++ b/thanasoft-front/src/stores/convoyStore.ts @@ -0,0 +1,145 @@ +import { defineStore } from "pinia"; +import { computed, ref } from "vue"; +import ConvoyService from "@/services/convoy"; +import type { + Convoy, + CreateConvoyPayload, + UpdateConvoyPayload, +} from "@/services/convoy"; + +export const useConvoyStore = defineStore("convoy", () => { + const convoys = ref([]); + const currentConvoy = ref(null); + const loading = ref(false); + const error = ref(null); + + const pagination = ref({ + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + }); + + const allConvoys = computed(() => convoys.value); + const isLoading = computed(() => loading.value); + const hasError = computed(() => error.value !== null); + const getError = computed(() => error.value); + const getPagination = computed(() => pagination.value); + + const setPagination = (meta: any) => { + if (!meta) return; + pagination.value = { + current_page: Number(meta.current_page) || 1, + last_page: Number(meta.last_page) || 1, + per_page: Number(meta.per_page) || 10, + total: Number(meta.total) || 0, + }; + }; + + const fetchConvoys = async (params?: { + page?: number; + per_page?: number; + search?: string; + status?: string; + convoy_type?: string; + vehicle_id?: number; + deceased_id?: number; + sort_by?: string; + sort_direction?: string; + }) => { + loading.value = true; + error.value = null; + try { + const response = await ConvoyService.getAllConvoys(params); + convoys.value = response.data; + setPagination(response.meta); + return response; + } catch (err: any) { + error.value = err.response?.data?.message || err.message || "Failed to fetch convoys"; + throw err; + } finally { + loading.value = false; + } + }; + + const fetchConvoy = async (id: number) => { + loading.value = true; + error.value = null; + try { + const response = await ConvoyService.getConvoy(id); + currentConvoy.value = response.data; + return response.data; + } catch (err: any) { + error.value = err.response?.data?.message || err.message || "Failed to fetch convoy"; + throw err; + } finally { + loading.value = false; + } + }; + + const createConvoy = async (payload: CreateConvoyPayload) => { + loading.value = true; + error.value = null; + try { + const response = await ConvoyService.createConvoy(payload); + convoys.value.unshift(response.data); + currentConvoy.value = response.data; + return response.data; + } catch (err: any) { + error.value = err.response?.data?.message || err.message || "Failed to create convoy"; + throw err; + } finally { + loading.value = false; + } + }; + + const updateConvoy = async (payload: UpdateConvoyPayload) => { + loading.value = true; + error.value = null; + try { + const response = await ConvoyService.updateConvoy(payload); + const index = convoys.value.findIndex((convoy) => convoy.id === response.data.id); + if (index !== -1) convoys.value[index] = response.data; + if (currentConvoy.value?.id === response.data.id) currentConvoy.value = response.data; + return response.data; + } catch (err: any) { + error.value = err.response?.data?.message || err.message || "Failed to update convoy"; + throw err; + } finally { + loading.value = false; + } + }; + + const deleteConvoy = async (id: number) => { + loading.value = true; + error.value = null; + try { + const response = await ConvoyService.deleteConvoy(id); + convoys.value = convoys.value.filter((convoy) => convoy.id !== id); + if (currentConvoy.value?.id === id) currentConvoy.value = null; + return response; + } catch (err: any) { + error.value = err.response?.data?.message || err.message || "Failed to delete convoy"; + throw err; + } finally { + loading.value = false; + } + }; + + return { + convoys, + currentConvoy, + loading, + error, + allConvoys, + isLoading, + hasError, + getError, + getPagination, + fetchConvoys, + fetchConvoy, + createConvoy, + updateConvoy, + deleteConvoy, + }; +}); diff --git a/thanasoft-front/src/views/pages/Convoys/AddConvoy.vue b/thanasoft-front/src/views/pages/Convoys/AddConvoy.vue new file mode 100644 index 0000000..55c77e0 --- /dev/null +++ b/thanasoft-front/src/views/pages/Convoys/AddConvoy.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/thanasoft-front/src/views/pages/Convoys/Convoys.vue b/thanasoft-front/src/views/pages/Convoys/Convoys.vue new file mode 100644 index 0000000..16a54f1 --- /dev/null +++ b/thanasoft-front/src/views/pages/Convoys/Convoys.vue @@ -0,0 +1,41 @@ + + + + +
Chargement des convois...
{{ error }}
Créez votre premier convoi pour commencer.
Convoi
+ {{ deceasedName }} +
Informations générales de la mission