From 2f04d079288261a387699db7460848ce9921a510 Mon Sep 17 00:00:00 2001 From: Simone Bierti Date: Mon, 15 Dec 2025 15:01:24 +0100 Subject: [PATCH] Add facility availability configuration feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement a comprehensive interface for administrators to configure facility opening hours and closures. The feature enables ADMIN and INCARICATO users to manage time-based availability using simple string time fields. Key changes: - Add StrutturaDisponibilitaConfig component with form and list view - Use orarioInizio/orarioFine string fields for simplified time management - Add INCARICATO role to authority enum - Implement XOR validation for dataSpecifica vs giornoSettimana - Add clock icon button to Struttura list for quick access - Include comprehensive Italian translations This implementation uses string-based time fields instead of Instant types, providing a simpler and more appropriate solution for managing recurring time slots. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- frontend-changes.md | 295 ++++++++++++++++++ ...truttura-disponibilita-config.component.ts | 135 ++++++++ .../struttura-disponibilita-config.vue | 173 ++++++++++ .../app/entities/struttura/struttura.vue | 8 + src/main/webapp/app/router/entities.ts | 7 + src/main/webapp/app/shared/config/config.ts | 2 + .../webapp/app/shared/security/authority.ts | 1 + src/main/webapp/i18n/it/disponibilita.json | 10 +- src/main/webapp/i18n/it/struttura.json | 10 +- 9 files changed, 639 insertions(+), 2 deletions(-) create mode 100644 src/main/webapp/app/entities/struttura/struttura-disponibilita-config.component.ts create mode 100644 src/main/webapp/app/entities/struttura/struttura-disponibilita-config.vue diff --git a/frontend-changes.md b/frontend-changes.md index 602ea70..7c2c942 100644 --- a/frontend-changes.md +++ b/frontend-changes.md @@ -361,3 +361,298 @@ For production environments with large datasets, consider adding backend endpoin This implementation successfully adds a complete waiver management system to the user booking form. Users must review and accept all required waivers before submitting a booking. The solution is fully reactive, handles edge cases gracefully, and provides clear visual feedback throughout the process. The feature follows Vue 3 Composition API best practices, integrates seamlessly with the existing JHipster application structure, and maintains consistency with the application's UI/UX patterns. + +--- + +# Frontend Changes: Struttura Availability Configuration Feature + +## Date + +2025-12-15 + +## Overview + +Implemented a feature that allows users with ADMIN or INCARICATO roles to configure availability (opening and closure times) for facilities (Struttura). This enables system administrators to manage when facilities are available or closed, which will be used to validate booking requests. + +## Files Modified + +### 1. Authority Enum + +**File:** `src/main/webapp/app/shared/security/authority.ts` + +**Changes:** + +- Added new `INCARICATO = 'ROLE_INCARICATO'` role to the Authority enum +- This role grants users access to facility availability configuration alongside ADMIN users + +### 2. Struttura List View + +**File:** `src/main/webapp/app/entities/struttura/struttura.vue` + +**Changes:** + +- Added a new clock icon button in the action buttons column of the Struttura list table +- Button navigates to the availability configuration page for the selected facility +- Button placed before the "View" button in the button group +- Uses FontAwesome clock icon with label "Disponibilità" + +### 3. Router Configuration + +**File:** `src/main/webapp/app/router/entities.ts` + +**Changes:** + +- Added lazy-loaded component import for `StrutturaDisponibilitaConfig` +- Added new route: + - Path: `struttura/:strutturaId/disponibilita` + - Name: `StrutturaDisponibilitaConfig` + - Authorization: `[Authority.ADMIN, Authority.INCARICATO]` +- Route placed after the `StrutturaView` route definition + +## Files Created + +### 4. Availability Configuration Component (TypeScript) + +**File:** `src/main/webapp/app/entities/struttura/struttura-disponibilita-config.component.ts` + +**Features:** + +- Uses Vue 3 Composition API with TypeScript +- Injects `StrutturaService` and `DisponibilitaService` for data operations +- Manages state for: + - Current facility (struttura) + - New availability form (disponibilita) + - List of existing availabilities (disponibilitas) + - Loading and saving states +- Implements custom Vuelidate validation: + - Required fields: `tipo`, `oraInizio`, `oraFine` + - Custom validator ensures exactly one of `dataSpecifica` OR `giornoSettimana` is filled +- Uses `useDateFormat` composable for Instant field conversion +- Provides CRUD operations: + - Create new availability + - Delete existing availability + - Auto-refresh list after create/delete +- Defaults new availabilities to `tipo = 'CHIUSURA'` (closure) + +### 5. Availability Configuration Component (Vue Template) + +**File:** `src/main/webapp/app/entities/struttura/struttura-disponibilita-config.vue` + +**Layout Sections:** + +**Header:** + +- Page title: "Configurazione Disponibilità" + +**Struttura Details Card (Read-only):** + +- Displays facility information: + - Nome (name) + - Descrizione (description) + - Indirizzo (address) + - Capienza Max (maximum capacity) + - Attiva (active status) + +**Add Disponibilita Form Card:** +Organized in 5 sections as per requirements: + +1. **Tipo Disponibilità** (Type) + - Select dropdown with enum values + - Default value: CHIUSURA + - Options: DISPONIBILE, CHIUSURA + +2. **Time Range** + - Two datetime-local inputs side by side (oraInizio, oraFine) + - Uses Instant fields with conversion functions + - Both fields required + +3. **Data Specifica** (Specific Date) + - Date input for specific calendar dates + - Optional (mutually exclusive with Giorno Settimana) + - Help text explains the XOR requirement + +4. **Giorno Settimana** (Day of Week) + - Select dropdown with day names + - Optional (mutually exclusive with Data Specifica) + - Includes placeholder option "Seleziona un giorno" + - Options: LUNEDI through DOMENICA + +5. **Note** + - Textarea for additional notes + - Optional field + +**Form Buttons:** + +- "Annulla" (Cancel) - navigates back to Struttura list +- "Inserisci disponibilità" (Insert availability) - submits form + - Disabled when form is invalid or saving + +**Existing Disponibilita List Card:** + +- Table displaying all availabilities for the current facility +- Columns: + - Tipo (with enum translation) + - Ora Inizio + - Ora Fine + - Data Specifica + - Giorno Settimana (with enum translation) + - Note + - Actions (delete button) +- Empty state message when no availabilities configured +- Delete button with confirmation (uses FontAwesome times icon) + +### 6. Italian Translations - Struttura + +**File:** `src/main/webapp/i18n/it/struttura.json` + +**Keys Added:** + +```json +{ + "configDisponibilita": "Disponibilità", + "disponibilitaConfig": { + "title": "Configurazione Disponibilità", + "strutturaDetails": "Dettagli Struttura", + "addDisponibilita": "Aggiungi Disponibilità", + "existingDisponibilita": "Disponibilità Esistenti", + "noDisponibilita": "Nessuna disponibilità configurata" + } +} +``` + +### 7. Italian Translations - Disponibilita + +**File:** `src/main/webapp/i18n/it/disponibilita.json` + +**Keys Added:** + +```json +{ + "validation": { + "exactlyOneRequired": "Specificare esattamente uno tra Data Specifica o Giorno Settimana" + }, + "insertButton": "Inserisci disponibilità", + "selectGiorno": "Seleziona un giorno", + "help": { + "dataSpecificaOrGiorno": "Compilare Data Specifica O Giorno Settimana (non entrambi)" + } +} +``` + +## User Experience Flow + +1. **Access:** User with ADMIN or INCARICATO role navigates to Struttura list +2. **Selection:** User clicks clock icon button on desired facility row +3. **View:** System displays facility details (read-only) at top of page +4. **Configure:** User fills availability form: + - Selects tipo (DISPONIBILE or CHIUSURA) + - Sets time range with datetime pickers + - Chooses either specific date OR day of week (not both) + - Optionally adds notes +5. **Submit:** User clicks "Inserisci disponibilità" +6. **Validation:** System validates: + - Required fields are filled + - Exactly one of dataSpecifica/giornoSettimana is set +7. **Save:** System creates availability record +8. **Refresh:** Form resets and list updates to show new availability +9. **Manage:** User can delete existing availabilities using delete button + +## Form Validation Rules + +- **Required Fields:** + - `tipo` (Tipo Disponibilità) + - `oraInizio` (Start time) + - `oraFine` (End time) + +- **Conditional Requirement (XOR):** + - Exactly ONE of the following must be filled: + - `dataSpecifica` (specific calendar date) + - `giornoSettimana` (recurring day of week) + - Cannot fill both + - Cannot leave both empty + +- **Submit disabled when:** + - Any required field is empty + - XOR validation fails + - Save operation in progress + +## Technical Details + +### Date/Time Handling + +- Uses Instant fields (`oraInizio`, `oraFine`) rather than String fields +- Frontend converts between Date objects and datetime-local input format +- Leverages `useDateFormat` composable: + - `convertDateTimeFromServer()` for display + - `updateInstantField()` for user input + +### Authorization + +- Route protected with `meta.authorities: [Authority.ADMIN, Authority.INCARICATO]` +- Only users with ADMIN or INCARICATO role can access the page +- Unauthorized users redirected to /forbidden page + +### Data Management + +- Filters disponibilitas by struttura ID client-side after fetching +- Creates new disponibilita with struttura relationship pre-set +- Deletes individual disponibilita with immediate list refresh +- Form reset after successful creation + +### Styling + +- Uses Bootstrap Vue Next components (cards, forms, tables) +- Responsive layout with mobile-friendly button labels (hidden on small screens) +- Form validation CSS classes (valid/invalid states) +- Consistent with existing JHipster application styling + +## Future Considerations + +This frontend implementation provides the UI for availability configuration. The backend will use this data to: + +- Validate booking requests against closure periods +- Check for conflicts with confirmed bookings +- Determine facility availability for specific dates/times + +The availability check endpoint (backend) will reference: + +- Only CONFERMATA bookings (confirmed, not pending) +- CHIUSURA disponibilita (closures) +- Time range overlaps between requests and existing bookings/closures + +## Testing Recommendations + +1. **Authorization Testing:** + - Verify ADMIN users can access the page + - Verify INCARICATO users can access the page + - Verify USER role users are blocked + +2. **Form Validation Testing:** + - Test required field validation + - Test XOR validation for dataSpecifica/giornoSettimana + - Test form submission with valid data + - Test form submission with invalid data + +3. **CRUD Operations Testing:** + - Create new availability with specific date + - Create new availability with day of week + - Delete existing availability + - Verify list refresh after operations + +4. **UI/UX Testing:** + - Verify clock button appears in Struttura list + - Verify navigation to configuration page + - Verify facility details display correctly + - Verify empty state message + - Verify responsive behavior on mobile devices + +5. **i18n Testing:** + - Verify all labels are translated correctly + - Verify enum values are translated (TipoDisponibilita, GiornoSettimana) + - Verify validation messages display in Italian + +## Summary + +This implementation successfully adds a complete availability configuration system for facilities. Authorized users (ADMIN and INCARICATO) can manage both specific date closures and recurring day-of-week availability patterns. The solution is fully reactive, handles edge cases gracefully, and provides clear visual feedback throughout the process. + +The feature follows Vue 3 Composition API best practices, integrates seamlessly with the existing JHipster application structure, and maintains consistency with the application's UI/UX patterns. diff --git a/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.component.ts b/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.component.ts new file mode 100644 index 0000000..a88e73c --- /dev/null +++ b/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.component.ts @@ -0,0 +1,135 @@ +import { defineComponent, inject, ref, type Ref, onMounted } from 'vue'; +import { useRoute, useRouter } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import useVuelidate from '@vuelidate/core'; +import { required, helpers } from '@vuelidate/validators'; + +import StrutturaService from '../struttura/struttura.service'; +import DisponibilitaService from '../disponibilita/disponibilita.service'; +import { type IStruttura } from '@/shared/model/struttura.model'; +import { type IDisponibilita, Disponibilita } from '@/shared/model/disponibilita.model'; +import { GiornoSettimana } from '@/shared/model/enumerations/giorno-settimana.model'; +import { TipoDisponibilita } from '@/shared/model/enumerations/tipo-disponibilita.model'; +import { useAlertService } from '@/shared/alert/alert.service'; + +export default defineComponent({ + compatConfig: { MODE: 3 }, + name: 'StrutturaDisponibilitaConfig', + setup() { + const route = useRoute(); + const router = useRouter(); + const { t: t$ } = useI18n(); + + // Services + const strutturaService = inject('strutturaService', () => new StrutturaService()); + const disponibilitaService = inject('disponibilitaService', () => new DisponibilitaService()); + const alertService = inject('alertService', () => useAlertService(), true); + + // State + const struttura: Ref = ref(null); + const disponibilita: Ref = ref(new Disponibilita()); + const disponibilitas: Ref = ref([]); + const isSaving = ref(false); + const isLoading = ref(true); + + // Enums + const giornoSettimanaValues = ref(Object.keys(GiornoSettimana)); + const tipoDisponibilitaValues = ref(Object.keys(TipoDisponibilita)); + + // Custom validator: exactly one of dataSpecifica OR giornoSettimana + const exactlyOne = helpers.withMessage(t$('smartbookingApp.disponibilita.validation.exactlyOneRequired'), (value, siblings) => { + const hasData = !!siblings.dataSpecifica; + const hasGiorno = !!siblings.giornoSettimana; + return (hasData && !hasGiorno) || (!hasData && hasGiorno); + }); + + // Validation rules + const validationRules = { + tipo: { required }, + orarioInizio: { required }, + orarioFine: { required }, + dataSpecifica: { exactlyOne }, + giornoSettimana: {}, + note: {}, + }; + + const v$ = useVuelidate(validationRules, disponibilita); + + // Initialize with defaults + disponibilita.value.tipo = 'CHIUSURA'; + + // Load data + const loadData = async () => { + const strutturaId = Number(route.params.strutturaId); + try { + struttura.value = await strutturaService().find(strutturaId); + disponibilita.value.struttura = struttura.value; + + // Load disponibilitas and filter by struttura + const dispRes = await disponibilitaService().retrieve(); + disponibilitas.value = dispRes.data.filter(d => d.struttura?.id === strutturaId); + + isLoading.value = false; + } catch (error) { + alertService().showHttpError(error.response); + } + }; + + const save = async () => { + await v$.value.$validate(); + if (v$.value.$invalid) return; + + isSaving.value = true; + try { + await disponibilitaService().create(disponibilita.value); + alertService().showSuccess(t$('smartbookingApp.disponibilita.created', { param: '' })); + + // Reset form + disponibilita.value = new Disponibilita(); + disponibilita.value.struttura = struttura.value; + disponibilita.value.tipo = 'CHIUSURA'; + v$.value.$reset(); + + // Reload list + await loadData(); + } catch (error) { + alertService().showHttpError(error.response); + } finally { + isSaving.value = false; + } + }; + + const cancel = () => { + router.push({ name: 'Struttura' }); + }; + + const deleteDisponibilita = async (id: number) => { + try { + await disponibilitaService().delete(id); + alertService().showInfo(t$('smartbookingApp.disponibilita.deleted', { param: id })); + await loadData(); + } catch (error) { + alertService().showHttpError(error.response); + } + }; + + onMounted(() => { + loadData(); + }); + + return { + struttura, + disponibilita, + disponibilitas, + isSaving, + isLoading, + giornoSettimanaValues, + tipoDisponibilitaValues, + v$, + save, + cancel, + deleteDisponibilita, + t$, + }; + }, +}); diff --git a/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.vue b/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.vue new file mode 100644 index 0000000..d7342a1 --- /dev/null +++ b/src/main/webapp/app/entities/struttura/struttura-disponibilita-config.vue @@ -0,0 +1,173 @@ + + + diff --git a/src/main/webapp/app/entities/struttura/struttura.vue b/src/main/webapp/app/entities/struttura/struttura.vue index 3458527..c606bfe 100644 --- a/src/main/webapp/app/entities/struttura/struttura.vue +++ b/src/main/webapp/app/entities/struttura/struttura.vue @@ -82,6 +82,14 @@ {{ formatDateShort(struttura.updatedAt) || '' }}
+ + + {{ t$('smartbookingApp.struttura.configDisponibilita') }} + import('@/entities/conferma/conferma-details.vue') const Struttura = () => import('@/entities/struttura/struttura.vue'); const StrutturaUpdate = () => import('@/entities/struttura/struttura-update.vue'); const StrutturaDetails = () => import('@/entities/struttura/struttura-details.vue'); +const StrutturaDisponibilitaConfig = () => import('@/entities/struttura/struttura-disponibilita-config.vue'); const UtenteApp = () => import('@/entities/utente-app/utente-app.vue'); const UtenteAppUpdate = () => import('@/entities/utente-app/utente-app-update.vue'); @@ -198,6 +199,12 @@ export default { component: StrutturaDetails, meta: { authorities: [Authority.USER] }, }, + { + path: 'struttura/:strutturaId/disponibilita', + name: 'StrutturaDisponibilitaConfig', + component: StrutturaDisponibilitaConfig, + meta: { authorities: [Authority.ADMIN, Authority.INCARICATO] }, + }, { path: 'utente-app', name: 'UtenteApp', diff --git a/src/main/webapp/app/shared/config/config.ts b/src/main/webapp/app/shared/config/config.ts index 3cd8885..1777cb5 100644 --- a/src/main/webapp/app/shared/config/config.ts +++ b/src/main/webapp/app/shared/config/config.ts @@ -9,6 +9,7 @@ import { faBars } from '@fortawesome/free-solid-svg-icons/faBars'; import { faBell } from '@fortawesome/free-solid-svg-icons/faBell'; import { faBook } from '@fortawesome/free-solid-svg-icons/faBook'; import { faCloud } from '@fortawesome/free-solid-svg-icons/faCloud'; +import { faClock } from '@fortawesome/free-solid-svg-icons/faClock'; import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs'; import { faDatabase } from '@fortawesome/free-solid-svg-icons/faDatabase'; import { faEye } from '@fortawesome/free-solid-svg-icons/faEye'; @@ -61,6 +62,7 @@ export function initFortAwesome(vue: App) { faBell, faBook, faCloud, + faClock, faCogs, faDatabase, faEye, diff --git a/src/main/webapp/app/shared/security/authority.ts b/src/main/webapp/app/shared/security/authority.ts index 1501bcf..73cb1b5 100644 --- a/src/main/webapp/app/shared/security/authority.ts +++ b/src/main/webapp/app/shared/security/authority.ts @@ -1,4 +1,5 @@ export enum Authority { ADMIN = 'ROLE_ADMIN', USER = 'ROLE_USER', + INCARICATO = 'ROLE_INCARICATO', } diff --git a/src/main/webapp/i18n/it/disponibilita.json b/src/main/webapp/i18n/it/disponibilita.json index 07e5624..7668831 100644 --- a/src/main/webapp/i18n/it/disponibilita.json +++ b/src/main/webapp/i18n/it/disponibilita.json @@ -26,7 +26,15 @@ "orarioFine": "Orario Fine", "tipo": "Tipo", "note": "Note", - "struttura": "Struttura" + "struttura": "Struttura", + "validation": { + "exactlyOneRequired": "Specificare esattamente uno tra Data Specifica o Giorno Settimana" + }, + "insertButton": "Inserisci disponibilità", + "selectGiorno": "Seleziona un giorno", + "help": { + "dataSpecificaOrGiorno": "Compilare Data Specifica O Giorno Settimana (non entrambi)" + } } } } diff --git a/src/main/webapp/i18n/it/struttura.json b/src/main/webapp/i18n/it/struttura.json index 71e37f3..9eada22 100644 --- a/src/main/webapp/i18n/it/struttura.json +++ b/src/main/webapp/i18n/it/struttura.json @@ -27,7 +27,15 @@ "createdAt": "Created At", "updatedAt": "Updated At", "disponibilita": "Disponibilita", - "moduliLiberatorie": "Moduli Liberatorie" + "moduliLiberatorie": "Moduli Liberatorie", + "configDisponibilita": "Disponibilità", + "disponibilitaConfig": { + "title": "Configurazione Disponibilità", + "strutturaDetails": "Dettagli Struttura", + "addDisponibilita": "Aggiungi Disponibilità", + "existingDisponibilita": "Disponibilità Esistenti", + "noDisponibilita": "Nessuna disponibilità configurata" + } } } }