315 lines
12 KiB
TypeScript
315 lines
12 KiB
TypeScript
"use client"
|
|
|
|
import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
|
|
import MessageBus from "@/lib/4nk/MessageBus";
|
|
import { iframeUrl } from "@/app/page";
|
|
import UserStore from "@/lib/4nk/UserStore";
|
|
import { FolderChatData, FolderData } from "@/lib/4nk/models/FolderData";
|
|
|
|
// Interface enrichie qui inclut maintenant les membres ET les fichiers
|
|
export interface EnrichedFolderData extends FolderData {
|
|
processId: string,
|
|
}
|
|
// ---
|
|
|
|
type FourNKContextType = {
|
|
isConnected: boolean;
|
|
userPairingId: string | null;
|
|
userName: string | null;
|
|
processes: any;
|
|
myProcesses: string[];
|
|
folderProcesses: any;
|
|
myFolderProcesses: string[];
|
|
folderPrivateData: Record<string, Record<string, any>>;
|
|
folders: EnrichedFolderData[];
|
|
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[]>>;
|
|
refreshUserName: () => Promise<void>;
|
|
};
|
|
|
|
const FourNKContext = createContext<FourNKContextType | undefined>(undefined);
|
|
|
|
export function FourNKProvider({ children }: { children: ReactNode }) {
|
|
const [isConnected, setIsConnected] = useState(false);
|
|
const [userPairingId, setUserPairingId] = useState<string | null>(null);
|
|
const [userName, setUserName] = 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[]>([]);
|
|
const [folderPrivateData, setFolderPrivateData] = useState<Record<string, Record<string, any>>>({});
|
|
const [loadingFolders, setLoadingFolders] = useState(true);
|
|
const [folders, setFolders] = useState<EnrichedFolderData[]>([]);
|
|
|
|
const fetchFolderPrivateData = useCallback(async (processId: string, stateId: string) => {
|
|
try {
|
|
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
await messageBus.isReady();
|
|
const data = await messageBus.getData(processId, stateId);
|
|
setFolderPrivateData(prev => ({ ...prev, [stateId]: data }));
|
|
return data;
|
|
} catch (err) {
|
|
console.error('Error fetching folder private data:', err);
|
|
return null;
|
|
}
|
|
}, []);
|
|
|
|
const loadFoldersFrom4NK = useCallback(() => {
|
|
if (!folderProcesses || !myFolderProcesses) {
|
|
return;
|
|
}
|
|
|
|
const folderData: EnrichedFolderData[] = [];
|
|
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: any) => state && state.state_id !== EXCLUDED_STATE_ID
|
|
);
|
|
if (validStates.length === 0) return;
|
|
const referenceState = validStates.find(
|
|
(state: any) => state.pcd_commitment?.folderNumber
|
|
);
|
|
const mainFolderNumber = referenceState?.pcd_commitment.folderNumber;
|
|
if (!mainFolderNumber) {
|
|
return;
|
|
}
|
|
|
|
let basePrivateData: any;
|
|
let mergedMessages: FolderChatData[] = [];
|
|
let mergedMessagesOwner: FolderChatData[] = [];
|
|
|
|
validStates.forEach((state: any) => {
|
|
hasFoldersToLoad = true;
|
|
const stateToProcess = state;
|
|
|
|
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);
|
|
|
|
if (privateData.messages_owner instanceof Map) {
|
|
const messageOwnerObj = Object.fromEntries(privateData.messages_owner);
|
|
mergedMessagesOwner.push(messageOwnerObj);
|
|
}
|
|
|
|
if (privateData.messages instanceof Map) {
|
|
const messageObj = Object.fromEntries(privateData.messages);
|
|
mergedMessages.push(messageObj);
|
|
}
|
|
|
|
if (privateData.folderNumber) {
|
|
basePrivateData = privateData;
|
|
}
|
|
});
|
|
|
|
if (!basePrivateData) return;
|
|
folderData.push({
|
|
processId: processId,
|
|
folderNumber: mainFolderNumber,
|
|
name: basePrivateData.name || `Dossier ${mainFolderNumber}`,
|
|
description: basePrivateData.description || '',
|
|
created_at: basePrivateData.created_at || new Date().toISOString(),
|
|
updated_at: basePrivateData.updated_at || new Date().toISOString(),
|
|
notes: basePrivateData.notes || [],
|
|
messages: mergedMessages || [],
|
|
messages_owner: mergedMessagesOwner || [],
|
|
attachedFiles: basePrivateData.attachedFiles || [],
|
|
});
|
|
});
|
|
|
|
|
|
if (hasFoldersToLoad && !hasAllPrivateData) {
|
|
setLoadingFolders(true);
|
|
const firstMissing = missingPrivateData[0];
|
|
if (firstMissing) {
|
|
if (!folderPrivateData[firstMissing.stateId]) {
|
|
fetchFolderPrivateData(firstMissing.processId, firstMissing.stateId);
|
|
}
|
|
}
|
|
} else {
|
|
setFolders(folderData);
|
|
setLoadingFolders(false);
|
|
}
|
|
}, [folderProcesses, myFolderProcesses, folderPrivateData, fetchFolderPrivateData, setFolders, setLoadingFolders]);
|
|
|
|
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: any) => state && state.state_id !== EXCLUDED_STATE_ID
|
|
);
|
|
if (validStates.length === 0) return;
|
|
|
|
const referenceState = validStates.find(
|
|
(state: any) => 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]);
|
|
|
|
// Chargement initial des données 4NK
|
|
useEffect(() => {
|
|
const userStore = UserStore.getInstance();
|
|
const connected = userStore.isConnected();
|
|
const pairingId = userStore.getUserPairingId();
|
|
|
|
setIsConnected(connected);
|
|
setUserPairingId(pairingId);
|
|
|
|
const handleConnectionFlow = async () => {
|
|
if (!connected) {
|
|
setLoadingFolders(false);
|
|
return;
|
|
}
|
|
setLoadingFolders(true);
|
|
try {
|
|
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
await messageBus.isReady();
|
|
|
|
let pid = pairingId;
|
|
if (!pid) {
|
|
pid = await messageBus.createUserPairing();
|
|
if (pid) {
|
|
userStore.pair(pid);
|
|
setUserPairingId(pid);
|
|
}
|
|
}
|
|
|
|
const procs = await messageBus.getProcesses();
|
|
const myProcs = await messageBus.getMyProcesses();
|
|
|
|
setProcesses(procs);
|
|
setFolderProcesses(procs);
|
|
setMyProcesses(myProcs);
|
|
setMyFolderProcesses(myProcs);
|
|
|
|
} catch (err) {
|
|
console.error("❌ Error during global connection flow:", err);
|
|
setLoadingFolders(false);
|
|
}
|
|
};
|
|
|
|
handleConnectionFlow();
|
|
}, [isConnected]);
|
|
|
|
// Re-calculer les dossiers lorsque les données changent
|
|
useEffect(() => {
|
|
loadFoldersFrom4NK();
|
|
}, [loadFoldersFrom4NK]);
|
|
|
|
// Re-calculer les membres lorsque les données changent
|
|
useEffect(() => {
|
|
loadMembersFrom4NK();
|
|
}, [loadMembersFrom4NK]);
|
|
|
|
|
|
// Fonction pour forcer la mise à jour du nom utilisateur
|
|
const refreshUserName = useCallback(async () => {
|
|
try {
|
|
const messageBus = MessageBus.getInstance(iframeUrl);
|
|
await messageBus.isReady();
|
|
|
|
// Recharger les processes et récupérer le nom
|
|
const freshProcesses = await messageBus.getProcesses();
|
|
if (!freshProcesses || !userPairingId) {
|
|
setUserName("Utilisateur 4NK");
|
|
return;
|
|
}
|
|
|
|
const userProcess = freshProcesses[userPairingId];
|
|
if (!userProcess?.states) {
|
|
setUserName("Utilisateur 4NK");
|
|
return;
|
|
}
|
|
|
|
const latestState = userProcess.states[userProcess.states.length - 2] || userProcess.states[0];
|
|
if (!latestState?.public_data?.memberPublicName) {
|
|
setUserName("Utilisateur 4NK");
|
|
return;
|
|
}
|
|
|
|
const decodedName = await messageBus.getPublicData(latestState.public_data.memberPublicName);
|
|
const finalName = (typeof decodedName === 'string' && decodedName.trim())
|
|
? decodedName.trim()
|
|
: "Utilisateur 4NK";
|
|
|
|
setProcesses(freshProcesses);
|
|
setUserName(finalName);
|
|
} catch (error) {
|
|
setUserName("Utilisateur 4NK");
|
|
}
|
|
}, [userPairingId]);
|
|
|
|
// Mettre à jour userName quand les processes changent (seulement au démarrage)
|
|
useEffect(() => {
|
|
if (processes && userPairingId && userName === null) {
|
|
refreshUserName();
|
|
}
|
|
}, [processes, userPairingId, userName, refreshUserName]);
|
|
|
|
const value = {
|
|
isConnected,
|
|
userPairingId,
|
|
userName,
|
|
processes,
|
|
myProcesses,
|
|
folderProcesses,
|
|
myFolderProcesses,
|
|
folderPrivateData,
|
|
folders,
|
|
loadingFolders,
|
|
members,
|
|
setFolderProcesses,
|
|
setMyFolderProcesses,
|
|
setFolderPrivateData,
|
|
setMembers,
|
|
refreshUserName,
|
|
};
|
|
|
|
return (
|
|
<FourNKContext.Provider value={value}>
|
|
{children}
|
|
</FourNKContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function use4NK() {
|
|
const context = useContext(FourNKContext);
|
|
if (context === undefined) {
|
|
throw new Error('use4NK must be used within a FourNKProvider');
|
|
}
|
|
return context;
|
|
} |