xpeditis2.0/apps/frontend/app/dashboard/settings/organization/page.tsx
2025-11-04 23:19:25 +01:00

380 lines
14 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
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 { user } = useAuth();
const [organization, setOrganization] = useState<OrganizationResponse | null>(null);
const [formData, setFormData] = useState<OrganizationForm>({
name: '',
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<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
useEffect(() => {
if (user?.organizationId) {
loadOrganization();
}
}, [user?.organizationId]);
const loadOrganization = async () => {
try {
setIsLoading(true);
setError(null);
const org = await getOrganization(user!.organizationId);
setOrganization(org);
setFormData({
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,
});
} catch (err) {
console.error('Failed to load organization:', err);
setError(err instanceof Error ? err.message : 'Erreur lors du chargement');
} finally {
setIsLoading(false);
}
};
const handleChange = (field: keyof OrganizationForm, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
setSuccessMessage(null);
};
const handleCancel = () => {
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 (
<div className="flex items-center justify-center min-h-screen">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-4 border-blue-600 mb-4"></div>
<p className="text-gray-600">Chargement...</p>
</div>
</div>
);
}
if (!organization) {
return (
<div className="max-w-4xl mx-auto">
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
<h3 className="text-lg font-semibold text-red-900 mb-2">Erreur</h3>
<p className="text-red-700">{error || "Impossible de charger l'organisation"}</p>
</div>
</div>
);
}
return (
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900">Paramètres de l'organisation</h1>
<p className="text-gray-600 mt-2">Gérez les informations de votre organisation</p>
</div>
{/* Success Message */}
{successMessage && (
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4">
<div className="flex items-center">
<span className="text-green-600 text-xl mr-3">✓</span>
<p className="text-green-800 font-medium">{successMessage}</p>
</div>
</div>
)}
{/* Error Message */}
{error && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
<div className="flex items-center">
<span className="text-red-600 text-xl mr-3">✕</span>
<p className="text-red-800 font-medium">{error}</p>
</div>
</div>
)}
{/* Form */}
<div className="bg-white rounded-lg shadow-md">
<div className="p-8">
<h2 className="text-xl font-semibold text-gray-900 mb-6">Informations</h2>
<div className="space-y-6">
{/* Nom de la société */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Nom de la société <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.name}
onChange={e => 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
/>
</div>
{/* SIREN */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
SIREN
<span className="ml-2 text-xs text-gray-500">(Système d'Identification du Répertoire des Entreprises)</span>
</label>
<input
type="text"
value={formData.siren}
onChange={e => 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}
/>
<p className="mt-1 text-xs text-gray-500">9 chiffres</p>
</div>
{/* Numéro EORI */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Numéro EORI
<span className="ml-2 text-xs text-gray-500">(Economic Operators Registration and Identification)</span>
</label>
<input
type="text"
value={formData.eori}
onChange={e => 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}
/>
<p className="mt-1 text-xs text-gray-500">Code pays (2 lettres) + numéro unique (max 15 caractères)</p>
</div>
{/* Téléphone */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Téléphone</label>
<input
type="tel"
value={formData.contact_phone}
onChange={e => 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"
/>
</div>
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input
type="email"
value={formData.contact_email}
onChange={e => 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"
/>
</div>
{/* Divider */}
<div className="border-t border-gray-200 pt-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Adresse</h3>
</div>
{/* Rue */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Rue <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.address_street}
onChange={e => 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
/>
</div>
{/* Ville et Code postal */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Code postal <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.address_postal_code}
onChange={e => 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
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Ville <span className="text-red-500">*</span>
</label>
<input
type="text"
value={formData.address_city}
onChange={e => 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
/>
</div>
</div>
{/* Pays */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Pays <span className="text-red-500">*</span>
</label>
<select
value={formData.address_country}
onChange={e => handleChange('address_country', 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"
required
>
<option value="FR">France</option>
<option value="BE">Belgique</option>
<option value="DE">Allemagne</option>
<option value="ES">Espagne</option>
<option value="IT">Italie</option>
<option value="NL">Pays-Bas</option>
<option value="GB">Royaume-Uni</option>
<option value="US">États-Unis</option>
<option value="CN">Chine</option>
</select>
</div>
</div>
</div>
{/* Actions */}
<div className="bg-gray-50 px-8 py-4 border-t border-gray-200 flex items-center justify-end space-x-4">
<button
type="button"
onClick={handleCancel}
disabled={isSaving}
className="px-6 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Annuler
</button>
<button
type="button"
onClick={handleSave}
disabled={isSaving || !formData.name || !formData.address_street}
className="px-6 py-2 text-sm font-medium text-white bg-blue-600 rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center"
>
{isSaving ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Enregistrement...
</>
) : (
'Enregistrer'
)}
</button>
</div>
</div>
{/* Info Note */}
{(formData.siren || formData.eori) && (
<div className="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
<div className="flex items-start">
<span className="text-blue-600 text-xl mr-3"></span>
<div>
<p className="text-blue-900 font-medium mb-1">Note importante</p>
<p className="text-blue-800 text-sm">
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.
</p>
</div>
</div>
</div>
)}
</div>
);
}