From 69e8d74f4d1e417ee8e7be9dbbf39be40acde073 Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 17 Feb 2026 08:01:25 +0000 Subject: [PATCH] [#328] Corrections (#3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit | Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [x] Pas de régression - [ ] TU/TI/TF rédigée - [x] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: https://gitea.malio.fr/MALIO-DEV/SIRH/pulls/3 Co-authored-by: tristan Co-committed-by: tristan --- frontend/components/CalendarGrid.vue | 6 +-- frontend/layouts/default.vue | 14 +++---- frontend/pages/calendar.vue | 34 +++++++++++---- frontend/pages/sites.vue | 63 ++++++++++++++++++++++++++-- frontend/services/dto/site.ts | 1 + frontend/services/sites.ts | 23 ++++++++-- migrations/Version20260216143000.php | 26 ++++++++++++ migrations/Version20260216143100.php | 37 ++++++++++++++++ src/Entity/Site.php | 16 +++++++ src/State/AbsencePrintProvider.php | 3 +- templates/absence/print.html.twig | 15 +++---- 11 files changed, 203 insertions(+), 35 deletions(-) create mode 100644 migrations/Version20260216143000.php create mode 100644 migrations/Version20260216143100.php diff --git a/frontend/components/CalendarGrid.vue b/frontend/components/CalendarGrid.vue index 3a82993..da090a7 100644 --- a/frontend/components/CalendarGrid.vue +++ b/frontend/components/CalendarGrid.vue @@ -30,12 +30,12 @@
diff --git a/frontend/pages/calendar.vue b/frontend/pages/calendar.vue index 32aed26..e91b7b3 100644 --- a/frontend/pages/calendar.vue +++ b/frontend/pages/calendar.vue @@ -53,14 +53,24 @@
-
- +
+
+ +
+
+

Légende :

+
+
+

{{ type.label }}

+
+
+
@@ -123,7 +133,12 @@ const sites = computed(() => { siteMap.set(employee.site.id, employee.site) } } - return Array.from(siteMap.values()).sort((siteA, siteB) => siteA.name.localeCompare(siteB.name, 'fr')) + return Array.from(siteMap.values()).sort((siteA, siteB) => { + const orderA = siteA.displayOrder ?? 0 + const orderB = siteB.displayOrder ?? 0 + if (orderA !== orderB) return orderA - orderB + return siteA.name.localeCompare(siteB.name, 'fr') + }) }) // Filtres de sites (par défaut: tous sélectionnés à l'init). @@ -139,6 +154,9 @@ watch(sites, (next) => { // Tri stable: site -> nom -> prénom. const sortedEmployees = computed(() => { return [...employees.value].sort((employeeA, employeeB) => { + const siteOrderA = employeeA.site?.displayOrder ?? 0 + const siteOrderB = employeeB.site?.displayOrder ?? 0 + if (siteOrderA !== siteOrderB) return siteOrderA - siteOrderB const siteNameA = employeeA.site?.name ?? '' const siteNameB = employeeB.site?.name ?? '' if (siteNameA !== siteNameB) return siteNameA.localeCompare(siteNameB, 'fr') diff --git a/frontend/pages/sites.vue b/frontend/pages/sites.vue index f1d9510..7614029 100644 --- a/frontend/pages/sites.vue +++ b/frontend/pages/sites.vue @@ -32,8 +32,15 @@ v-for="site in sites" :key="site.id" class="grid grid-cols-[1fr_140px_160px] items-center gap-4 border-b border-neutral-100 px-6 py-3 text-md text-neutral-800 last:border-b-0" + draggable="true" + @dragstart="handleDragStart($event, site)" + @dragover="handleDragOver" + @drop="handleDrop($event, site)" > - {{ site.name }} + + :: + {{ site.name }} +
import type { Site } from '~/services/dto/site' -import { createSite, deleteSite, listSites, updateSite } from '~/services/sites' +import { createSite, deleteSite, listSites, updateSite, updateSiteOrder } from '~/services/sites' const isDrawerOpen = ref(false) const isSubmitting = ref(false) const isLoading = ref(false) +const isReordering = ref(false) const sites = ref([]) const editingSite = ref(null) @@ -207,7 +215,8 @@ const handleSubmit = async () => { } else { await createSite({ name: form.name, - color: form.color + color: form.color, + displayOrder: sites.value.length + 1 }) } @@ -231,4 +240,52 @@ const confirmDelete = async (site: Site) => { await deleteSite(site.id) await loadSites() } + +const handleDragStart = (event: DragEvent, site: Site) => { + if (isReordering.value || !event.dataTransfer) return + event.dataTransfer.effectAllowed = 'move' + event.dataTransfer.setData('text/plain', String(site.id)) +} + +const handleDragOver = (event: DragEvent) => { + event.preventDefault() +} + +const handleDrop = async (event: DragEvent, site: Site) => { + event.preventDefault() + if (isReordering.value) return + + const dragId = Number(event.dataTransfer?.getData('text/plain')) + if (!dragId || dragId === site.id) return + + const fromIndex = sites.value.findIndex((item) => item.id === dragId) + const toIndex = sites.value.findIndex((item) => item.id === site.id) + if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return + + const reordered = [...sites.value] + const [moved] = reordered.splice(fromIndex, 1) + reordered.splice(toIndex, 0, moved) + + const updates: Array<{ id: number; displayOrder: number }> = [] + reordered.forEach((item, index) => { + const nextOrder = index + 1 + if ((item.displayOrder ?? 0) !== nextOrder) { + updates.push({ id: item.id, displayOrder: nextOrder }) + } + item.displayOrder = nextOrder + }) + + sites.value = reordered + if (updates.length === 0) return + + isReordering.value = true + try { + await Promise.all(updates.map((update) => updateSiteOrder(update.id, update.displayOrder))) + } catch { + window.alert("Impossible de reordonner les sites.") + await loadSites() + } finally { + isReordering.value = false + } +} diff --git a/frontend/services/dto/site.ts b/frontend/services/dto/site.ts index 037ac1a..02eb6d6 100644 --- a/frontend/services/dto/site.ts +++ b/frontend/services/dto/site.ts @@ -2,4 +2,5 @@ export type Site = { id: number name: string color: string + displayOrder?: number } diff --git a/frontend/services/sites.ts b/frontend/services/sites.ts index 50e87dd..2eea19a 100644 --- a/frontend/services/sites.ts +++ b/frontend/services/sites.ts @@ -8,10 +8,15 @@ export const listSites = async () => { {}, { toast: false } ) - return extractItems(data) + return extractItems(data).sort((siteA, siteB) => { + const orderA = siteA.displayOrder ?? 0 + const orderB = siteB.displayOrder ?? 0 + if (orderA !== orderB) return orderA - orderB + return siteA.name.localeCompare(siteB.name, 'fr') + }) } -export const createSite = async (payload: Pick) => { +export const createSite = async (payload: Pick & { displayOrder?: number }) => { const api = useApi() return api.post('/sites', payload, { toastSuccessKey: 'success.site.create', @@ -19,7 +24,10 @@ export const createSite = async (payload: Pick) => { }) } -export const updateSite = async (id: number, payload: Pick) => { +export const updateSite = async ( + id: number, + payload: Pick & { displayOrder?: number } +) => { const api = useApi() return api.patch(`/sites/${id}`, payload, { toastSuccessKey: 'success.site.update', @@ -27,6 +35,15 @@ export const updateSite = async (id: number, payload: Pick { + const api = useApi() + return api.patch(`/sites/${id}`, { + displayOrder + }, { + toast: false + }) +} + export const deleteSite = async (id: number) => { const api = useApi() return api.delete(`/sites/${id}`, {}, { diff --git a/migrations/Version20260216143000.php b/migrations/Version20260216143000.php new file mode 100644 index 0000000..8cc4263 --- /dev/null +++ b/migrations/Version20260216143000.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE sites ADD display_order INT NOT NULL DEFAULT 0'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE sites DROP COLUMN display_order'); + } +} diff --git a/migrations/Version20260216143100.php b/migrations/Version20260216143100.php new file mode 100644 index 0000000..499fb09 --- /dev/null +++ b/migrations/Version20260216143100.php @@ -0,0 +1,37 @@ +addSql(' + UPDATE sites s + SET display_order = ranked.rn + FROM ( + SELECT id, + ROW_NUMBER() OVER ( + ORDER BY name ASC, id ASC + ) AS rn + FROM sites + ) ranked + WHERE ranked.id = s.id + '); + } + + public function down(Schema $schema): void + { + // Pas de rollback pertinent. + } +} diff --git a/src/Entity/Site.php b/src/Entity/Site.php index 6dff18a..554a9c9 100644 --- a/src/Entity/Site.php +++ b/src/Entity/Site.php @@ -31,6 +31,10 @@ class Site #[Groups(['site:read', 'employee:read'])] private string $color = ''; + #[ORM\Column(type: 'integer', options: ['default' => 0])] + #[Groups(['site:read'])] + private int $displayOrder = 0; + public function getId(): ?int { return $this->id; @@ -59,4 +63,16 @@ class Site return $this; } + + public function getDisplayOrder(): int + { + return $this->displayOrder; + } + + public function setDisplayOrder(int $displayOrder): self + { + $this->displayOrder = $displayOrder; + + return $this; + } } diff --git a/src/State/AbsencePrintProvider.php b/src/State/AbsencePrintProvider.php index 8e6329c..e46c02e 100644 --- a/src/State/AbsencePrintProvider.php +++ b/src/State/AbsencePrintProvider.php @@ -114,7 +114,8 @@ class AbsencePrintProvider implements ProviderInterface ->createQueryBuilder('e') ->leftJoin('e.site', 's') ->addSelect('s') - ->orderBy('s.name', 'ASC') + ->orderBy('s.displayOrder', 'ASC') + ->addOrderBy('s.name', 'ASC') ->addOrderBy('e.displayOrder', 'ASC') ->addOrderBy('e.lastName', 'ASC') ->addOrderBy('e.firstName', 'ASC') diff --git a/templates/absence/print.html.twig b/templates/absence/print.html.twig index 9dd487a..4c4b88a 100644 --- a/templates/absence/print.html.twig +++ b/templates/absence/print.html.twig @@ -35,29 +35,27 @@ } .col-employee { - font-size: 16px; + font-size: 10px; font-weight: bold; width: 10mm; text-align: left; } .col-day { - font-size: 10px; + font-size: 8px; text-align: center; } .month { - font-size: 16px; + font-size: 14px; font-weight: bold; - padding: 3px 0; } .site-title td { font-weight: bold; - font-size: 16px; + font-size: 12px; text-transform: uppercase; border-color: #0a0a0a; - padding: 4px 6px; } .site-title .label { @@ -66,7 +64,7 @@ .code { font-weight: bold; - font-size: 9px; + font-size: 8px; } .holiday-code { @@ -95,8 +93,6 @@ .full-cell { display: block; - height: 6mm; - line-height: 6mm; text-align: center; margin: 0; padding: 0; @@ -104,7 +100,6 @@ .half-table { width: 100%; - height: 6mm; border-collapse: collapse; table-layout: fixed; border: 0;