Compare commits
No commits in common. "e607d7850b29859b652af11c18ea40c44334d261" and "362c34c816e12ff9e92f02c06add1bb59174d7dd" have entirely different histories.
e607d7850b
...
362c34c816
@ -21,10 +21,9 @@ import {
|
||||
LogOut,
|
||||
ChevronDown,
|
||||
Home,
|
||||
Key,
|
||||
TestTube,
|
||||
User,
|
||||
Copy,
|
||||
CheckCircle,
|
||||
User
|
||||
} from "@/lib/icons"
|
||||
import UserStore from "@/lib/4nk/UserStore"
|
||||
import EventBus from "@/lib/4nk/EventBus"
|
||||
@ -40,11 +39,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false)
|
||||
const [show4nkAuthModal, setShow4nkAuthModal] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isMockMode, setIsMockMode] = useState(true)
|
||||
const [isMockMode, setIsMockMode] = useState(false)
|
||||
const [userInfo, setUserInfo] = useState<any>(null)
|
||||
const [showLogoutConfirm, setShowLogoutConfirm] = useState(false)
|
||||
const [isCopied, setIsCopied] = useState(false)
|
||||
const [isPrivateKeyFlash, setIsPrivateKeyFlash] = useState(false)
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
@ -114,16 +114,20 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
setShowLogoutConfirm(true)
|
||||
}, []);
|
||||
|
||||
const handleCopyToClipboard = useCallback(() => {
|
||||
if (userPairingId) {
|
||||
navigator.clipboard.writeText(userPairingId).then(() => {
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000);
|
||||
}).catch(err => {
|
||||
console.error('Erreur lors de la copie : ', err);
|
||||
});
|
||||
useEffect(() => {
|
||||
const onPrivateKeyAccess = () => {
|
||||
setIsPrivateKeyFlash(true)
|
||||
setTimeout(() => setIsPrivateKeyFlash(false), 400)
|
||||
}
|
||||
}, [userPairingId]);
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("private-key-access", onPrivateKeyAccess as EventListener)
|
||||
}
|
||||
return () => {
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("private-key-access", onPrivateKeyAccess as EventListener)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-gray-50 dark:bg-gray-900">
|
||||
@ -176,27 +180,6 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-56">
|
||||
<DropdownMenuLabel>
|
||||
<div className="flex items-center justify-between group">
|
||||
<p className="text-sm font-medium truncate" title={userPairingId || ""}>
|
||||
{userInfo?.id}
|
||||
</p>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleCopyToClipboard();
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<CheckCircle className="h-4 w-4 text-green-500" />
|
||||
) : (
|
||||
<Copy className="h-4 w-4 text-gray-500 group-hover:text-gray-900 dark:group-hover:text-gray-100" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm font-medium">{userInfo?.name}</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{userInfo?.company}</p>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
@ -96,6 +96,7 @@ export default function DashboardPage() {
|
||||
const [folderType, setFolderType] = useState<FolderType | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [notification, setNotification] = useState<{ type: "success" | "error" | "info"; message: string } | null>(null)
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = useState<EnrichedFolderData | null>(null);
|
||||
|
||||
const {
|
||||
@ -103,7 +104,6 @@ export default function DashboardPage() {
|
||||
userPairingId,
|
||||
folders,
|
||||
loadingFolders,
|
||||
members,
|
||||
setFolderProcesses,
|
||||
setMyFolderProcesses,
|
||||
setFolderPrivateData
|
||||
@ -131,46 +131,33 @@ export default function DashboardPage() {
|
||||
};
|
||||
|
||||
const handleSaveNewFolder = useCallback(
|
||||
(folderData: FolderData, selectedMembers: string[]) => {
|
||||
(folderData: FolderData) => {
|
||||
if (!isConnected || !userPairingId) {
|
||||
showNotification("error", "Vous devez être connecté à 4NK pour créer un dossier");
|
||||
return;
|
||||
}
|
||||
|
||||
// Crée les rôles par défaut (probablement 'owner' = vous)
|
||||
const roles = setDefaultFolderRoles(userPairingId);
|
||||
const folderPrivateFields = FolderPrivateFields;
|
||||
MessageBus.getInstance(iframeUrl)
|
||||
.createFolder(folderData, folderPrivateFields, roles)
|
||||
.then((_folderCreated: FolderCreated) => {
|
||||
const firstStateId = _folderCreated.process.states[0].state_id;
|
||||
MessageBus.getInstance(iframeUrl)
|
||||
.notifyProcessUpdate(_folderCreated.processId, firstStateId)
|
||||
.then(() => {
|
||||
const { processId, process } = _folderCreated;
|
||||
|
||||
// Fusionne votre userPairingId avec les membres sélectionnés
|
||||
// On utilise un Set pour éviter les doublons
|
||||
const allOwnerMembers = new Set([
|
||||
...roles.owner.members, // Membres par défaut (vous)
|
||||
userPairingId, // S'assurer que vous y êtes
|
||||
...selectedMembers // Ajoute les nouveaux membres
|
||||
]);
|
||||
setFolderProcesses((prevProcesses: any) => ({ ...prevProcesses, [processId]: process }));
|
||||
setMyFolderProcesses((prevMyProcesses: string[]) => {
|
||||
if (prevMyProcesses.includes(processId)) return prevMyProcesses;
|
||||
return [...prevMyProcesses, processId];
|
||||
});
|
||||
setFolderPrivateData((prevData) => ({ ...prevData, [firstStateId]: folderData }));
|
||||
|
||||
// Met à jour la liste des membres pour le rôle 'owner'
|
||||
// (Vous pouvez ajuster "owner" pour un autre rôle si nécessaire)
|
||||
roles.owner.members = Array.from(allOwnerMembers);
|
||||
console.log(roles);
|
||||
|
||||
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) => {
|
||||
const { processId, process } = _folderCreated;
|
||||
|
||||
setFolderProcesses((prevProcesses: any) => ({ ...prevProcesses, [processId]: process }));
|
||||
setMyFolderProcesses((prevMyProcesses: string[]) => {
|
||||
if (prevMyProcesses.includes(processId)) return prevMyProcesses;
|
||||
return [...prevMyProcesses, processId];
|
||||
showNotification("success", "Dossier créé avec succès !");
|
||||
handleCloseModal();
|
||||
});
|
||||
setFolderPrivateData((prevData) => ({ ...prevData, [_folderCreated.process.states[0].state_id]: folderData }));
|
||||
|
||||
showNotification("success", "Dossier créé avec succès !");
|
||||
handleCloseModal();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Erreur lors de la création du dossier:', error);
|
||||
showNotification("error", "Erreur lors de la création du dossier");
|
||||
@ -235,7 +222,7 @@ export default function DashboardPage() {
|
||||
<Folder className="h-5 w-5 text-blue-500 flex-shrink-0" />
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-medium text-gray-100 truncate">{folder.name}</h3>
|
||||
{/* Texte sous le nom du dossier */}
|
||||
{/* ID du dossier supprimé */}
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -258,24 +245,7 @@ export default function DashboardPage() {
|
||||
<div className="flex-shrink-0">
|
||||
<h1 className="text-2xl font-semibold">{selectedFolder.name}</h1>
|
||||
<p className="text-gray-400 mt-2">{selectedFolder.description}</p>
|
||||
<div className="flex items-center space-x-4 mt-3 text-xs text-gray-500 dark:text-gray-400">
|
||||
<div className="flex items-center space-x-1" title={selectedFolder.created_at}>
|
||||
<Clock className="h-3 w-3" />
|
||||
<span>
|
||||
Créé le: {new Date(selectedFolder.created_at).toLocaleString('fr-FR', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1" title={selectedFolder.updated_at}>
|
||||
<Clock className="h-3 w-3" />
|
||||
<span>
|
||||
Modifié le: {new Date(selectedFolder.updated_at).toLocaleString('fr-FR', {
|
||||
day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit'
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Badge ID supprimé */}
|
||||
</div>
|
||||
|
||||
{/* Contenu Colonne 2 */}
|
||||
@ -309,7 +279,7 @@ export default function DashboardPage() {
|
||||
Fichiers
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
{/* <CardContent>
|
||||
<CardContent>
|
||||
{selectedFolder.files && selectedFolder.files.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{selectedFolder.files.map((file, index) => (
|
||||
@ -327,7 +297,7 @@ export default function DashboardPage() {
|
||||
) : (
|
||||
<p className="text-gray-500">Aucun fichier dans ce dossier.</p>
|
||||
)}
|
||||
</CardContent> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</>
|
||||
@ -349,7 +319,6 @@ export default function DashboardPage() {
|
||||
onSave={handleSaveNewFolder}
|
||||
onCancel={handleCloseModal}
|
||||
folderType={folderType || "autre"}
|
||||
members={members}
|
||||
/>
|
||||
)}
|
||||
{notification && (
|
||||
|
||||
@ -13,8 +13,6 @@ import {
|
||||
MessageSquare
|
||||
} from "lucide-react"
|
||||
import type { EnrichedFolderData } from "@/lib/contexts/FourNKContext";
|
||||
import MessageBus from "@/lib/4nk/MessageBus"
|
||||
import { iframeUrl } from "@/app/page"
|
||||
|
||||
// Interface pour les props (accepte null)
|
||||
interface FolderChatProps {
|
||||
@ -47,50 +45,10 @@ export default function FolderChat({ folder }: FolderChatProps) {
|
||||
// Filtre les messages basé sur l'onglet actif
|
||||
const filteredMessages = mockMessages.filter(msg => msg.type === activeTab);
|
||||
|
||||
const handleProcessUpdate = async (processId: string, key: string, value: any) => {
|
||||
try {
|
||||
const messageBus = MessageBus.getInstance(iframeUrl);
|
||||
await messageBus.isReady();
|
||||
|
||||
const updateData = {
|
||||
[key]: value
|
||||
};
|
||||
|
||||
// First update the process
|
||||
const updatedProcess = await messageBus.updateProcess(processId, updateData, [], null);
|
||||
if (!updatedProcess) {
|
||||
throw new Error('No updated process found');
|
||||
}
|
||||
|
||||
const newStateId = updatedProcess.diffs[0]?.state_id;
|
||||
|
||||
if (!newStateId) {
|
||||
throw new Error('No new state id found');
|
||||
}
|
||||
|
||||
// Then notify about the update
|
||||
await messageBus.notifyProcessUpdate(processId, newStateId);
|
||||
|
||||
// Finally validate the state
|
||||
await messageBus.validateState(processId, newStateId);
|
||||
|
||||
// Refresh the processes data
|
||||
// const updatedProcesses = await messageBus.getProcesses();
|
||||
|
||||
console.log('Process updated successfully');
|
||||
} catch (error) {
|
||||
console.error('Error updating field:', error);
|
||||
// You might want to show an error message to the user here
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendMessage = () => {
|
||||
if (newMessage.trim()) {
|
||||
console.log(`Envoi message [${activeTab}] à:`, folder?.folderNumber, "Msg:", newMessage)
|
||||
// TODO: Implémenter la logique d'envoi de message
|
||||
if(!folder) return;
|
||||
const key = activeTab === 'owner' ? 'messages_owner' : 'messages'
|
||||
handleProcessUpdate(folder.processId, key, newMessage)
|
||||
setNewMessage("")
|
||||
}
|
||||
}
|
||||
@ -173,8 +131,8 @@ export default function FolderChat({ folder }: FolderChatProps) {
|
||||
<div>
|
||||
<div
|
||||
className={`p-3 rounded-lg ${msg.sender === 'me'
|
||||
? 'bg-blue-600 text-white rounded-br-none'
|
||||
: 'bg-gray-700 text-gray-100 rounded-bl-none'
|
||||
? 'bg-blue-600 text-white rounded-br-none'
|
||||
: 'bg-gray-700 text-gray-100 rounded-bl-none'
|
||||
}`}
|
||||
>
|
||||
{msg.sender === 'other' && (
|
||||
|
||||
@ -1,21 +1,17 @@
|
||||
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;
|
||||
onSave?: (folderData: FolderData) => 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>>
|
||||
@ -28,9 +24,7 @@ const defaultFolder: FolderData = {
|
||||
description: '',
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
notes: [],
|
||||
messages: [],
|
||||
messages_owner: []
|
||||
notes: []
|
||||
};
|
||||
|
||||
function capitalize(s?: string) {
|
||||
@ -38,7 +32,7 @@ function capitalize(s?: string) {
|
||||
return s.charAt(0).toUpperCase() + s.slice(1);
|
||||
}
|
||||
|
||||
// Mapping des couleurs
|
||||
// Mapping des couleurs par type de dossier
|
||||
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' },
|
||||
@ -57,19 +51,15 @@ function FolderModal({
|
||||
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]);
|
||||
|
||||
@ -80,14 +70,6 @@ function FolderModal({
|
||||
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;
|
||||
@ -101,7 +83,7 @@ function FolderModal({
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
onSave?.({ ...folderData, updated_at: new Date().toISOString() }, selectedMembers);
|
||||
onSave?.({ ...folderData, updated_at: new Date().toISOString() });
|
||||
onClose();
|
||||
};
|
||||
|
||||
@ -157,18 +139,6 @@ function FolderModal({
|
||||
</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>
|
||||
|
||||
@ -1,184 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
|
||||
function Command({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||
return (
|
||||
<CommandPrimitive
|
||||
data-slot="command"
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandDialog({
|
||||
title = "Command Palette",
|
||||
description = "Search for a command to run...",
|
||||
children,
|
||||
className,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Dialog> & {
|
||||
title?: string
|
||||
description?: string
|
||||
className?: string
|
||||
showCloseButton?: boolean
|
||||
}) {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogHeader className="sr-only">
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogContent
|
||||
className={cn("overflow-hidden p-0", className)}
|
||||
showCloseButton={showCloseButton}
|
||||
>
|
||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandInput({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="command-input-wrapper"
|
||||
className="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandList({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||
return (
|
||||
<CommandPrimitive.List
|
||||
data-slot="command-list"
|
||||
className={cn(
|
||||
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandEmpty({
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||
return (
|
||||
<CommandPrimitive.Empty
|
||||
data-slot="command-empty"
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||
return (
|
||||
<CommandPrimitive.Group
|
||||
data-slot="command-group"
|
||||
className={cn(
|
||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||
return (
|
||||
<CommandPrimitive.Separator
|
||||
data-slot="command-separator"
|
||||
className={cn("bg-border -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||
return (
|
||||
<CommandPrimitive.Item
|
||||
data-slot="command-item"
|
||||
className={cn(
|
||||
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function CommandShortcut({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="command-shortcut"
|
||||
className={cn(
|
||||
"text-muted-foreground ml-auto text-xs tracking-widest",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
||||
return (
|
||||
<DialogPrimitive.Overlay
|
||||
data-slot="dialog-overlay"
|
||||
className={cn(
|
||||
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
className,
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||
showCloseButton?: boolean
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<DialogPrimitive.Close
|
||||
data-slot="dialog-close"
|
||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-header"
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="dialog-footer"
|
||||
className={cn(
|
||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
||||
return (
|
||||
<DialogPrimitive.Title
|
||||
data-slot="dialog-title"
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
||||
return (
|
||||
<DialogPrimitive.Description
|
||||
data-slot="dialog-description"
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogOverlay,
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Check, ChevronsUpDown, X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
|
||||
interface MemberAutocompleteProps {
|
||||
allMembers: string[];
|
||||
selectedMembers: string[];
|
||||
onChange: (selectedMembers: string[]) => void;
|
||||
}
|
||||
|
||||
export function MemberAutocomplete({
|
||||
allMembers,
|
||||
selectedMembers,
|
||||
onChange,
|
||||
}: MemberAutocompleteProps) {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
|
||||
// Liste des membres qui ne sont PAS encore sélectionnés
|
||||
const availableMembers = allMembers.filter(
|
||||
(member) => !selectedMembers.includes(member)
|
||||
)
|
||||
|
||||
// Gère la sélection d'un membre dans la liste
|
||||
const handleSelect = (memberId: string) => {
|
||||
onChange([...selectedMembers, memberId])
|
||||
setOpen(false) // Ferme le popover après sélection
|
||||
}
|
||||
|
||||
// Gère la suppression d'un membre (clic sur le 'X' du badge)
|
||||
const handleRemove = (memberId: string) => {
|
||||
onChange(selectedMembers.filter((m) => m !== memberId))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* 1. Affichage des membres déjà sélectionnés (Badges) */}
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{selectedMembers.map((member) => (
|
||||
<Badge
|
||||
key={member}
|
||||
variant="secondary"
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
<span className="truncate max-w-[200px]" title={member}>{member}</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemove(member)}
|
||||
className="rounded-full hover:bg-red-500/20 p-0.5"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 2. Le Popover avec le bouton de recherche */}
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
type="button" // Important pour ne pas soumettre le formulaire
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="w-full justify-between bg-gray-100 dark:bg-gray-700"
|
||||
>
|
||||
Ajouter un membre...
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[--radix-popover-trigger-width] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Rechercher un membre..." />
|
||||
<CommandList>
|
||||
<CommandEmpty>Aucun membre trouvé.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{availableMembers.map((member) => (
|
||||
<CommandItem
|
||||
key={member}
|
||||
value={member} // 'value' est utilisé pour la recherche
|
||||
onSelect={() => handleSelect(member)}
|
||||
className="truncate"
|
||||
>
|
||||
{member}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,48 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Popover({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
|
||||
return <PopoverPrimitive.Root data-slot="popover" {...props} />
|
||||
}
|
||||
|
||||
function PopoverTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
|
||||
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
|
||||
}
|
||||
|
||||
function PopoverContent({
|
||||
className,
|
||||
align = "center",
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-slot="popover-content"
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
function PopoverAnchor({
|
||||
...props
|
||||
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
|
||||
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
|
||||
}
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||
@ -489,7 +489,7 @@ export default class MessageBus {
|
||||
});
|
||||
}
|
||||
|
||||
public updateProcess(processId: string, newData: Record<string, any>, privateFields: string[], roles: Record<string, RoleDefinition> | null): Promise<any> {
|
||||
public updateProcess(processId: string, lastStateId: string, newData: Record<string, any>, privateFields: string[], roles: Record<string, RoleDefinition> | null): Promise<any> {
|
||||
return new Promise<any>((resolve: (updatedProcess: any) => void, reject: (error: string) => void) => {
|
||||
this.checkToken().then(() => {
|
||||
const userStore = UserStore.getInstance();
|
||||
@ -520,6 +520,7 @@ export default class MessageBus {
|
||||
this.sendMessage({
|
||||
type: 'UPDATE_PROCESS',
|
||||
processId,
|
||||
lastStateId,
|
||||
newData,
|
||||
privateFields,
|
||||
roles,
|
||||
@ -623,7 +624,7 @@ export default class MessageBus {
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log('[MessageBus] sendMessage:', message, 'to', this.origin);
|
||||
console.log('[MessageBus] sendMessage:', message, 'to', this.origin);
|
||||
iframe.contentWindow?.postMessage(message, this.origin);
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,6 @@ export interface FolderData {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
notes: string[];
|
||||
messages: string[];
|
||||
messages_owner: string[];
|
||||
}
|
||||
|
||||
export function isFolderData(data: any): data is FolderData {
|
||||
@ -40,9 +38,7 @@ const emptyFolderData: FolderData = {
|
||||
description: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
notes: [],
|
||||
messages: [],
|
||||
messages_owner: [],
|
||||
notes: []
|
||||
};
|
||||
|
||||
const folderDataFields: string[] = Object.keys(emptyFolderData);
|
||||
@ -76,7 +72,7 @@ export function setDefaultFolderRoles(ownerId: string): Record<string, RoleDefin
|
||||
min_sig_member: 1,
|
||||
},
|
||||
],
|
||||
storages: ['https://dev2.4nkweb.com/storage']
|
||||
storages: []
|
||||
},
|
||||
apophis: {
|
||||
members: [ownerId],
|
||||
|
||||
@ -6,9 +6,19 @@ import { iframeUrl } from "@/app/page";
|
||||
import UserStore from "@/lib/4nk/UserStore";
|
||||
import { FolderData } from "@/lib/4nk/models/FolderData";
|
||||
|
||||
// --- Définition des types pour plus de clarté ---
|
||||
export interface FolderMember {
|
||||
id: string
|
||||
name: string
|
||||
avatar: string
|
||||
isOnline: boolean
|
||||
}
|
||||
|
||||
// Interface enrichie qui inclut maintenant les membres ET les fichiers
|
||||
export interface EnrichedFolderData extends FolderData {
|
||||
processId: string,
|
||||
members: FolderMember[];
|
||||
files: any[]; // <-- AJOUT DES FICHIERS
|
||||
// notes: any[]; // 'notes' est déjà dans FolderData
|
||||
}
|
||||
// ---
|
||||
|
||||
@ -20,14 +30,12 @@ type FourNKContextType = {
|
||||
folderProcesses: any;
|
||||
myFolderProcesses: string[];
|
||||
folderPrivateData: Record<string, Record<string, any>>;
|
||||
folders: EnrichedFolderData[];
|
||||
folders: EnrichedFolderData[]; // <-- Utilise le type enrichi
|
||||
loadingFolders: boolean;
|
||||
members: string[];
|
||||
|
||||
setFolderProcesses: React.Dispatch<React.SetStateAction<any>>;
|
||||
setMyFolderProcesses: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
setFolderPrivateData: React.Dispatch<React.SetStateAction<Record<string, Record<string, any>>>>;
|
||||
setMembers: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
};
|
||||
|
||||
const FourNKContext = createContext<FourNKContextType | undefined>(undefined);
|
||||
@ -36,7 +44,6 @@ export function FourNKProvider({ children }: { children: ReactNode }) {
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
const [userPairingId, setUserPairingId] = useState<string | null>(null);
|
||||
const [processes, setProcesses] = useState<any>(null);
|
||||
const [members, setMembers] = useState<string[]>([]);;
|
||||
const [myProcesses, setMyProcesses] = useState<string[]>([]);
|
||||
const [folderProcesses, setFolderProcesses] = useState<any>(null);
|
||||
const [myFolderProcesses, setMyFolderProcesses] = useState<string[]>([]);
|
||||
@ -66,109 +73,58 @@ export function FourNKProvider({ children }: { children: ReactNode }) {
|
||||
let hasAllPrivateData = true;
|
||||
let hasFoldersToLoad = false;
|
||||
const missingPrivateData: Array<{ processId: string, stateId: string }> = [];
|
||||
const EXCLUDED_STATE_ID = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
Object.entries(folderProcesses).forEach(([processId, process]: [string, any]) => {
|
||||
if (!myFolderProcesses.includes(processId)) return;
|
||||
const validStates = process.states.filter(
|
||||
state => state && state.state_id !== EXCLUDED_STATE_ID
|
||||
);
|
||||
if (validStates.length === 0) return;
|
||||
const referenceState = validStates.find(
|
||||
state => state.pcd_commitment?.folderNumber
|
||||
);
|
||||
const mainFolderNumber = referenceState?.pcd_commitment.folderNumber;
|
||||
if (!mainFolderNumber) {
|
||||
const latestState = process.states[0];
|
||||
if (!latestState) return;
|
||||
const folderNumber = latestState.pcd_commitment?.folderNumber;
|
||||
if (!folderNumber) return;
|
||||
|
||||
hasFoldersToLoad = true;
|
||||
const privateData = folderPrivateData[latestState.state_id];
|
||||
|
||||
if (!privateData) {
|
||||
hasAllPrivateData = false;
|
||||
missingPrivateData.push({ processId, stateId: latestState.state_id });
|
||||
return;
|
||||
}
|
||||
|
||||
// console.log(validStates);
|
||||
|
||||
// validStates.forEach(state => {
|
||||
hasFoldersToLoad = true;
|
||||
const stateToProcess = referenceState;
|
||||
|
||||
const privateData = folderPrivateData[stateToProcess.state_id];
|
||||
|
||||
// Si on n'a pas les données pour cet état de référence...
|
||||
if (!privateData) {
|
||||
hasAllPrivateData = false;
|
||||
missingPrivateData.push({ processId, stateId: stateToProcess.state_id });
|
||||
return; // On quitte ce process, on réessaiera au prochain rendu
|
||||
}
|
||||
// console.log("Données déchiffrées pour le state:", stateToProcess.state_id, privateData);
|
||||
|
||||
/*
|
||||
// 4. CONDITION B: On vérifie si cet état contient des messages.
|
||||
const hasMessages = (privateData.messages && privateData.messages.length > 0);
|
||||
const hasMessagesOwner = (privateData.messages_owner && privateData.messages_owner.length > 0);
|
||||
|
||||
// Si cet état n'a pas de messages, on l'ignore (return)
|
||||
if (!hasMessages && !hasMessagesOwner) {
|
||||
return; // Passe à l'état suivant
|
||||
}
|
||||
*/
|
||||
const ownerMembers = latestState.roles?.owner?.members || [];
|
||||
const members: FolderMember[] = ownerMembers.map((memberId: string) => {
|
||||
const avatar = memberId.slice(0, 2).toUpperCase();
|
||||
return {
|
||||
id: memberId,
|
||||
name: `Membre ${memberId.slice(0, 4)}`,
|
||||
avatar: avatar,
|
||||
isOnline: Math.random() > 0.5
|
||||
}
|
||||
});
|
||||
|
||||
folderData.push({
|
||||
processId: processId,
|
||||
folderNumber: mainFolderNumber, // La clé unique
|
||||
name: privateData.name || `Dossier ${mainFolderNumber}`,
|
||||
folderNumber: folderNumber,
|
||||
name: privateData.name || `Dossier ${folderNumber}`,
|
||||
description: privateData.description || '',
|
||||
created_at: privateData.created_at || new Date().toISOString(),
|
||||
updated_at: privateData.updated_at || new Date().toISOString(),
|
||||
notes: privateData.notes || [],
|
||||
messages: privateData.messages || [],
|
||||
messages_owner: privateData.messages_owner || [],
|
||||
files: privateData.files || [], // <-- AJOUT DE L'EXTRACTION DES FICHIERS
|
||||
members: members
|
||||
});
|
||||
// });
|
||||
});
|
||||
|
||||
|
||||
if (hasFoldersToLoad && !hasAllPrivateData) {
|
||||
setLoadingFolders(true);
|
||||
const firstMissing = missingPrivateData[0];
|
||||
if (firstMissing) {
|
||||
if (!folderPrivateData[firstMissing.stateId]) {
|
||||
fetchFolderPrivateData(firstMissing.processId, firstMissing.stateId);
|
||||
missingPrivateData.forEach(({ processId, stateId }) => {
|
||||
if (!folderPrivateData[stateId]) {
|
||||
fetchFolderPrivateData(processId, stateId);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFolders(folderData);
|
||||
setLoadingFolders(false);
|
||||
}
|
||||
}, [folderProcesses, myFolderProcesses, folderPrivateData, fetchFolderPrivateData, setFolders, setLoadingFolders]); // J'ai ajouté setFolders et setLoadingFolders aux dépendances
|
||||
|
||||
const loadMembersFrom4NK = useCallback(() => {
|
||||
if (!processes || !userPairingId) return;
|
||||
|
||||
const memberList: string[] = [];
|
||||
const EXCLUDED_STATE_ID = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
Object.entries(processes).forEach(([processId, process]: [string, any]) => {
|
||||
const validStates = process.states.filter(
|
||||
state => state && state.state_id !== EXCLUDED_STATE_ID
|
||||
);
|
||||
if (validStates.length === 0) return;
|
||||
|
||||
const referenceState = validStates.find(
|
||||
state => state.pcd_commitment?.pairedAddresses
|
||||
);
|
||||
if (!referenceState) return;
|
||||
|
||||
const userAddress = referenceState.commited_in
|
||||
|
||||
memberList.push(userAddress);
|
||||
});
|
||||
|
||||
// Filtrer la liste pour enlever l'ID de l'utilisateur connecté
|
||||
const filteredMemberList = memberList.filter(
|
||||
member => member !== userPairingId
|
||||
);
|
||||
|
||||
// Sauvegarder la liste filtrée dans l'état
|
||||
setMembers(filteredMemberList);
|
||||
|
||||
}, [processes, userPairingId]);
|
||||
}, [folderProcesses, myFolderProcesses, folderPrivateData, fetchFolderPrivateData]);
|
||||
|
||||
// Chargement initial des données 4NK
|
||||
useEffect(() => {
|
||||
@ -220,11 +176,6 @@ export function FourNKProvider({ children }: { children: ReactNode }) {
|
||||
loadFoldersFrom4NK();
|
||||
}, [loadFoldersFrom4NK]);
|
||||
|
||||
// Re-calculer les membres lorsque les données changent
|
||||
useEffect(() => {
|
||||
loadMembersFrom4NK();
|
||||
}, [loadMembersFrom4NK]);
|
||||
|
||||
|
||||
const value = {
|
||||
isConnected,
|
||||
@ -236,11 +187,9 @@ export function FourNKProvider({ children }: { children: ReactNode }) {
|
||||
folderPrivateData,
|
||||
folders,
|
||||
loadingFolders,
|
||||
members,
|
||||
setFolderProcesses,
|
||||
setMyFolderProcesses,
|
||||
setFolderPrivateData,
|
||||
setMembers,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@ -17,20 +17,20 @@
|
||||
"@radix-ui/react-checkbox": "latest",
|
||||
"@radix-ui/react-collapsible": "1.1.2",
|
||||
"@radix-ui/react-context-menu": "2.2.4",
|
||||
"@radix-ui/react-dialog": "^1.1.4",
|
||||
"@radix-ui/react-dialog": "1.1.4",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.4",
|
||||
"@radix-ui/react-hover-card": "1.1.4",
|
||||
"@radix-ui/react-label": "latest",
|
||||
"@radix-ui/react-menubar": "1.1.4",
|
||||
"@radix-ui/react-navigation-menu": "1.2.3",
|
||||
"@radix-ui/react-popover": "^1.1.4",
|
||||
"@radix-ui/react-popover": "1.1.4",
|
||||
"@radix-ui/react-progress": "1.1.1",
|
||||
"@radix-ui/react-radio-group": "latest",
|
||||
"@radix-ui/react-scroll-area": "1.2.2",
|
||||
"@radix-ui/react-select": "latest",
|
||||
"@radix-ui/react-separator": "1.1.1",
|
||||
"@radix-ui/react-slider": "1.2.2",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-slot": "latest",
|
||||
"@radix-ui/react-switch": "latest",
|
||||
"@radix-ui/react-tabs": "1.1.2",
|
||||
"@radix-ui/react-toast": "1.2.4",
|
||||
@ -41,7 +41,6 @@
|
||||
"bip39": "^3.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"geist": "^1.3.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "15.2.4",
|
||||
|
||||
3655
pnpm-lock.yaml
generated
Normal file
3655
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user