Add folder modal

This commit is contained in:
omaroughriss 2025-09-30 17:57:28 +02:00
parent 213eac3fda
commit 981da668b7
2 changed files with 665 additions and 0 deletions

244
components/FolderModal.css Normal file
View File

@ -0,0 +1,244 @@
/* Folder Modal Styles */
.folder-container {
padding: 1.5rem;
max-height: 70vh;
overflow-y: auto;
}
.folder-form {
display: flex;
flex-direction: column;
gap: 2rem;
}
/* Form Sections */
.form-section {
display: flex;
flex-direction: column;
gap: 1rem;
}
.section-title {
font-size: 1.125rem;
font-weight: 600;
color: #374151;
margin: 0 0 0.5rem 0;
padding-bottom: 0.5rem;
border-bottom: 2px solid #e5e7eb;
}
/* Form Layout */
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-field label {
font-size: 0.875rem;
font-weight: 500;
color: #374151;
}
.required {
color: #dc2626;
}
/* Form Inputs */
.form-field input,
.form-field textarea,
.form-field select {
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
transition: border-color 0.2s, box-shadow 0.2s;
background-color: white;
}
.form-field input:focus,
.form-field textarea:focus,
.form-field select:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.form-field input:disabled,
.form-field textarea:disabled,
.form-field select:disabled {
background-color: #f9fafb;
color: #6b7280;
cursor: not-allowed;
}
.form-field input::placeholder,
.form-field textarea::placeholder {
color: #9ca3af;
}
/* Tag System */
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
.tag-item {
display: flex;
align-items: center;
gap: 0.5rem;
background-color: #eff6ff;
color: #1d4ed8;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
border: 1px solid #bfdbfe;
}
.tag-remove {
display: flex;
align-items: center;
justify-content: center;
width: 1.25rem;
height: 1.25rem;
border: none;
background: none;
color: #1d4ed8;
cursor: pointer;
border-radius: 50%;
font-size: 1rem;
line-height: 1;
transition: background-color 0.2s;
}
.tag-remove:hover {
background-color: #dbeafe;
}
.tag-input-container {
display: flex;
gap: 0.5rem;
align-items: flex-end;
}
.tag-input-container input {
flex: 1;
}
.btn-add-tag {
padding: 0.75rem 1rem;
background-color: #3b82f6;
color: white;
border: none;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
white-space: nowrap;
}
.btn-add-tag:hover {
background-color: #2563eb;
}
/* Form Actions */
.form-actions {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding-top: 1rem;
border-top: 1px solid #e5e7eb;
margin-top: 1rem;
}
.btn-cancel {
padding: 0.75rem 1.5rem;
background-color: white;
color: #374151;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-cancel:hover {
background-color: #f9fafb;
border-color: #9ca3af;
}
.btn-submit {
padding: 0.75rem 1.5rem;
background-color: #059669;
color: white;
border: none;
border-radius: 0.5rem;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-submit:hover {
background-color: #047857;
}
.btn-submit:disabled {
background-color: #9ca3af;
cursor: not-allowed;
}
/* Responsive Design */
@media (max-width: 768px) {
.folder-container {
padding: 1rem;
}
.form-row {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column-reverse;
}
.btn-cancel,
.btn-submit {
width: 100%;
justify-content: center;
}
.tag-input-container {
flex-direction: column;
align-items: stretch;
}
}
/* Custom scrollbar for the container */
.folder-container::-webkit-scrollbar {
width: 6px;
}
.folder-container::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 3px;
}
.folder-container::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 3px;
}
.folder-container::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}

421
components/FolderModal.tsx Normal file
View File

@ -0,0 +1,421 @@
import React, { useState, memo } from 'react';
import Modal from './ui/modal/Modal';
import './FolderModal.css';
import type { FolderData } from '../lib/4nk/models/FolderData';
interface FolderModalProps {
folder?: FolderData;
onSave?: (folderData: FolderData) => void;
onCancel?: () => void;
readOnly?: boolean;
isOpen: boolean;
onClose: () => void;
}
const defaultFolder: FolderData = {
folderNumber: '',
name: '',
deedType: '',
description: '',
archived_description: '',
status: 'active',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
customers: [],
documents: [],
motes: [],
stakeholders: []
};
function FolderModal({
folder = defaultFolder,
onSave,
onCancel,
readOnly = false,
isOpen,
onClose
}: FolderModalProps) {
const [folderData, setFolderData] = useState<FolderData>(folder);
const [currentCustomer, setCurrentCustomer] = useState<string>('');
const [currentStakeholder, setCurrentStakeholder] = useState<string>('');
const [currentMote, setCurrentMote] = useState<string>('');
if (!isOpen) return null;
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFolderData(prev => ({
...prev,
[name]: value
}));
};
const addCustomer = () => {
if (currentCustomer.trim() && !folderData.customers.includes(currentCustomer.trim())) {
setFolderData(prev => ({
...prev,
customers: [...prev.customers, currentCustomer.trim()]
}));
setCurrentCustomer('');
}
};
const removeCustomer = (customer: string) => {
setFolderData(prev => ({
...prev,
customers: prev.customers.filter(c => c !== customer)
}));
};
const addStakeholder = () => {
if (currentStakeholder.trim() && !folderData.stakeholders.includes(currentStakeholder.trim())) {
setFolderData(prev => ({
...prev,
stakeholders: [...prev.stakeholders, currentStakeholder.trim()]
}));
setCurrentStakeholder('');
}
};
const removeStakeholder = (stakeholder: string) => {
setFolderData(prev => ({
...prev,
stakeholders: prev.stakeholders.filter(s => s !== stakeholder)
}));
};
const addMote = () => {
if (currentMote.trim() && !folderData.motes.includes(currentMote.trim())) {
setFolderData(prev => ({
...prev,
motes: [...prev.motes, currentMote.trim()]
}));
setCurrentMote('');
}
};
const removeMote = (mote: string) => {
setFolderData(prev => ({
...prev,
motes: prev.motes.filter(m => m !== mote)
}));
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (onSave) {
onSave({
...folderData,
updated_at: new Date().toISOString()
});
}
};
const handleCancel = () => {
if (onCancel) {
onCancel();
} else {
onClose();
}
};
return (
<Modal isOpen={isOpen} onClose={onClose} title="Créer un nouveau dossier" size="lg">
<div className="folder-container">
<form className="folder-form" onSubmit={handleSubmit}>
<div className="form-section">
<h3 className="section-title">Informations principales</h3>
<div className="form-row">
<div className="form-field">
<label>
Numéro de dossier <span className="required">*</span>
</label>
<input
type="text"
name="folderNumber"
value={folderData.folderNumber}
onChange={handleInputChange}
required
disabled={readOnly}
placeholder="ex: DOC-2025-001"
/>
</div>
<div className="form-field">
<label>
Nom <span className="required">*</span>
</label>
<input
type="text"
name="name"
value={folderData.name}
onChange={handleInputChange}
required
disabled={readOnly}
placeholder="Nom du dossier"
/>
</div>
</div>
<div className="form-row">
<div className="form-field">
<label>
Type d'acte <span className="required">*</span>
</label>
<select
name="deedType"
value={folderData.deedType}
onChange={handleInputChange}
required
disabled={readOnly}
>
<option value="">Sélectionnez le type d'acte</option>
<option value="vente">Vente</option>
<option value="achat">Achat</option>
<option value="succession">Succession</option>
<option value="donation">Donation</option>
<option value="hypotheque">Hypothèque</option>
<option value="bail">Bail</option>
<option value="autre">Autre</option>
</select>
</div>
<div className="form-field">
<label>
Statut <span className="required">*</span>
</label>
<select
name="status"
value={folderData.status}
onChange={handleInputChange}
required
disabled={readOnly}
>
<option value="active">Actif</option>
<option value="pending">En attente</option>
<option value="completed">Complété</option>
<option value="archived">Archivé</option>
</select>
</div>
</div>
<div className="form-field">
<label>Description</label>
<textarea
name="description"
value={folderData.description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Description du dossier"
rows={3}
/>
</div>
{folderData.status === 'archived' && (
<div className="form-field">
<label>Description d'archivage</label>
<textarea
name="archived_description"
value={folderData.archived_description}
onChange={handleInputChange}
disabled={readOnly}
placeholder="Raison d'archivage"
rows={2}
/>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Clients</h3>
<div className="tag-list">
{folderData.customers.map((customer, index) => (
<div key={index} className="tag-item">
<span>{customer}</span>
{!readOnly && (
<button
type="button"
className="tag-remove"
onClick={() => removeCustomer(customer)}
aria-label="Supprimer ce client"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentCustomer}
onChange={(e) => setCurrentCustomer(e.target.value)}
placeholder="Ajouter un client"
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addCustomer();
}
}}
/>
<button
type="button"
className="btn-add-tag"
onClick={addCustomer}
>
Ajouter
</button>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Parties prenantes</h3>
<div className="tag-list">
{folderData.stakeholders.map((stakeholder, index) => (
<div key={index} className="tag-item">
<span>{stakeholder}</span>
{!readOnly && (
<button
type="button"
className="tag-remove"
onClick={() => removeStakeholder(stakeholder)}
aria-label="Supprimer cette partie prenante"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentStakeholder}
onChange={(e) => setCurrentStakeholder(e.target.value)}
placeholder="Ajouter une partie prenante"
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addStakeholder();
}
}}
/>
<button
type="button"
className="btn-add-tag"
onClick={addStakeholder}
>
Ajouter
</button>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Notes</h3>
<div className="tag-list">
{folderData.motes.map((mote, index) => (
<div key={index} className="tag-item">
<span>{mote}</span>
{!readOnly && (
<button
type="button"
className="tag-remove"
onClick={() => removeMote(mote)}
aria-label="Supprimer cette note"
>
×
</button>
)}
</div>
))}
</div>
{!readOnly && (
<div className="form-field tag-input-container">
<input
type="text"
value={currentMote}
onChange={(e) => setCurrentMote(e.target.value)}
placeholder="Ajouter une note"
onKeyPress={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
addMote();
}
}}
/>
<button
type="button"
className="btn-add-tag"
onClick={addMote}
>
Ajouter
</button>
</div>
)}
</div>
<div className="form-section">
<h3 className="section-title">Informations système</h3>
<div className="form-row">
<div className="form-field">
<label>Créé le</label>
<input
type="text"
value={new Date(folderData.created_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
<div className="form-field">
<label>Dernière mise à jour</label>
<input
type="text"
value={new Date(folderData.updated_at).toLocaleDateString('fr-FR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
})}
disabled
readOnly
/>
</div>
</div>
</div>
<div className="form-actions">
<button
type="button"
className="btn-cancel"
onClick={handleCancel}
>
Annuler
</button>
<button
type="submit"
className="btn-submit"
disabled={readOnly}
>
Enregistrer
</button>
</div>
</form>
</div>
</Modal>
);
};
FolderModal.displayName = 'FolderModal';
export default memo(FolderModal);