From e4358a31aeef3cd33dc21ccc07726f502c78444e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilien=20Pressens=C3=A9?= Date: Sun, 26 Jan 2025 15:15:21 +0000 Subject: [PATCH 01/31] Add location management to contribution form - Implemented OsblLocations component for managing organization locations - Created Location and Address models with polymorphic associations - Added database migrations for locations and addresses - Updated Osbl model to support nested location attributes - Integrated location input with address autocomplete using French address API - Enhanced contribution form to include location details with type selection and optional information This commit introduces comprehensive location management for organizations, improving data collection and representation in the contribution form. --- .../users/contributions_controller.rb | 15 ++ .../pages/contribution/new/OsblLocations.tsx | 186 ++++++++++++++++++ .../components/shared/MyAsyncSelect.tsx | 97 +++++++++ app/frontend/pages/Contribution/New.tsx | 2 + app/frontend/pages/Contribution/types.ts | 43 +++- app/models/address.rb | 3 + app/models/location.rb | 18 ++ app/models/osbl.rb | 3 + db/migrate/20250126104700_create_locations.rb | 19 ++ db/migrate/20250126140903_create_addresses.rb | 22 +++ db/schema.rb | 32 ++- spec/factories/addresses.rb | 12 ++ spec/factories/locations.rb | 8 + spec/models/address_spec.rb | 5 + spec/models/location_spec.rb | 5 + 15 files changed, 461 insertions(+), 9 deletions(-) create mode 100644 app/frontend/components/pages/contribution/new/OsblLocations.tsx create mode 100644 app/frontend/components/shared/MyAsyncSelect.tsx create mode 100644 app/models/address.rb create mode 100644 app/models/location.rb create mode 100644 db/migrate/20250126104700_create_locations.rb create mode 100644 db/migrate/20250126140903_create_addresses.rb create mode 100644 spec/factories/addresses.rb create mode 100644 spec/factories/locations.rb create mode 100644 spec/models/address_spec.rb create mode 100644 spec/models/location_spec.rb diff --git a/app/controllers/users/contributions_controller.rb b/app/controllers/users/contributions_controller.rb index b6ca8b5..d27b211 100644 --- a/app/controllers/users/contributions_controller.rb +++ b/app/controllers/users/contributions_controller.rb @@ -77,6 +77,21 @@ def osbl_params :year, :description ]} + ]}, + {locations_attributes: [ + :type, + :name, + :description, + :website, + {address_attributes: [ + :street_number, + :street_name, + :additional_info, + :postal_code, + :city, + :latitude, + :longitude + ]} ]} ) end diff --git a/app/frontend/components/pages/contribution/new/OsblLocations.tsx b/app/frontend/components/pages/contribution/new/OsblLocations.tsx new file mode 100644 index 0000000..69a98ef --- /dev/null +++ b/app/frontend/components/pages/contribution/new/OsblLocations.tsx @@ -0,0 +1,186 @@ +import { ReactElement } from 'react' +import { FormProps, Location } from '@/pages/Contribution/types' +import { Button } from '@/components/ui/button' +import { PlusIcon, TrashIcon, ChevronDown } from 'lucide-react' +import MyInput from '@/components/forms/MyInput' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' +import { Card, CardContent, CardHeader } from '@/components/ui/card' +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' +import MyAsyncSelect from '@/components/shared/MyAsyncSelect' + +const LocationTypeList = [ + { value: 'siege_social', label: 'Siège social' }, + { value: 'antenne_locale', label: 'Antenne locale' }, + { value: 'lieux_d_activite', label: 'Lieux d\'activité' }, + { value: 'autre', label: 'Autre' } +] + +export default function OsblLocations ({ data, setData, clearErrors }: FormProps): ReactElement { + const locations = data.locations_attributes ?? [] + + const hasSiegeSocial = locations.some(loc => loc.type === 'siege_social') + + function handleLocationChange ( + index: number, + field: keyof Location, + value: Location[typeof field] + ): void { + const updatedLocations = locations.map((loc, i: number) => + i === index + ? { + ...loc, + [field]: value + } + : loc + ) + + setData('locations_attributes', updatedLocations) + clearErrors(`locations_attributes.${index}.${field}`) + } + + function handleAddressChange (index: number, value: any, field?: string): void { + const updatedLocations = locations.map((loc, i) => + i === index + ? { + ...loc, + address_attributes: field + ? { ...loc.address_attributes, [field]: value } + : value + } + : loc + ) + setData('locations_attributes', updatedLocations) + } + + function handleLocationAdd (e: React.MouseEvent): void { + e.preventDefault() + setData('locations_attributes', [...locations, {}]) + } + + function handleLocationRemove ( + e: React.MouseEvent, + index: number + ): void { + e.preventDefault() + const updatedDocuments = locations.filter((_, i) => i !== index) + setData('locations_attributes', updatedDocuments) + } + + return ( +
+
+
+

Implantation

+ +
+ +
+ {locations?.map((loc, index) => ( + + +
+ + + +
+
+ + + handleAddressChange(index, value)} + attributeName='address_attributes' + placeholder='Adresse *' + required + /> + + + + + Informations additionnelles + + + + handleLocationChange(index, 'name', e.target.value)} + placeholder='Nom du lieu' + /> + + handleAddressChange(index, e.target.value, 'additional_info')} + placeholder="Complément d'adresse" + /> + + handleLocationChange(index, 'website', e.target.value)} + placeholder='Site web' + /> + +
+