diff --git a/frontend/pages/infrastructure/batiment.vue b/frontend/pages/infrastructure/batiment.vue index 8293cdb..2b1a424 100644 --- a/frontend/pages/infrastructure/batiment.vue +++ b/frontend/pages/infrastructure/batiment.vue @@ -30,21 +30,26 @@
-
+
@@ -63,21 +94,25 @@ import type { BuildingData } from "~/services/dto/building-data" import type { BuildingLayoutData } from "~/services/dto/building-layout-data" import type { BuildingCasePositionData } from "~/services/dto/building-case-position-data" +import type { BuildingCaseStatusData } from "~/services/dto/building-case-status-data" import { getBuildingList } from "~/services/building" +import { getStatutList } from "~/services/statut" definePageMeta({ layout: "default" }) const router = useRouter() const buildingList = ref([]) +const statutLegend = ref([]) const buildingLayouts = computed(() => { return buildingList.value.map((building) => { - const layout = getDisplayLayout(building) + const layout = building.layouts?.[0] ?? null const view = layout ? buildLayoutView(layout) : null return { building, layout, cells: view?.cells ?? [], - columnsTemplate: view?.columnsTemplate + columnsTemplate: view?.columnsTemplate, + gridStyle: view?.gridStyle ?? {} } }) }) @@ -93,35 +128,27 @@ type LayoutCell = { caseId: number | null display: string rawId?: number | string + hasGapLeft: boolean + hasGapRight: boolean + isOuterLeft: boolean + isOuterRight: boolean + caseStatusLabel: string | null + caseStyle?: Record + spanStyle: Record } -const getDisplayLayout = (building: BuildingData): BuildingLayoutData | null => { - const layouts = (building.layouts ?? []).filter(Boolean) as BuildingLayoutData[] - if (layouts.length === 0) return null - return layouts[0] ?? null +const normalizeCaseStatusColor = (value: string | null | undefined): string | null => { + const color = (value ?? "").trim() + return color.length > 0 ? color : null } -const getGridStyle = (layout: BuildingLayoutData, columnsTemplate?: string) => { - const cols = Math.max(0, layout.columns ?? 0) - return { - gridTemplateColumns: columnsTemplate ?? `repeat(${cols}, minmax(0, 1fr))`, - gridAutoRows: "1fr", - rowGap: "18.5px", - columnGap: "0px", - width: "100%" - } -} -const getCellSpanStyle = (cell: LayoutCell) => ({ - gridColumn: `${cell.x} / span ${cell.w}`, - gridRow: `${cell.y} / span ${cell.h}` -}) const buildLayoutView = ( layout: BuildingLayoutData -): { cells: LayoutCell[]; columnsTemplate: string } | null => { +): { cells: LayoutCell[]; columnsTemplate: string; gridStyle: Record } | null => { const totalRows = layout.rows ?? 0 const totalCols = layout.columns ?? 0 if (totalRows <= 0 || totalCols <= 0) return null const positions = (layout.casePositions ?? []).filter(Boolean) as BuildingCasePositionData[] - // marque les cases couvertes par un span pour ne pas générer du "vide" dessus + // éviter des doublons/chevauchements dans la grille pour les spans. const covered = Array.from({ length: totalRows }, () => Array.from({ length: totalCols }, () => false)) // map positions by (x,y) @@ -140,6 +167,7 @@ const buildLayoutView = ( for (let x = 1; x <= totalCols; x++) { if (!occupiedColumns.has(x)) gapColumns.push(x) } + const gapColumnsSet = new Set(gapColumns) for (let y = 1; y <= totalRows; y++) { for (let x = 1; x <= totalCols; x++) { @@ -157,6 +185,20 @@ const buildLayoutView = ( } const caseNumber = (p.buildingCase?.caseNumber ?? null) as number | null const caseId = (p.buildingCase?.id ?? null) as number | null + const statusLabel = p.buildingCase?.statut?.label ?? null + const statusColor = normalizeCaseStatusColor(p.buildingCase?.statut?.couleur) + const caseStatusLabel = statusLabel + const caseStyle = statusColor + ? { backgroundColor: statusColor } + : undefined + const spanStyle = { + gridColumn: `${x} / span ${w}`, + gridRow: `${y} / span ${h}` + } + const hasGapLeft = gapColumnsSet.has(x - 1) + const hasGapRight = gapColumnsSet.has(x + w) + const isOuterLeft = x === 1 + const isOuterRight = x + w - 1 === totalCols cells.push({ key: `case-${layout.id}-${p.id}`, x, @@ -167,11 +209,22 @@ const buildLayoutView = ( caseNumber, caseId, display: caseNumber !== null ? String(caseNumber) : "Case", - rawId: p.id + rawId: p.id, + hasGapLeft, + hasGapRight, + isOuterLeft, + isOuterRight, + caseStatusLabel, + caseStyle, + spanStyle }) } for (const gapX of gapColumns) { + const spanStyle = { + gridColumn: `${gapX} / span 1`, + gridRow: `${y} / span 1` + } cells.push({ key: `gap-${layout.id}-${y}-${gapX}`, x: gapX, @@ -182,18 +235,40 @@ const buildLayoutView = ( caseNumber: null, caseId: null, display: "", - rawId: undefined + rawId: undefined, + hasGapLeft: false, + hasGapRight: false, + isOuterLeft: gapX === 1, + isOuterRight: gapX === totalCols, + caseStatusLabel: null, + caseStyle: undefined, + spanStyle }) } } const columnsTemplate = Array.from({ length: totalCols }, (_, idx) => - gapColumns.includes(idx + 1) ? "24px" : "minmax(0, 1fr)" + gapColumnsSet.has(idx + 1) ? "24px" : "minmax(0, 1fr)" ).join(" ") + const cols = Math.max(0, layout.columns ?? 0) + const gridStyle = { + gridTemplateColumns: columnsTemplate ?? `repeat(${cols}, minmax(0, 1fr))`, + gridAutoRows: "1fr", + rowGap: "18px", + columnGap: "0px", + width: "100%" + } - return { cells, columnsTemplate } + return { cells, columnsTemplate, gridStyle } } onMounted(async () => { - buildingList.value = await getBuildingList() + const buildingsPromise = getBuildingList() + const statutsPromise = getStatutList() + const buildings = await buildingsPromise + const statuts = await statutsPromise + buildingList.value = buildings + statutLegend.value = [...statuts].sort((a, b) => + (a.label ?? "").localeCompare(b.label ?? "", "fr", { sensitivity: "base" }) + ) }) diff --git a/frontend/services/dto/building-case-data.ts b/frontend/services/dto/building-case-data.ts index 6a59f1f..4d718c3 100644 --- a/frontend/services/dto/building-case-data.ts +++ b/frontend/services/dto/building-case-data.ts @@ -1,6 +1,9 @@ +import type { BuildingCaseStatusData } from '~/services/dto/building-case-status-data' + export interface BuildingCaseData { id: number caseNumber: number | null code: string | null capacity: number | null + statut?: BuildingCaseStatusData | null } diff --git a/frontend/services/dto/building-case-status-data.ts b/frontend/services/dto/building-case-status-data.ts new file mode 100644 index 0000000..187a412 --- /dev/null +++ b/frontend/services/dto/building-case-status-data.ts @@ -0,0 +1,6 @@ +export interface BuildingCaseStatusData { + id: number + label: string | null + code: string | null + couleur: string | null +} diff --git a/migrations/Version20260220101607.php b/migrations/Version20260220101607.php new file mode 100644 index 0000000..301f0d8 --- /dev/null +++ b/migrations/Version20260220101607.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE statut (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, label VARCHAR(255) NOT NULL, code VARCHAR(255) NOT NULL, color VARCHAR(255) NOT NULL, PRIMARY KEY (id))'); + $this->addSql('ALTER TABLE building_case ADD statut_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE building_case ADD CONSTRAINT FK_DE2CEE50F6203804 FOREIGN KEY (statut_id) REFERENCES statut (id)'); + $this->addSql('CREATE INDEX IDX_DE2CEE50F6203804 ON building_case (statut_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE statut'); + $this->addSql('ALTER TABLE building_case DROP CONSTRAINT FK_DE2CEE50F6203804'); + $this->addSql('DROP INDEX IDX_DE2CEE50F6203804'); + $this->addSql('ALTER TABLE building_case DROP statut_id'); + } +} diff --git a/src/Entity/BuildingCase.php b/src/Entity/BuildingCase.php index 1730324..1864560 100644 --- a/src/Entity/BuildingCase.php +++ b/src/Entity/BuildingCase.php @@ -42,6 +42,10 @@ class BuildingCase #[ORM\ManyToOne(inversedBy: 'buildingCases')] private ?Building $id_building = null; + #[ORM\ManyToOne(inversedBy: 'id_case')] + #[Groups(['building:read'])] + private ?Statut $statut = null; + public function __construct() { $this->id_case_position = new ArrayCollection(); @@ -136,4 +140,16 @@ class BuildingCase return $this; } + + public function getStatut(): ?Statut + { + return $this->statut; + } + + public function setStatut(?Statut $statut): static + { + $this->statut = $statut; + + return $this; + } } diff --git a/src/Entity/Statut.php b/src/Entity/Statut.php new file mode 100644 index 0000000..431f889 --- /dev/null +++ b/src/Entity/Statut.php @@ -0,0 +1,139 @@ + '\d+'], + normalizationContext: ['groups' => ['building:read']], + ), + new GetCollection( + normalizationContext: ['groups' => ['building:read']], + ), + ], + security: "is_granted('ROLE_USER')", +)] +class Statut +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + #[Groups(['building:read'])] + private ?int $id = null; + + #[ORM\Column(length: 255)] + #[Groups(['building:read'])] + private ?string $label = null; + + #[ORM\Column(length: 255)] + #[Groups(['building:read'])] + private ?string $code = null; + + #[ORM\Column(length: 255)] + #[Groups(['building:read'])] + #[SerializedName('couleur')] + private ?string $color = null; + + /** + * @var Collection + */ + #[ORM\OneToMany(targetEntity: BuildingCase::class, mappedBy: 'statut')] + private Collection $id_case; + + public function __construct() + { + $this->id_case = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function setId(int $id): static + { + $this->id = $id; + + return $this; + } + + public function getLabel(): ?string + { + return $this->label; + } + + public function setLabel(string $label): static + { + $this->label = $label; + + return $this; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(string $code): static + { + $this->code = $code; + + return $this; + } + + public function getColor(): ?string + { + return $this->color; + } + + public function setColor(string $color): static + { + $this->color = $color; + + return $this; + } + + /** + * @return Collection + */ + public function getIdCase(): Collection + { + return $this->id_case; + } + + public function addIdCase(BuildingCase $idCase): static + { + if (!$this->id_case->contains($idCase)) { + $this->id_case->add($idCase); + $idCase->setStatut($this); + } + + return $this; + } + + public function removeIdCase(BuildingCase $idCase): static + { + if ($this->id_case->removeElement($idCase)) { + // set the owning side to null (unless already changed) + if ($idCase->getStatut() === $this) { + $idCase->setStatut(null); + } + } + + return $this; + } +}