243 lines
11 KiB
TypeScript
243 lines
11 KiB
TypeScript
import React, { useEffect, useState, memo } from 'react';
|
||
import Modal from './Modal';
|
||
import type { FolderData } from '@/lib/4nk/models/FolderData';
|
||
import { MemberAutocomplete } from '../ui/member-autocomplete';
|
||
|
||
type FolderType = 'contrat' | 'projet' | 'rapport' | 'finance' | 'rh' | 'marketing' | 'autre';
|
||
|
||
interface FolderModalProps {
|
||
folder?: FolderData;
|
||
// --- MODIFIÉ ---
|
||
onSave?: (folderData: FolderData, selectedMembers: string[]) => void;
|
||
onCancel?: () => void;
|
||
readOnly?: boolean;
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
folderType?: FolderType;
|
||
// --- NOUVEAU ---
|
||
members?: string[]; // Liste des membres disponibles
|
||
renderExtraFields?: (
|
||
folderData: FolderData,
|
||
setFolderData: React.Dispatch<React.SetStateAction<FolderData>>
|
||
) => React.ReactNode;
|
||
}
|
||
|
||
const defaultFolder: FolderData = {
|
||
folderNumber: '',
|
||
name: '',
|
||
description: '',
|
||
created_at: new Date().toISOString(),
|
||
updated_at: new Date().toISOString(),
|
||
notes: [],
|
||
messages: [],
|
||
messages_owner: []
|
||
};
|
||
|
||
function capitalize(s?: string) {
|
||
if (!s) return '';
|
||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||
}
|
||
|
||
// Mapping des couleurs
|
||
const folderColors: Record<FolderType, { bg: string; border: string; focus: string; button: string }> = {
|
||
contrat: { bg: 'bg-blue-50 dark:bg-blue-900', border: 'border-blue-300 dark:border-blue-700', focus: 'focus:ring-blue-400 dark:focus:ring-blue-600', button: 'bg-blue-500 hover:bg-blue-600' },
|
||
projet: { bg: 'bg-green-50 dark:bg-green-900', border: 'border-green-300 dark:border-green-700', focus: 'focus:ring-green-400 dark:focus:ring-green-600', button: 'bg-green-500 hover:bg-green-600' },
|
||
rapport: { bg: 'bg-yellow-50 dark:bg-yellow-900', border: 'border-yellow-300 dark:border-yellow-700', focus: 'focus:ring-yellow-400 dark:focus:ring-yellow-600', button: 'bg-yellow-500 hover:bg-yellow-600' },
|
||
finance: { bg: 'bg-indigo-50 dark:bg-indigo-900', border: 'border-indigo-300 dark:border-indigo-700', focus: 'focus:ring-indigo-400 dark:focus:ring-indigo-600', button: 'bg-indigo-500 hover:bg-indigo-600' },
|
||
rh: { bg: 'bg-pink-50 dark:bg-pink-900', border: 'border-pink-300 dark:border-pink-700', focus: 'focus:ring-pink-400 dark:focus:ring-pink-600', button: 'bg-pink-500 hover:bg-pink-600' },
|
||
marketing: { bg: 'bg-purple-50 dark:bg-purple-900', border: 'border-purple-300 dark:border-purple-700', focus: 'focus:ring-purple-400 dark:focus:ring-purple-600', button: 'bg-purple-500 hover:bg-purple-600' },
|
||
autre: { bg: 'bg-gray-50 dark:bg-gray-800', border: 'border-gray-300 dark:border-gray-600', focus: 'focus:ring-blue-400 dark:focus:ring-blue-600', button: 'bg-blue-500 hover:bg-blue-600' },
|
||
};
|
||
|
||
function FolderModal({
|
||
folder = defaultFolder,
|
||
onSave,
|
||
onCancel,
|
||
readOnly = false,
|
||
isOpen,
|
||
onClose,
|
||
folderType = 'autre',
|
||
members = [],
|
||
renderExtraFields
|
||
}: FolderModalProps) {
|
||
const [folderData, setFolderData] = useState<FolderData>({ ...defaultFolder, ...folder });
|
||
const [currentNote, setCurrentNote] = useState('');
|
||
// --- NOUVEAU: État pour les membres sélectionnés ---
|
||
const [selectedMembers, setSelectedMembers] = useState<string[]>([]);
|
||
|
||
useEffect(() => {
|
||
if (isOpen) {
|
||
setFolderData({ ...defaultFolder, ...(folder || {}) });
|
||
setCurrentNote('');
|
||
setSelectedMembers([]); // <-- MODIFIÉ: Réinitialise les membres
|
||
}
|
||
}, [isOpen, folder]);
|
||
|
||
const handleInputChange = (
|
||
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||
) => {
|
||
const { name, value } = e.target;
|
||
setFolderData(prev => ({ ...(prev as any), [name]: value } as FolderData));
|
||
};
|
||
|
||
const handleMemberToggle = (memberId: string) => {
|
||
setSelectedMembers(prev =>
|
||
prev.includes(memberId)
|
||
? prev.filter(id => id !== memberId)
|
||
: [...prev, memberId]
|
||
);
|
||
};
|
||
|
||
const addNote = () => {
|
||
const v = currentNote.trim();
|
||
if (!v) return;
|
||
setFolderData(prev => ({ ...prev, notes: [...(prev.notes || []), v] }));
|
||
setCurrentNote('');
|
||
};
|
||
|
||
const removeNote = (note: string) => {
|
||
setFolderData(prev => ({ ...prev, notes: (prev.notes || []).filter(n => n !== note) }));
|
||
};
|
||
|
||
const handleSubmit = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
onSave?.({ ...folderData, updated_at: new Date().toISOString() }, selectedMembers);
|
||
onClose();
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
if (onCancel) onCancel();
|
||
else onClose();
|
||
};
|
||
|
||
const colors = folderColors[folderType];
|
||
const title = `Créer un dossier ${capitalize(folderType)}`;
|
||
|
||
return (
|
||
<Modal isOpen={isOpen} onClose={onClose} title={title} size="lg">
|
||
<div className={`p-6 rounded-lg space-y-8 ${colors.bg} text-gray-900 dark:text-gray-100`}>
|
||
<form className="space-y-8" onSubmit={handleSubmit}>
|
||
{/* Informations principales */}
|
||
<div className="space-y-6">
|
||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Informations principales</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{['folderNumber', 'name'].map((field) => (
|
||
<div className="relative" key={field}>
|
||
<input
|
||
type="text"
|
||
name={field}
|
||
value={folderData[field as keyof FolderData] || ''}
|
||
onChange={handleInputChange}
|
||
required
|
||
disabled={readOnly}
|
||
placeholder=" "
|
||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus}`}
|
||
/>
|
||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||
{field === 'folderNumber' ? 'Numéro de dossier *' : 'Nom *'}
|
||
</label>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Description */}
|
||
<div className="relative">
|
||
<textarea
|
||
name="description"
|
||
value={folderData.description || ''}
|
||
onChange={handleInputChange}
|
||
disabled={readOnly}
|
||
placeholder=" "
|
||
rows={3}
|
||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus} resize-none`}
|
||
/>
|
||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||
Description
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Membres */}
|
||
{!readOnly && (
|
||
<div className="space-y-4">
|
||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Membres</h3>
|
||
<MemberAutocomplete
|
||
allMembers={members}
|
||
selectedMembers={selectedMembers}
|
||
onChange={setSelectedMembers} // On passe le setter de l'état
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{/* Notes */}
|
||
<div className="space-y-4">
|
||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Notes</h3>
|
||
<div className="space-y-2">
|
||
{(folderData.notes || []).map((note, index) => (
|
||
<div key={index} className="flex items-center justify-between bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 px-3 py-1 rounded-md">
|
||
<span>{note}</span>
|
||
{!readOnly && (
|
||
<button type="button" className="text-red-500 hover:text-red-700 ml-2" onClick={() => removeNote(note)}>×</button>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
{!readOnly && (
|
||
<div className="flex space-x-2">
|
||
<input
|
||
type="text"
|
||
value={currentNote}
|
||
onChange={(e) => setCurrentNote(e.target.value)}
|
||
placeholder="Ajouter une note"
|
||
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addNote(); } }}
|
||
className={`flex-1 border rounded-md px-3 py-2 ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-400 border ${colors.border} focus:outline-none ${colors.focus}`}
|
||
/>
|
||
<button type="button" className={`px-4 py-2 text-white rounded-md ${colors.button}`} onClick={addNote}>Ajouter</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Informations système */}
|
||
<div className="space-y-4">
|
||
<h3 className="text-xl font-semibold text-gray-900 dark:text-gray-100">Informations système</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{['created_at', 'updated_at'].map((field) => {
|
||
const value = new Date(folderData[field as keyof FolderData] as string).toLocaleString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||
const label = field === 'created_at' ? 'Créé le' : 'Dernière mise à jour';
|
||
return (
|
||
<div className="relative" key={field}>
|
||
<input
|
||
type="text"
|
||
value={value}
|
||
disabled
|
||
readOnly
|
||
placeholder=" "
|
||
className={`peer block w-full px-3 pt-5 pb-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 placeholder-transparent border ${colors.border} focus:outline-none ${colors.focus}`}
|
||
/>
|
||
<label className="absolute left-3 top-2.5 text-gray-500 dark:text-gray-400 text-sm transition-all peer-placeholder-shown:top-5 peer-placeholder-shown:text-base peer-focus:top-2.5 peer-focus:text-sm">
|
||
{label}
|
||
</label>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Actions */}
|
||
<div className="flex justify-end space-x-3">
|
||
<button type="button" className={`px-4 py-2 rounded-md ${colors.bg} text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:hover:bg-gray-700`} onClick={handleCancel}>Annuler</button>
|
||
<button type="submit" className={`px-4 py-2 text-white rounded-md ${colors.button} disabled:opacity-50`} disabled={readOnly}>Enregistrer</button>
|
||
</div>
|
||
|
||
{/* Champs spécifiques injectés */}
|
||
{renderExtraFields && renderExtraFields(folderData, setFolderData)}
|
||
|
||
</form>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
}
|
||
|
||
FolderModal.displayName = 'FolderModal';
|
||
export default memo(FolderModal);
|