From 0ac5b589e83c5c41f32511f80c0e1e8b8c119fc5 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 4 Nov 2025 23:19:25 +0100 Subject: [PATCH] add page organisation --- .../dashboard/settings/organization/page.tsx | 608 ++++++++++-------- 1 file changed, 341 insertions(+), 267 deletions(-) diff --git a/apps/frontend/app/dashboard/settings/organization/page.tsx b/apps/frontend/app/dashboard/settings/organization/page.tsx index 6bd8b3e..79cd6bb 100644 --- a/apps/frontend/app/dashboard/settings/organization/page.tsx +++ b/apps/frontend/app/dashboard/settings/organization/page.tsx @@ -1,305 +1,379 @@ -/** - * Organization Settings Page - * - * Manage organization details - */ - 'use client'; -import { useState } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { organizationsApi } from '@/lib/api'; +import { useEffect, useState } from 'react'; +import { useAuth } from '@/lib/context/auth-context'; +import { getOrganization, updateOrganization } from '@/lib/api/organizations'; +import type { OrganizationResponse } from '@/types/api'; + +interface OrganizationForm { + name: string; + siren: string; // TODO: Add to backend + eori: string; // TODO: Add to backend + contact_phone: string; + contact_email: string; + address_street: string; + address_city: string; + address_postal_code: string; + address_country: string; +} export default function OrganizationSettingsPage() { - const queryClient = useQueryClient(); - const [isEditing, setIsEditing] = useState(false); - const [error, setError] = useState(''); - const [success, setSuccess] = useState(''); - - const { data: organization, isLoading } = useQuery({ - queryKey: ['organization', 'current'], - queryFn: () => organizationsApi.getCurrent(), - }); - - const [formData, setFormData] = useState({ + const { user } = useAuth(); + const [organization, setOrganization] = useState(null); + const [formData, setFormData] = useState({ name: '', - contactEmail: '', - contactPhone: '', - address: { - street: '', - city: '', - postalCode: '', - country: '', - }, + siren: '', + eori: '', + contact_phone: '', + contact_email: '', + address_street: '', + address_city: '', + address_postal_code: '', + address_country: 'FR', }); + const [isLoading, setIsLoading] = useState(true); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); - const updateMutation = useMutation({ - mutationFn: (data: typeof formData) => organizationsApi.update(organization?.id || '', data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['organization'] }); - setSuccess('Organization updated successfully'); - setIsEditing(false); - setTimeout(() => setSuccess(''), 3000); - }, - onError: (err: any) => { - setError(err.response?.data?.message || 'Failed to update organization'); - }, - }); + useEffect(() => { + if (user?.organizationId) { + loadOrganization(); + } + }, [user?.organizationId]); - const handleEdit = () => { - if (organization) { + const loadOrganization = async () => { + try { + setIsLoading(true); + setError(null); + const org = await getOrganization(user!.organizationId); + setOrganization(org); setFormData({ - name: organization.name, - contactEmail: organization.contactEmail, - contactPhone: organization.contactPhone, - address: organization.address, + name: org.name, + siren: '', // TODO: Get from backend when available + eori: '', // TODO: Get from backend when available + contact_phone: org.contact_phone || '', + contact_email: org.contact_email || '', + address_street: org.address_street, + address_city: org.address_city, + address_postal_code: org.address_postal_code, + address_country: org.address_country, }); - setIsEditing(true); - setError(''); - setSuccess(''); + } catch (err) { + console.error('Failed to load organization:', err); + setError(err instanceof Error ? err.message : 'Erreur lors du chargement'); + } finally { + setIsLoading(false); } }; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - updateMutation.mutate(formData); + const handleChange = (field: keyof OrganizationForm, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + setSuccessMessage(null); }; const handleCancel = () => { - setIsEditing(false); - setError(''); + if (organization) { + setFormData({ + name: organization.name, + siren: '', + eori: '', + contact_phone: organization.contact_phone || '', + contact_email: organization.contact_email || '', + address_street: organization.address_street, + address_city: organization.address_city, + address_postal_code: organization.address_postal_code, + address_country: organization.address_country, + }); + setSuccessMessage(null); + setError(null); + } + }; + + const handleSave = async () => { + if (!user?.organizationId) return; + + try { + setIsSaving(true); + setError(null); + setSuccessMessage(null); + + // Update organization (excluding SIREN and EORI for now) + const updatedOrg = await updateOrganization(user.organizationId, { + name: formData.name, + contact_phone: formData.contact_phone, + contact_email: formData.contact_email, + address_street: formData.address_street, + address_city: formData.address_city, + address_postal_code: formData.address_postal_code, + address_country: formData.address_country, + }); + + setOrganization(updatedOrg); + setSuccessMessage('Informations sauvegardées avec succès'); + + // TODO: Save SIREN and EORI when backend supports them + if (formData.siren || formData.eori) { + console.log('SIREN/EORI will be saved when backend is updated:', { + siren: formData.siren, + eori: formData.eori, + }); + } + } catch (err) { + console.error('Failed to update organization:', err); + setError(err instanceof Error ? err.message : 'Erreur lors de la sauvegarde'); + } finally { + setIsSaving(false); + } }; if (isLoading) { return ( -
-
+
+
+
+

Chargement...

+
); } if (!organization) { return ( -
-

Organization not found

+
+
+

Erreur

+

{error || "Impossible de charger l'organisation"}

+
); } return ( -
-
-

Organization Settings

-

Manage your organization information

+
+ {/* Header */} +
+

Paramètres de l'organisation

+

Gérez les informations de votre organisation

- {success && ( -
-
{success}
-
- )} - - {error && ( -
-
{error}
-
- )} - -
-
-

Organization Details

- {!isEditing && ( - - )} -
- -
-
- {/* Basic Info */} -
-
- - {isEditing ? ( - setFormData({ ...formData, name: e.target.value })} - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.name}

- )} -
- -
- -

{organization.type.replace('_', ' ')}

-
- -
- - {isEditing ? ( - setFormData({ ...formData, contactEmail: e.target.value })} - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.contactEmail}

- )} -
- -
- - {isEditing ? ( - setFormData({ ...formData, contactPhone: e.target.value })} - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.contactPhone}

- )} -
-
- - {/* Address */} -
-

Address

-
-
- - {isEditing ? ( - - setFormData({ - ...formData, - address: { - ...formData.address, - street: e.target.value, - }, - }) - } - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.address.street}

- )} -
- -
- - {isEditing ? ( - - setFormData({ - ...formData, - address: { - ...formData.address, - city: e.target.value, - }, - }) - } - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.address.city}

- )} -
- -
- - {isEditing ? ( - - setFormData({ - ...formData, - address: { - ...formData.address, - postalCode: e.target.value, - }, - }) - } - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.address.postalCode}

- )} -
- -
- - {isEditing ? ( - - setFormData({ - ...formData, - address: { - ...formData.address, - country: e.target.value, - }, - }) - } - className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm px-3 py-2 border" - required - /> - ) : ( -

{organization.address.country}

- )} -
-
-
- - {isEditing && ( -
- - -
- )} + {/* Success Message */} + {successMessage && ( +
+
+ +

{successMessage}

- +
+ )} + + {/* Error Message */} + {error && ( +
+
+ +

{error}

+
+
+ )} + + {/* Form */} +
+
+

Informations

+ +
+ {/* Nom de la société */} +
+ + handleChange('name', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Xpeditis" + required + /> +
+ + {/* SIREN */} +
+ + handleChange('siren', e.target.value.replace(/\D/g, '').slice(0, 9))} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="123 456 789" + maxLength={9} + /> +

9 chiffres

+
+ + {/* Numéro EORI */} +
+ + handleChange('eori', e.target.value.toUpperCase())} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="FR123456789" + maxLength={17} + /> +

Code pays (2 lettres) + numéro unique (max 15 caractères)

+
+ + {/* Téléphone */} +
+ + handleChange('contact_phone', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="06 80 18 28 12" + /> +
+ + {/* Email */} +
+ + handleChange('contact_email', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="contact@xpeditis.com" + /> +
+ + {/* Divider */} +
+

Adresse

+
+ + {/* Rue */} +
+ + handleChange('address_street', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="123 Rue de la Paix" + required + /> +
+ + {/* Ville et Code postal */} +
+
+ + handleChange('address_postal_code', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="75001" + required + /> +
+
+ + handleChange('address_city', e.target.value)} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Paris" + required + /> +
+
+ + {/* Pays */} +
+ + +
+
+
+ + {/* Actions */} +
+ + +
+ + {/* Info Note */} + {(formData.siren || formData.eori) && ( +
+
+ ℹ️ +
+

Note importante

+

+ Les champs SIREN et EORI seront sauvegardés une fois que le backend sera mis à jour pour les + supporter. Pour l'instant, seules les autres informations seront enregistrées. +

+
+
+
+ )}
); }