Compare commits
No commits in common. "981da668b7bb3a2faffff788525a0d31df0c29f2" and "9057b7af1e031b42e463a0a37e5996a7b744d1af" have entirely different histories.
981da668b7
...
9057b7af1e
@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { Card, CardContent } from "@/components/ui/card"
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
@ -48,14 +48,6 @@ import {
|
|||||||
Archive,
|
Archive,
|
||||||
FileCheck,
|
FileCheck,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import FolderModal from "@/components/FolderModal"
|
|
||||||
import type { FolderData as SDKFolderData, FolderCreated } from "@/lib/4nk/models/FolderData"
|
|
||||||
import { FolderPrivateFields, setDefaultFolderRoles } from "@/lib/4nk/models/FolderData"
|
|
||||||
import AuthModal from "@/components/4nk/AuthModal"
|
|
||||||
import Iframe from "@/components/4nk/Iframe"
|
|
||||||
import MessageBus from "@/lib/4nk/MessageBus"
|
|
||||||
import EventBus from "@/lib/4nk/EventBus"
|
|
||||||
import UserStore from "@/lib/4nk/UserStore"
|
|
||||||
|
|
||||||
interface FolderData {
|
interface FolderData {
|
||||||
id: number
|
id: number
|
||||||
@ -164,16 +156,6 @@ export default function FoldersPage() {
|
|||||||
const [showFilters, setShowFilters] = useState(false)
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
const [currentPath, setCurrentPath] = useState<string[]>(["Racine"])
|
const [currentPath, setCurrentPath] = useState<string[]>(["Racine"])
|
||||||
const [actionModal, setActionModal] = useState<ActionModal>({ type: null, folder: null, folders: [] })
|
const [actionModal, setActionModal] = useState<ActionModal>({ type: null, folder: null, folders: [] })
|
||||||
const [showCreateFolderModal, setShowCreateFolderModal] = useState(false)
|
|
||||||
|
|
||||||
// 4NK Integration states
|
|
||||||
const [isConnected, setIsConnected] = useState(false)
|
|
||||||
const [showAuthModal, setShowAuthModal] = useState(false)
|
|
||||||
const [processes, setProcesses] = useState<any>(null)
|
|
||||||
const [myProcesses, setMyProcesses] = useState<string[]>([])
|
|
||||||
const [userPairingId, setUserPairingId] = useState<string | null>(null)
|
|
||||||
const [pairingIdInitialized, setPairingIdInitialized] = useState(false)
|
|
||||||
const iframeUrl = 'https://dev3.4nkweb.com'
|
|
||||||
|
|
||||||
// Modal states
|
// Modal states
|
||||||
const [inviteMessage, setInviteMessage] = useState("")
|
const [inviteMessage, setInviteMessage] = useState("")
|
||||||
@ -333,102 +315,6 @@ export default function FoldersPage() {
|
|||||||
{ id: "gray", name: "Gris", class: "text-gray-600 bg-gray-100" },
|
{ id: "gray", name: "Gris", class: "text-gray-600 bg-gray-100" },
|
||||||
]
|
]
|
||||||
|
|
||||||
// 4NK Integration useEffects
|
|
||||||
useEffect(() => {
|
|
||||||
const userStore = UserStore.getInstance();
|
|
||||||
const connected = userStore.isConnected();
|
|
||||||
const pairingId = userStore.getUserPairingId();
|
|
||||||
|
|
||||||
console.log('Initialisation 4NK:', { connected, pairingId });
|
|
||||||
|
|
||||||
setIsConnected(connected);
|
|
||||||
setUserPairingId(pairingId);
|
|
||||||
setPairingIdInitialized(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isConnected) {
|
|
||||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
||||||
messageBus.isReady().then(() => {
|
|
||||||
messageBus.getProcesses().then((processes: any) => {
|
|
||||||
setProcesses(processes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isConnected]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isConnected && processes !== null) {
|
|
||||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
||||||
messageBus.isReady().then(() => {
|
|
||||||
messageBus.getMyProcesses().then((res: string[]) => {
|
|
||||||
setMyProcesses(res);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isConnected, processes]);
|
|
||||||
|
|
||||||
// useEffect pour écouter les changements dans sessionStorage et récupérer le userPairingId
|
|
||||||
useEffect(() => {
|
|
||||||
const handleStorageChange = (e: StorageEvent) => {
|
|
||||||
console.log('Storage change détecté:', e.key, e.newValue ? 'ajouté' : 'supprimé');
|
|
||||||
|
|
||||||
// Si un token d'accès vient d'être ajouté
|
|
||||||
if (e.key === 'accessToken' && e.newValue) {
|
|
||||||
console.log('Token d\'accès détecté, récupération du userPairingId...');
|
|
||||||
|
|
||||||
// Attendre un peu que les deux tokens soient bien en place
|
|
||||||
setTimeout(() => {
|
|
||||||
const userStore = UserStore.getInstance();
|
|
||||||
if (userStore.isConnected() && !userStore.getUserPairingId()) {
|
|
||||||
console.log('Tokens confirmés, récupération userPairingId...');
|
|
||||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
||||||
messageBus.isReady().then(() => {
|
|
||||||
messageBus.getUserPairingId().then((retrievedPairingId: string) => {
|
|
||||||
console.log('UserPairingId récupéré via storage event:', retrievedPairingId);
|
|
||||||
userStore.pair(retrievedPairingId);
|
|
||||||
setUserPairingId(retrievedPairingId);
|
|
||||||
setIsConnected(true);
|
|
||||||
showNotification("success", `UserPairingId récupéré: ${retrievedPairingId.substring(0, 8)}...`);
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Erreur récupération userPairingId via storage:', error);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Écouter les changements de sessionStorage
|
|
||||||
window.addEventListener('storage', handleStorageChange);
|
|
||||||
|
|
||||||
// Vérification initiale au chargement
|
|
||||||
const userStore = UserStore.getInstance();
|
|
||||||
if (userStore.isConnected()) {
|
|
||||||
const existingPairingId = userStore.getUserPairingId();
|
|
||||||
if (existingPairingId) {
|
|
||||||
console.log('UserPairingId existant trouvé:', existingPairingId);
|
|
||||||
setUserPairingId(existingPairingId);
|
|
||||||
setIsConnected(true);
|
|
||||||
} else {
|
|
||||||
console.log('Tokens existants mais pas de userPairingId, récupération...');
|
|
||||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
||||||
messageBus.isReady().then(() => {
|
|
||||||
messageBus.getUserPairingId().then((retrievedPairingId: string) => {
|
|
||||||
console.log('UserPairingId récupéré au chargement:', retrievedPairingId);
|
|
||||||
userStore.pair(retrievedPairingId);
|
|
||||||
setUserPairingId(retrievedPairingId);
|
|
||||||
setIsConnected(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('storage', handleStorageChange);
|
|
||||||
};
|
|
||||||
}, []); // Une seule fois au montage
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Simuler le chargement des dossiers
|
// Simuler le chargement des dossiers
|
||||||
const loadFolders = () => {
|
const loadFolders = () => {
|
||||||
@ -716,75 +602,6 @@ export default function FoldersPage() {
|
|||||||
setTimeout(() => setNotification(null), 3000)
|
setTimeout(() => setNotification(null), 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4NK Authentication handlers
|
|
||||||
const handleLogin = useCallback(() => {
|
|
||||||
setShowAuthModal(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLogout = useCallback(() => {
|
|
||||||
UserStore.getInstance().disconnect();
|
|
||||||
setIsConnected(false);
|
|
||||||
setProcesses(null);
|
|
||||||
setMyProcesses([]);
|
|
||||||
setUserPairingId(null);
|
|
||||||
|
|
||||||
// Émettre un événement pour vider les messages locaux
|
|
||||||
EventBus.getInstance().emit('CLEAR_CONSOLE');
|
|
||||||
|
|
||||||
showNotification("info", "Déconnexion réussie");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAuthConnect = useCallback(() => {
|
|
||||||
setShowAuthModal(false);
|
|
||||||
console.log('Auth Connect - Connexion établie, le useEffect se chargera de récupérer le userPairingId');
|
|
||||||
showNotification("success", "Connexion 4NK réussie");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleAuthClose = useCallback(() => {
|
|
||||||
setShowAuthModal(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Debug function pour forcer la récupération du userPairingId
|
|
||||||
const handleForceGetPairingId = useCallback(() => {
|
|
||||||
console.log('Force récupération userPairingId - État actuel:', {
|
|
||||||
isConnected,
|
|
||||||
userPairingId,
|
|
||||||
userStoreConnected: UserStore.getInstance().isConnected(),
|
|
||||||
userStorePairingId: UserStore.getInstance().getUserPairingId()
|
|
||||||
});
|
|
||||||
|
|
||||||
// D'abord essayer de synchroniser depuis UserStore
|
|
||||||
const userStorePairingId = UserStore.getInstance().getUserPairingId();
|
|
||||||
if (userStorePairingId) {
|
|
||||||
console.log('Force - Synchronisation depuis UserStore:', userStorePairingId);
|
|
||||||
setUserPairingId(userStorePairingId);
|
|
||||||
showNotification("success", `UserPairingId synchronisé: ${userStorePairingId.substring(0, 8)}...`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sinon récupérer depuis MessageBus
|
|
||||||
if (isConnected) {
|
|
||||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
||||||
messageBus.isReady().then(() => {
|
|
||||||
console.log('Force - MessageBus prêt');
|
|
||||||
messageBus.getUserPairingId().then((retrievedPairingId: string) => {
|
|
||||||
console.log('Force - UserPairingId récupéré:', retrievedPairingId);
|
|
||||||
UserStore.getInstance().pair(retrievedPairingId);
|
|
||||||
setUserPairingId(retrievedPairingId);
|
|
||||||
showNotification("success", `UserPairingId récupéré: ${retrievedPairingId.substring(0, 8)}...`);
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Force - Erreur récupération userPairingId:', error);
|
|
||||||
showNotification("error", "Erreur lors de la récupération du userPairingId");
|
|
||||||
});
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Force - Erreur MessageBus isReady:', error);
|
|
||||||
showNotification("error", "MessageBus non prêt");
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
showNotification("error", "Vous devez être connecté à 4NK");
|
|
||||||
}
|
|
||||||
}, [isConnected, userPairingId, iframeUrl]);
|
|
||||||
|
|
||||||
// Fonction pour envoyer une notification dans le chat du dossier
|
// Fonction pour envoyer une notification dans le chat du dossier
|
||||||
const sendFolderChatNotification = (folderId: string, message: string, actionType: string) => {
|
const sendFolderChatNotification = (folderId: string, message: string, actionType: string) => {
|
||||||
const folderUsers = users.filter((user) => user.folderRoles[folderId])
|
const folderUsers = users.filter((user) => user.folderRoles[folderId])
|
||||||
@ -961,50 +778,12 @@ export default function FoldersPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleCreateFolder = () => {
|
const handleCreateFolder = () => {
|
||||||
setShowCreateFolderModal(true)
|
setFolderName("")
|
||||||
}
|
setFolderDescription("")
|
||||||
|
setFolderColor("blue")
|
||||||
const handleSaveNewFolder = useCallback((folderData: SDKFolderData) => {
|
setFolderTags("")
|
||||||
console.log('Debug - handleSaveNewFolder:', {
|
setFolderAccess("private")
|
||||||
isConnected,
|
setActionModal({ type: "create", folder: null, folders: [] })
|
||||||
userPairingId,
|
|
||||||
userPairingIdType: typeof userPairingId,
|
|
||||||
userStoreConnected: UserStore.getInstance().isConnected(),
|
|
||||||
userStorePairingId: UserStore.getInstance().getUserPairingId()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (userPairingId !== null && isConnected) {
|
|
||||||
const roles = setDefaultFolderRoles(userPairingId, [], []);
|
|
||||||
const folderPrivateFields = FolderPrivateFields;
|
|
||||||
|
|
||||||
MessageBus.getInstance(iframeUrl).createFolder(folderData, folderPrivateFields, roles).then((_folderCreated: FolderCreated) => {
|
|
||||||
MessageBus.getInstance(iframeUrl).notifyProcessUpdate(_folderCreated.processId, _folderCreated.process.states[0].state_id).then(() => {
|
|
||||||
MessageBus.getInstance(iframeUrl).validateState(_folderCreated.processId, _folderCreated.process.states[0].state_id).then((_updatedProcess: any) => {
|
|
||||||
MessageBus.getInstance(iframeUrl).getProcesses().then((processes: any) => {
|
|
||||||
setProcesses(processes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Erreur lors de la création du dossier 4NK:', error);
|
|
||||||
showNotification("error", "Erreur lors de la création du dossier");
|
|
||||||
});
|
|
||||||
|
|
||||||
setShowCreateFolderModal(false);
|
|
||||||
showNotification("success", `Dossier "${folderData.name}" créé avec succès sur 4NK`);
|
|
||||||
} else {
|
|
||||||
console.error('Conditions non remplies:', {
|
|
||||||
userPairingIdCheck: userPairingId !== null,
|
|
||||||
isConnectedCheck: isConnected,
|
|
||||||
actualUserPairingId: userPairingId,
|
|
||||||
actualIsConnected: isConnected
|
|
||||||
});
|
|
||||||
showNotification("error", `Vous devez être connecté à 4NK pour créer un dossier (Connected: ${isConnected}, PairingId: ${userPairingId ? 'OK' : 'NULL'})`);
|
|
||||||
}
|
|
||||||
}, [userPairingId, isConnected, iframeUrl]);
|
|
||||||
|
|
||||||
const handleCancelCreateFolder = () => {
|
|
||||||
setShowCreateFolderModal(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleToggleFavorite = (folderId: number) => {
|
const handleToggleFavorite = (folderId: number) => {
|
||||||
@ -1371,9 +1150,9 @@ export default function FoldersPage() {
|
|||||||
|
|
||||||
const getStorageIcon = (storageType: string) => {
|
const getStorageIcon = (storageType: string) => {
|
||||||
return storageType === "permanent" ? (
|
return storageType === "permanent" ? (
|
||||||
<Cloud className="h-4 w-4 text-blue-600" />
|
<Cloud className="h-4 w-4 text-blue-600" title="Stockage permanent" />
|
||||||
) : (
|
) : (
|
||||||
<HardDrive className="h-4 w-4 text-gray-600" />
|
<HardDrive className="h-4 w-4 text-gray-600" title="Stockage temporaire" />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1472,52 +1251,18 @@ export default function FoldersPage() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3">
|
<h1 className="text-2xl font-bold text-gray-900">Dossiers</h1>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Dossiers</h1>
|
<p className="text-gray-600 mt-1">Organisez vos documents par dossiers</p>
|
||||||
<Badge
|
|
||||||
variant={isConnected ? "default" : "secondary"}
|
|
||||||
className={isConnected ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-600"}
|
|
||||||
>
|
|
||||||
{isConnected ? "4NK Connecté" : "4NK Déconnecté"}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<p className="text-gray-600 mt-1">
|
|
||||||
Organisez vos documents par dossiers
|
|
||||||
{isConnected && userPairingId && (
|
|
||||||
<span className="text-sm text-green-600 ml-2">
|
|
||||||
• ID: {userPairingId.substring(0, 8)}...
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-3 mt-4 sm:mt-0">
|
<div className="flex items-center space-x-3 mt-4 sm:mt-0">
|
||||||
<Button variant="outline" size="sm">
|
<Button variant="outline" size="sm">
|
||||||
<Upload className="h-4 w-4 mr-2" />
|
<Upload className="h-4 w-4 mr-2" />
|
||||||
Importer
|
Importer
|
||||||
</Button>
|
</Button>
|
||||||
{isConnected ? (
|
<Button size="sm" onClick={handleCreateFolder}>
|
||||||
<>
|
<FolderPlus className="h-4 w-4 mr-2" />
|
||||||
<Button size="sm" onClick={handleCreateFolder}>
|
Nouveau dossier
|
||||||
<FolderPlus className="h-4 w-4 mr-2" />
|
</Button>
|
||||||
Nouveau dossier
|
|
||||||
</Button>
|
|
||||||
<Button variant="outline" size="sm" onClick={handleLogout}>
|
|
||||||
<X className="h-4 w-4 mr-2" />
|
|
||||||
Déconnexion 4NK
|
|
||||||
</Button>
|
|
||||||
{!userPairingId && (
|
|
||||||
<Button variant="outline" size="sm" onClick={handleForceGetPairingId}>
|
|
||||||
<Brain className="h-4 w-4 mr-2" />
|
|
||||||
Debug PairingId
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Button variant="outline" size="sm" onClick={handleLogin}>
|
|
||||||
<Shield className="h-4 w-4 mr-2" />
|
|
||||||
Connexion 4NK
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -2547,25 +2292,6 @@ export default function FoldersPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Folder Creation Modal */}
|
|
||||||
<FolderModal
|
|
||||||
isOpen={showCreateFolderModal}
|
|
||||||
onClose={handleCancelCreateFolder}
|
|
||||||
onSave={handleSaveNewFolder}
|
|
||||||
onCancel={handleCancelCreateFolder}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 4NK Authentication Modal */}
|
|
||||||
<AuthModal
|
|
||||||
isOpen={showAuthModal}
|
|
||||||
onConnect={handleAuthConnect}
|
|
||||||
onClose={handleAuthClose}
|
|
||||||
iframeUrl={iframeUrl}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* 4NK Iframe - only show when connected */}
|
|
||||||
{isConnected && <Iframe iframeUrl={iframeUrl} />}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,244 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
@ -1,421 +0,0 @@
|
|||||||
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);
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
/* Modal Overlay */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Container */
|
|
||||||
.modal-container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
||||||
max-height: 90vh;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-sm {
|
|
||||||
max-width: 28rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-md {
|
|
||||||
max-width: 32rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-lg {
|
|
||||||
max-width: 48rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-xl {
|
|
||||||
max-width: 64rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Header */
|
|
||||||
.modal-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
background: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #111827;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
color: #6b7280;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-close-button:hover {
|
|
||||||
background-color: #f3f4f6;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Content */
|
|
||||||
.modal-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.modal-overlay {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-container {
|
|
||||||
max-height: 95vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-title {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { X } from 'lucide-react';
|
|
||||||
import './Modal.css';
|
|
||||||
|
|
||||||
interface ModalProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
title: string;
|
|
||||||
children: React.ReactNode;
|
|
||||||
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
||||||
}
|
|
||||||
|
|
||||||
const Modal: React.FC<ModalProps> = ({
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
title,
|
|
||||||
children,
|
|
||||||
size = 'md'
|
|
||||||
}) => {
|
|
||||||
if (!isOpen) return null;
|
|
||||||
|
|
||||||
const handleBackdropClick = (e: React.MouseEvent) => {
|
|
||||||
if (e.target === e.currentTarget) {
|
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="modal-overlay" onClick={handleBackdropClick}>
|
|
||||||
<div className={`modal-container modal-${size}`}>
|
|
||||||
<div className="modal-header">
|
|
||||||
<h2 className="modal-title">{title}</h2>
|
|
||||||
<button
|
|
||||||
className="modal-close-button"
|
|
||||||
onClick={onClose}
|
|
||||||
aria-label="Fermer la modal"
|
|
||||||
>
|
|
||||||
<X className="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="modal-content">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Modal;
|
|
||||||
Loading…
x
Reference in New Issue
Block a user