From ff4f531875e22a71281b5fb089824a84450de2fa Mon Sep 17 00:00:00 2001 From: Emmanuel Meuwly Date: Sat, 20 Jun 2026 22:40:03 +0200 Subject: [PATCH] feat(incident,maintenance): pick affected services from a list instead of typing IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The incident form required pasting a raw service_id and the maintenance form a free-text comma-separated list, so users had to know internal IDs and exact service names. Both now use the uptime services list. - Incident: the "Service ID" field becomes a dropdown of uptime services (single select, with a status dot) that writes the chosen service's id to service_id. - Maintenance: "Affected services" becomes a multi-select — pick services from a dropdown, shown as removable badges; still stored in the existing `affected` text field as comma-separated service names. Frontend-only. Create and edit dialogs both reuse these field components. No new i18n keys (reuses serviceId / selectUptimeService / affectedServices). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../incident/form/IncidentBasicFields.tsx | 39 +++++++- .../form/MaintenanceAffectedFields.tsx | 90 ++++++++++++++++--- 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx b/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx index 38825953..7e0b4704 100644 --- a/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx +++ b/application/src/components/schedule-incident/incident/form/IncidentBasicFields.tsx @@ -12,8 +12,16 @@ import { } from '@/components/ui/form'; import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { useQuery } from '@tanstack/react-query'; import { userService } from '@/services/userService'; +import { serviceService } from '@/services/serviceService'; import { Badge } from '@/components/ui/badge'; import { X, Users } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -50,6 +58,13 @@ export const IncidentBasicFields: React.FC = () => { staleTime: 300000 // Cache for 5 minutes }); + // Fetch uptime services for the affected-service dropdown + const { data: services = [] } = useQuery({ + queryKey: ['services'], + queryFn: serviceService.getServices, + staleTime: 300000, + }); + // Add user to assigned_to const addUser = (userId: string) => { // For now, we're using a single user assignment @@ -119,9 +134,27 @@ export const IncidentBasicFields: React.FC = () => { render={({ field }) => ( {t('serviceId')} - - - + )} diff --git a/application/src/components/schedule-incident/maintenance/form/MaintenanceAffectedFields.tsx b/application/src/components/schedule-incident/maintenance/form/MaintenanceAffectedFields.tsx index 2f933dde..43dc88c0 100644 --- a/application/src/components/schedule-incident/maintenance/form/MaintenanceAffectedFields.tsx +++ b/application/src/components/schedule-incident/maintenance/form/MaintenanceAffectedFields.tsx @@ -10,26 +10,94 @@ import { FormControl, FormMessage, } from '@/components/ui/form'; -import { Input } from '@/components/ui/input'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { X } from 'lucide-react'; +import { useQuery } from '@tanstack/react-query'; +import { serviceService } from '@/services/serviceService'; + +// Affected services are stored as a comma-separated list of service names in +// the `affected` text field. This control lets the user pick them from the +// uptime services instead of typing names by hand, while keeping that format. +const splitServices = (value: string): string[] => + value + ? value.split(',').map((s) => s.trim()).filter(Boolean) + : []; export const MaintenanceAffectedFields: React.FC = () => { const { t } = useLanguage(); const { control } = useFormContext(); + const { data: services = [] } = useQuery({ + queryKey: ['services'], + queryFn: serviceService.getServices, + staleTime: 300000, + }); + return ( ( - - {t('affectedServices')} - - - - -

{t('separateServicesWithComma')}

-
- )} + render={({ field }) => { + const selected = splitServices(field.value); + + const addService = (name: string) => { + if (name && !selected.includes(name)) { + field.onChange([...selected, name].join(', ')); + } + }; + + const removeService = (name: string) => { + field.onChange(selected.filter((s) => s !== name).join(', ')); + }; + + return ( + + {t('affectedServices')} + + + + + {selected.length > 0 && ( +
+ {selected.map((name) => ( + + {name} + + + ))} +
+ )} + +
+ ); + }} /> ); };