380 lines
14 KiB
TypeScript
380 lines
14 KiB
TypeScript
'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>
|
||
);
|
||
}
|