| import { useState, useEffect } from "react"; |
| import { |
| Phone, |
| Building2, |
| Mail, |
| ExternalLink, |
| Search, |
| Copy, |
| Check, |
| Loader2, |
| AlertCircle, |
| CheckCircle, |
| MapPin, |
| ChevronDown, |
| ChevronUp, |
| Tag |
| } from "lucide-react"; |
|
|
| |
| |
| |
|
|
| interface OdooPartner { |
| id: number; |
| name: string; |
| phone: string | false; |
| mobile: string | false; |
| email: string | false; |
| parent_id: [number, string] | false; |
| city: string | false; |
| street: string | false; |
| zip: string | false; |
| type: string; |
| company_name?: string | false; |
| commercial_company_name?: string | false; |
| } |
|
|
| interface PrestaCustomer { |
| id: string; |
| firstname: string; |
| lastname: string; |
| email: string; |
| company?: string; |
| phone?: string; |
| phones?: string[]; |
| group_name?: string; |
| } |
|
|
| interface CustomerProfile { |
| firstname: string; |
| lastname: string; |
| company: string; |
| phone: string; |
| email: string; |
| prestaId?: string | number; |
| odooId?: string | number; |
| street?: string; |
| zip?: string; |
| city?: string; |
| groupName?: string; |
| addresses?: { street: string; zip: string; city: string; isLatest?: boolean }[]; |
| companies?: string[]; |
| } |
|
|
| |
| |
| |
|
|
| function formatFirstname(name: string): string { |
| if (!name) return ""; |
| const trimmed = name.trim(); |
| const capitalizeWord = (w: string) => { |
| if (!w) return ""; |
| return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase(); |
| }; |
| return trimmed |
| .split("-") |
| .map(part => { |
| return part |
| .split(" ") |
| .map(capitalizeWord) |
| .join(" "); |
| }) |
| .join("-"); |
| } |
|
|
| function formatLastname(name: string): string { |
| if (!name) return ""; |
| return name.trim().toUpperCase(); |
| } |
|
|
| |
| |
| |
|
|
| const FALLBACK_JIMMY: CustomerProfile = { |
| firstname: "Jimmy", |
| lastname: "COCQUEREL", |
| company: "TousErgo", |
| phone: "07 80 98 19 98", |
| email: "jcocquerel@tousergo.com", |
| prestaId: "2201", |
| odooId: "42", |
| street: "1C QUAI MARION", |
| zip: "59158", |
| city: "Mortagne-du-Nord", |
| groupName: "Personnels, 20%", |
| addresses: [ |
| { street: "1C QUAI MARION", zip: "59158", city: "Mortagne-du-Nord", isLatest: true }, |
| { street: "12 Rue de la République", zip: "69003", city: "Lyon" }, |
| { street: "59 Boulevard de la Liberté", zip: "59000", city: "Lille" }, |
| { street: "22 Avenue des Champs-Élysées", zip: "75008", city: "Paris" }, |
| { street: "8 Rue de l'Énergie", zip: "59650", city: "Villeneuve-d'Ascq" } |
| ], |
| companies: [ |
| "TousErgo", |
| "ErgoSourcing SAS", |
| "Mobilité & Autonomie", |
| "Haut-de-France Santé", |
| "Cocquerel Holding" |
| ] |
| }; |
|
|
| const FALLBACK_DEFAULT: CustomerProfile = { |
| firstname: "Sophie", |
| lastname: "LEFEBVRE", |
| company: "Ergomed SAS", |
| phone: "06 12 34 56 78", |
| email: "sophie.lefebvre@example.fr", |
| prestaId: "8834", |
| odooId: "125", |
| street: "45 Avenue Jean Jaurès", |
| zip: "69007", |
| city: "Lyon", |
| groupName: "Pro à échéance - 0%", |
| addresses: [ |
| { street: "45 Avenue Jean Jaurès", zip: "69007", city: "Lyon", isLatest: true }, |
| { street: "18 Rue Saint-Maur", zip: "75011", city: "Paris" } |
| ], |
| companies: [ |
| "Ergomed SAS", |
| "Santé Services", |
| "Medical Pro" |
| ] |
| }; |
|
|
| function formatPhoneFrench(phone: string, contextPhone?: string) { |
| if (!phone) return ""; |
| const cleaned = phone.trim(); |
| const digits = cleaned.replace(/[^\d]/g, ""); |
| const hasPlus = cleaned.startsWith("+"); |
| |
| let cc = ""; |
| let isBelgian = false; |
| let isFrench = false; |
| let isSwiss = false; |
| let isLux = false; |
| |
| if (hasPlus) { |
| if (cleaned.startsWith("+33")) { cc = "33"; isFrench = true; } |
| else if (cleaned.startsWith("+32")) { cc = "32"; isBelgian = true; } |
| else if (cleaned.startsWith("+41")) { cc = "41"; isSwiss = true; } |
| else if (cleaned.startsWith("+352")) { cc = "352"; isLux = true; } |
| } else if (cleaned.startsWith("00")) { |
| if (cleaned.startsWith("0033")) { cc = "33"; isFrench = true; } |
| else if (cleaned.startsWith("0032")) { cc = "32"; isBelgian = true; } |
| else if (cleaned.startsWith("0041")) { cc = "41"; isSwiss = true; } |
| else if (cleaned.startsWith("00352")) { cc = "352"; isLux = true; } |
| } |
| |
| if (!cc && contextPhone) { |
| const ctxClean = contextPhone.trim(); |
| if (ctxClean.startsWith("+33") || ctxClean.startsWith("0033")) { cc = "33"; isFrench = true; } |
| else if (ctxClean.startsWith("+32") || ctxClean.startsWith("0032")) { cc = "32"; isBelgian = true; } |
| else if (ctxClean.startsWith("+41") || ctxClean.startsWith("0041")) { cc = "41"; isSwiss = true; } |
| else if (ctxClean.startsWith("+352") || ctxClean.startsWith("00352")) { cc = "352"; isLux = true; } |
| } |
| |
| if (!cc) { |
| if (digits.length === 10) { |
| if (digits.startsWith("045") || digits.startsWith("046") || digits.startsWith("047") || digits.startsWith("048") || digits.startsWith("049")) { |
| cc = "32"; |
| isBelgian = true; |
| } else { |
| cc = "33"; |
| isFrench = true; |
| } |
| } else if (digits.length === 9 && digits.startsWith("0")) { |
| cc = "32"; |
| isBelgian = true; |
| } else { |
| cc = "33"; |
| isFrench = true; |
| } |
| } |
| |
| let national = digits; |
| if (hasPlus) { |
| national = digits.slice(cc.length); |
| } else if (cleaned.startsWith("00")) { |
| national = digits.slice(2 + cc.length); |
| } else if (digits.startsWith(cc) && digits.length > cc.length + 4) { |
| national = digits.slice(cc.length); |
| } |
| |
| if (national.startsWith("0")) { |
| national = national.slice(1); |
| } |
| |
| if (isFrench) { |
| const padded = national.padEnd(9, " "); |
| const p1 = padded.slice(0, 1); |
| const p2 = padded.slice(1, 3); |
| const p3 = padded.slice(3, 5); |
| const p4 = padded.slice(5, 7); |
| const p5 = padded.slice(7, 9); |
| return `+33 ${p1} ${p2} ${p3} ${p4} ${p5}`.trim(); |
| } |
| |
| if (isBelgian) { |
| if (national.length === 9 || national.startsWith("45") || national.startsWith("46") || national.startsWith("47") || national.startsWith("48") || national.startsWith("49")) { |
| const padded = national.padEnd(9, " "); |
| const p1 = padded.slice(0, 3); |
| const p2 = padded.slice(3, 5); |
| const p3 = padded.slice(5, 7); |
| const p4 = padded.slice(7, 9); |
| return `+32 ${p1} ${p2} ${p3} ${p4}`.trim(); |
| } else { |
| const startsWithTwo = national.startsWith("2") || national.startsWith("3") || national.startsWith("4") || national.startsWith("9"); |
| if (startsWithTwo && national.length === 8) { |
| const padded = national.padEnd(8, " "); |
| const p1 = padded.slice(0, 1); |
| const p2 = padded.slice(1, 4); |
| const p3 = padded.slice(4, 6); |
| const p4 = padded.slice(6, 8); |
| return `+32 ${p1} ${p2} ${p3} ${p4}`.trim(); |
| } else { |
| const padded = national.padEnd(8, " "); |
| const p1 = padded.slice(0, 2); |
| const p2 = padded.slice(2, 4); |
| const p3 = padded.slice(4, 6); |
| const p4 = padded.slice(6, 8); |
| return `+32 ${p1} ${p2} ${p3} ${p4}`.trim(); |
| } |
| } |
| } |
| |
| if (isSwiss) { |
| const padded = national.padEnd(9, " "); |
| const p1 = padded.slice(0, 2); |
| const p2 = padded.slice(2, 5); |
| const p3 = padded.slice(5, 7); |
| const p4 = padded.slice(7, 9); |
| return `+41 ${p1} ${p2} ${p3} ${p4}`.trim(); |
| } |
| |
| if (isLux) { |
| if (national.length >= 6) { |
| const padded = national.padEnd(9, " "); |
| const p1 = padded.slice(0, 3); |
| const p2 = padded.slice(3, 6); |
| const p3 = padded.slice(6, 9).trim(); |
| const space3 = p3 ? " " + p3 : ""; |
| return `+352 ${p1} ${p2}${space3}`.trim(); |
| } |
| } |
| |
| return phone; |
| } |
|
|
| export default function App() { |
| const [searchQuery, setSearchQuery] = useState(""); |
| const [loading, setLoading] = useState(false); |
| const [activeProfile, setActiveProfile] = useState<CustomerProfile | null>(null); |
| |
| const [showOtherAddresses, setShowOtherAddresses] = useState(false); |
| const [showOtherCompanies, setShowOtherCompanies] = useState(false); |
|
|
| const [odooResults, setOdooResults] = useState<OdooPartner[]>([]); |
| const [prestaResult, setPrestaResult] = useState<PrestaCustomer | null>(null); |
| const [prestaResults, setPrestaResults] = useState<PrestaCustomer[]>([]); |
| const [error, setError] = useState<string | null>(null); |
| const [isDemoData, setIsDemoData] = useState(false); |
| const [copiedField, setCopiedField] = useState<string | null>(null); |
|
|
| |
| const handleSearch = async (phoneToSearch: string) => { |
| if (!phoneToSearch) return; |
| |
| const cleanPhone = phoneToSearch.trim().replace(/[\s\-\.()\/]/g, ""); |
| if (!cleanPhone) return; |
|
|
| setLoading(true); |
| setError(null); |
| setIsDemoData(false); |
| |
| setShowOtherAddresses(false); |
| setShowOtherCompanies(false); |
|
|
| try { |
| const res = await fetch(`/api/search?phone=${encodeURIComponent(cleanPhone)}`); |
| if (!res.ok) { |
| throw new Error(`Erreur serveur (${res.status})`); |
| } |
| |
| const data = await res.json(); |
| |
| const odooList: OdooPartner[] = data.odoo || []; |
| const psMatch: PrestaCustomer | null = data.ps || null; |
| const psList: PrestaCustomer[] = data.psList || (psMatch ? [psMatch] : []); |
|
|
| setOdooResults(odooList); |
| setPrestaResult(psMatch); |
| setPrestaResults(psList); |
|
|
| if (odooList.length > 0 || psMatch || psList.length > 0) { |
| const activePs = psMatch || psList[0] || null; |
| if (activePs && !prestaResult) { |
| setPrestaResult(activePs); |
| } |
| |
| const activeEmail = activePs?.email || odooList[0]?.email || ""; |
| |
| const matchingOdoo = odooList.find(partner => { |
| const pEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| const actEmail = activeEmail.trim().toLowerCase(); |
| return pEmail && actEmail && pEmail === actEmail; |
| }) || odooList[0]; |
| |
| const firstname = formatFirstname(activePs?.firstname || matchingOdoo?.name?.split(" ")[0] || ""); |
| const lastname = formatLastname(activePs?.lastname || matchingOdoo?.name?.split(" ").slice(1).join(" ") || "Client Odoo"); |
| |
| const addressesList: { street: string; zip: string; city: string; isLatest?: boolean }[] = []; |
| const seenAddr = new Set<string>(); |
| |
| if (data.latestOdooOrderAddress) { |
| const lat = data.latestOdooOrderAddress; |
| const s = lat.street ? String(lat.street).trim() : ""; |
| const z = lat.zip ? String(lat.zip).trim() : ""; |
| const c = lat.city ? String(lat.city).trim() : ""; |
| |
| if (s || c) { |
| const key = `${s.toLowerCase()}|${z.toLowerCase()}|${c.toLowerCase()}`; |
| seenAddr.add(key); |
| addressesList.push({ |
| street: s, |
| zip: z, |
| city: c, |
| isLatest: true |
| }); |
| } |
| } |
|
|
| odooList.forEach((partner) => { |
| const partnerEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| const actEmail = activeEmail.trim().toLowerCase(); |
| if (partnerEmail && actEmail && partnerEmail !== actEmail) return; |
| |
| const s = partner.street ? String(partner.street).trim() : ""; |
| const z = partner.zip ? String(partner.zip).trim() : ""; |
| const c = partner.city ? String(partner.city).trim() : ""; |
| |
| if (s || c) { |
| const key = `${s.toLowerCase()}|${z.toLowerCase()}|${c.toLowerCase()}`; |
| if (!seenAddr.has(key)) { |
| seenAddr.add(key); |
| addressesList.push({ |
| street: s, |
| zip: z, |
| city: c, |
| isLatest: addressesList.length === 0 |
| }); |
| } |
| } |
| }); |
|
|
| const companiesList: string[] = []; |
| const seenComp = new Set<string>(); |
| |
| if (data.latestOdooOrderAddress?.company) { |
| const cName = data.latestOdooOrderAddress.company.trim(); |
| if (cName && !seenComp.has(cName.toLowerCase())) { |
| seenComp.add(cName.toLowerCase()); |
| companiesList.push(cName); |
| } |
| } |
|
|
| if (activePs?.company && activePs.email?.trim().toLowerCase() === activeEmail.trim().toLowerCase()) { |
| const cName = activePs.company.trim(); |
| if (cName && !seenComp.has(cName.toLowerCase())) { |
| seenComp.add(cName.toLowerCase()); |
| companiesList.push(cName); |
| } |
| } |
| |
| odooList.forEach((partner) => { |
| const partnerEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| const actEmail = activeEmail.trim().toLowerCase(); |
| if (partnerEmail && actEmail && partnerEmail !== actEmail) return; |
| |
| if (partner.parent_id && partner.parent_id[1]) { |
| const cName = partner.parent_id[1].trim(); |
| if (cName && !seenComp.has(cName.toLowerCase())) { |
| seenComp.add(cName.toLowerCase()); |
| companiesList.push(cName); |
| } |
| } |
| }); |
|
|
| const mainStreet = matchingOdoo?.street ? String(matchingOdoo.street) : ""; |
| const mainZip = matchingOdoo?.zip ? String(matchingOdoo.zip) : ""; |
| const mainCity = matchingOdoo?.city ? String(matchingOdoo.city) : ""; |
| |
| if (addressesList.length === 0 && (mainStreet || mainCity)) { |
| addressesList.push({ |
| street: mainStreet, |
| zip: mainZip, |
| city: mainCity, |
| isLatest: true |
| }); |
| } |
|
|
| const phone = formatPhoneFrench(activePs?.phone || matchingOdoo?.mobile || matchingOdoo?.phone || phoneToSearch, phoneToSearch); |
| const email = activeEmail; |
| const groupName = activePs?.group_name || "Client Standard"; |
|
|
| setActiveProfile({ |
| firstname, |
| lastname, |
| company: companiesList[0] || "Particulier", |
| phone, |
| email, |
| prestaId: activePs?.id, |
| odooId: matchingOdoo?.id, |
| street: addressesList[0]?.street || mainStreet || "", |
| zip: addressesList[0]?.zip || mainZip || "", |
| city: addressesList[0]?.city || mainCity || "", |
| groupName, |
| addresses: addressesList, |
| companies: companiesList |
| }); |
| } else { |
| if (cleanPhone.includes("0780981998") || cleanPhone.includes("780981998")) { |
| setActiveProfile(FALLBACK_JIMMY); |
| setIsDemoData(true); |
| setPrestaResults([ |
| { id: "2201", firstname: "Jimmy", lastname: "COCQUEREL", email: "jcocquerel@tousergo.com", company: "TousErgo", phones: ["+33780981998"] }, |
| { id: "3045", firstname: "Jimmy (Pro)", lastname: "COCQUEREL", email: "jimmy.pro@tousergo.com", company: "ErgoSourcing SAS", phones: ["+32466887672"] } |
| ]); |
| setPrestaResult({ id: "2201", firstname: "Jimmy", lastname: "COCQUEREL", email: "jcocquerel@tousergo.com", company: "TousErgo", phones: ["+33780981998"] }); |
| } else { |
| setActiveProfile(null); |
| setPrestaResults([]); |
| setPrestaResult(null); |
| } |
| } |
| } catch (err: any) { |
| console.error("Erreur de recherche:", err); |
| setError("Impossible de contacter le serveur de recherche en temps réel."); |
| |
| if (cleanPhone.includes("0780981998") || cleanPhone.includes("780981998")) { |
| setActiveProfile(FALLBACK_JIMMY); |
| setIsDemoData(true); |
| setPrestaResults([ |
| { id: "2201", firstname: "Jimmy", lastname: "COCQUEREL", email: "jcocquerel@tousergo.com", company: "TousErgo", phones: ["+33780981998"] }, |
| { id: "3045", firstname: "Jimmy (Pro)", lastname: "COCQUEREL", email: "jimmy.pro@tousergo.com", company: "ErgoSourcing SAS", phones: ["+32466887672"] } |
| ]); |
| setPrestaResult({ id: "2201", firstname: "Jimmy", lastname: "COCQUEREL", email: "jcocquerel@tousergo.com", company: "TousErgo", phones: ["+33780981998"] }); |
| } else if (cleanPhone.includes("0612345678") || cleanPhone === "0612345678") { |
| setActiveProfile(FALLBACK_DEFAULT); |
| setIsDemoData(true); |
| setPrestaResults([ |
| { id: "8834", firstname: "Sophie", lastname: "LEFEBVRE", email: "sophie.lefebvre@example.fr", company: "Ergomed SAS", phones: ["0612345678"] } |
| ]); |
| setPrestaResult({ id: "8834", firstname: "Sophie", lastname: "LEFEBVRE", email: "sophie.lefebvre@example.fr", company: "Ergomed SAS", phones: ["0612345678"] }); |
| } else { |
| setActiveProfile({ |
| firstname: formatFirstname("Client"), |
| lastname: formatLastname("Inconnu"), |
| company: "Non renseigné", |
| phone: formatPhoneFrench(phoneToSearch, phoneToSearch), |
| email: "Aucun e-mail trouvé", |
| }); |
| setIsDemoData(true); |
| setPrestaResults([]); |
| setPrestaResult(null); |
| } |
| } finally { |
| setLoading(false); |
| } |
| }; |
|
|
| useEffect(() => { |
| const params = new URLSearchParams(window.location.search); |
| const phoneParam = params.get("phone"); |
| if (phoneParam) { |
| const clean = phoneParam.trim(); |
| setSearchQuery(clean); |
| handleSearch(clean); |
| } |
| |
| |
| }, []); |
|
|
|
|
| const onSubmitSearch = (e: React.FormEvent) => { |
| e.preventDefault(); |
| if (searchQuery.trim()) { |
| const newUrl = `${window.location.pathname}?phone=${encodeURIComponent(searchQuery.trim())}`; |
| window.history.replaceState({}, "", newUrl); |
| handleSearch(searchQuery.trim()); |
| } |
| }; |
|
|
| const fetchLatestOrderDetails = async (odooId?: string | number, email?: string) => { |
| try { |
| let queryParam = ""; |
| if (odooId) { |
| queryParam = `odooId=${encodeURIComponent(odooId)}`; |
| } else if (email) { |
| queryParam = `email=${encodeURIComponent(email)}`; |
| } else { |
| return; |
| } |
| |
| const res = await fetch(`/api/search?${queryParam}`); |
| if (res.ok) { |
| const data = await res.json(); |
| if (data.latestOdooOrderAddress) { |
| const lat = data.latestOdooOrderAddress; |
| const s = lat.street ? String(lat.street).trim() : ""; |
| const z = lat.zip ? String(lat.zip).trim() : ""; |
| const c = lat.city ? String(lat.city).trim() : ""; |
| |
| setActiveProfile(prev => { |
| if (!prev) return null; |
| const updatedAddresses = prev.addresses ? [...prev.addresses] : []; |
| const keyToFind = `${s.toLowerCase()}|${z.toLowerCase()}|${c.toLowerCase()}`; |
| |
| const filtered = updatedAddresses.filter(addr => { |
| const k = `${(addr.street || "").toLowerCase()}|${(addr.zip || "").toLowerCase()}|${(addr.city || "").toLowerCase()}`; |
| return k !== keyToFind; |
| }); |
| |
| if (s || c) { |
| filtered.unshift({ |
| street: s, |
| zip: z, |
| city: c, |
| isLatest: true |
| }); |
| } |
| |
| const updatedCompanies = prev.companies ? [...prev.companies] : []; |
| if (lat.company) { |
| const cName = lat.company.trim(); |
| const filteredComp = updatedCompanies.filter(comp => comp.toLowerCase() !== cName.toLowerCase()); |
| filteredComp.unshift(cName); |
| return { |
| ...prev, |
| addresses: filtered, |
| companies: filteredComp |
| }; |
| } |
| |
| return { |
| ...prev, |
| addresses: filtered |
| }; |
| }); |
| } |
| } |
| } catch (err) { |
| console.error("Error fetching latest order details:", err); |
| } |
| }; |
|
|
| const handleSelectPrestaCustomer = (ps: PrestaCustomer) => { |
| setPrestaResult(ps); |
| const activeEmail = ps.email || ""; |
| const formattedFirst = formatFirstname(ps.firstname); |
| const formattedLast = formatLastname(ps.lastname); |
| const phone = formatPhoneFrench(ps.phone || (ps.phones && ps.phones[0]) || searchQuery, searchQuery); |
| |
| const matchingOdoo = odooResults.find(partner => { |
| const pEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| const actEmail = activeEmail.trim().toLowerCase(); |
| return pEmail && actEmail && pEmail === actEmail; |
| }) || odooResults[0]; |
| |
| const addressesList: { street: string; zip: string; city: string; isLatest?: boolean }[] = []; |
| const seenAddr = new Set<string>(); |
| |
| odooResults.forEach((partner) => { |
| const partnerEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| if (partnerEmail && activeEmail && partnerEmail !== activeEmail.trim().toLowerCase()) return; |
| |
| const s = partner.street ? String(partner.street).trim() : ""; |
| const z = partner.zip ? String(partner.zip).trim() : ""; |
| const c = partner.city ? String(partner.city).trim() : ""; |
| |
| if (s || c) { |
| const key = `${s.toLowerCase()}|${z.toLowerCase()}|${c.toLowerCase()}`; |
| if (!seenAddr.has(key)) { |
| seenAddr.add(key); |
| addressesList.push({ |
| street: s, |
| zip: z, |
| city: c, |
| isLatest: addressesList.length === 0 |
| }); |
| } |
| } |
| }); |
| |
| const companiesList: string[] = []; |
| const seenComp = new Set<string>(); |
| |
| if (ps.company) { |
| const cName = ps.company.trim(); |
| if (cName && !seenComp.has(cName.toLowerCase())) { |
| seenComp.add(cName.toLowerCase()); |
| companiesList.push(cName); |
| } |
| } |
| |
| odooResults.forEach((partner) => { |
| const partnerEmail = partner.email ? String(partner.email).trim().toLowerCase() : ""; |
| if (partnerEmail && activeEmail && partnerEmail !== activeEmail.trim().toLowerCase()) return; |
| |
| if (partner.parent_id && partner.parent_id[1]) { |
| const cName = partner.parent_id[1].trim(); |
| if (cName && !seenComp.has(cName.toLowerCase())) { |
| seenComp.add(cName.toLowerCase()); |
| companiesList.push(cName); |
| } |
| } |
| }); |
| |
| const mainStreet = matchingOdoo?.street ? String(matchingOdoo.street) : ""; |
| const mainZip = matchingOdoo?.zip ? String(matchingOdoo.zip) : ""; |
| const mainCity = matchingOdoo?.city ? String(matchingOdoo.city) : ""; |
| |
| if (addressesList.length === 0 && (mainStreet || mainCity)) { |
| addressesList.push({ |
| street: mainStreet, |
| zip: mainZip, |
| city: mainCity, |
| isLatest: true |
| }); |
| } |
| |
| setActiveProfile({ |
| firstname: formattedFirst, |
| lastname: formattedLast, |
| company: companiesList[0] || "Particulier", |
| phone, |
| email: activeEmail, |
| prestaId: ps.id, |
| odooId: matchingOdoo?.id, |
| street: addressesList[0]?.street || mainStreet || "", |
| zip: addressesList[0]?.zip || mainZip || "", |
| city: addressesList[0]?.city || mainCity || "", |
| groupName: ps.group_name || "Client Standard", |
| addresses: addressesList, |
| companies: companiesList |
| }); |
| |
| if (ps.email) { |
| fetchLatestOrderDetails(matchingOdoo?.id || undefined, ps.email); |
| } |
| }; |
|
|
| const handleCopy = (text: string, fieldName: string) => { |
| if (!text) return; |
| navigator.clipboard.writeText(text); |
| setCopiedField(fieldName); |
| setTimeout(() => setCopiedField(null), 1500); |
| }; |
|
|
| const getPrestashopUrl = (id?: string | number) => { |
| const custId = id || "2201"; |
| return `https://www.tousergo.com/admin_ps_t_fr/index.php?controller=AdminCustomers&id_customer=${custId}&viewcustomer`; |
| }; |
|
|
| const getOdooUrl = (id?: string | number) => { |
| const partnerId = id || "42"; |
| return `https://tousergo.eggs-solutions.fr/web#cids=1&id=${partnerId}&model=res.partner&view_type=form&menu_id=`; |
| }; |
|
|
| const companies = activeProfile?.companies || []; |
| const companiesToDisplay = companies.slice(0, 2); |
| const otherCompanies = companies.slice(2); |
|
|
| const addresses = activeProfile?.addresses || []; |
| const latestAddress = addresses[0] || (activeProfile ? { |
| street: activeProfile.street || "", |
| zip: activeProfile.zip || "", |
| city: activeProfile.city || "" |
| } : null); |
|
|
| const otherAddresses = addresses.slice(1); |
|
|
| return ( |
| <div className="w-full min-h-screen bg-slate-50 flex flex-col font-sans text-slate-800 pb-12"> |
| |
| {/* ══════════ EN-TÊTE / BANDEAU BLEU PROFESSIONNEL ══════════ */} |
| <header className="h-14 bg-tousergo-navy border-b border-tousergo-rust/20 text-white flex items-center px-6 justify-between shadow-sm flex-shrink-0"> |
| <div className="flex items-center gap-3"> |
| <div className="bg-white p-1 rounded-lg flex items-center justify-center shadow-sm"> |
| <img |
| src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRU6l9MDfTrCzuUVgdop5vjMkuYMvcwdm1enj1y3qfThsnK6jzy-5vUoOo&s=10" |
| alt="TOUS ERGO" |
| className="h-8 w-auto object-contain" |
| referrerPolicy="no-referrer" |
| /> |
| </div> |
| <div> |
| <h1 className="text-sm font-black tracking-wide uppercase text-white leading-none"> |
| TOUS ERGO DASHBOARD |
| </h1> |
| <p className="text-[10px] text-tousergo-peach font-bold tracking-wider mt-0.5"> |
| V.1 BÊTA TEST - by JCB |
| </p> |
| </div> |
| </div> |
| |
| {/* Moteur de recherche dédié intégré au bandeau */} |
| <div className="flex-1 max-w-md mx-8"> |
| <form onSubmit={onSubmitSearch} className="relative flex items-center"> |
| <div className="absolute left-3.5 text-slate-400"> |
| <Search className="w-4 h-4" /> |
| </div> |
| <input |
| type="text" |
| value={searchQuery} |
| onChange={(e) => setSearchQuery(e.target.value)} |
| placeholder="Rechercher par numéro de téléphone (ex: 0320819389)..." |
| className="w-full bg-slate-800 hover:bg-slate-750 focus:bg-white focus:text-slate-950 placeholder-slate-400 text-xs pl-10 pr-24 py-2 rounded-xl border border-slate-700 focus:border-tousergo-rust transition-all outline-none font-medium text-slate-200" |
| /> |
| <button |
| type="submit" |
| className="absolute right-1.5 bg-tousergo-rust hover:brightness-110 text-white font-bold text-[10px] px-3 py-1.5 rounded-lg transition-colors cursor-pointer shadow-sm uppercase tracking-wider" |
| > |
| Rechercher |
| </button> |
| </form> |
| </div> |
| |
| {/* Lien de test 3CX */} |
| <div className="hidden md:flex items-center gap-2 text-xs"> |
| <div className="px-2.5 py-1 bg-slate-800 border border-slate-700 rounded-lg text-slate-300 font-mono text-[10px] flex items-center gap-1.5"> |
| <span className="w-1.5 h-1.5 rounded-full bg-tousergo-rust animate-pulse" /> |
| <span>3CX Link: ?phone={searchQuery || "0780981998"}</span> |
| </div> |
| </div> |
| </header> |
| |
| {/* ══════════ ZONE PRINCIPALE DE CONTEXTE ══════════ */} |
| <main className="flex-1 p-6 max-w-5xl mx-auto w-full flex flex-col gap-6"> |
| |
| {/* Indicateur de chargement */} |
| {loading && ( |
| <div className="flex-1 flex flex-col items-center justify-center p-12 bg-white rounded-2xl border border-slate-200 shadow-sm gap-4"> |
| <Loader2 className="w-10 h-10 text-tousergo-rust animate-spin" /> |
| <div className="text-center"> |
| <h3 className="text-sm font-extrabold text-slate-800">Recherche dans les bases de données...</h3> |
| <p className="text-xs text-slate-400 mt-1">Interrogation en temps réel de PrestaShop et Odoo</p> |
| </div> |
| </div> |
| )} |
| |
| {/* Contenu de la fiche client */} |
| {!loading && ( |
| <div className="flex-1 grid grid-cols-1 md:grid-cols-5 gap-6"> |
| |
| {/* COLONNE GAUCHE : LA FICHE CLIENT PRINCIPALE (3/5) */} |
| <div className="md:col-span-3 flex flex-col gap-5"> |
| |
| {/* SÉLECTEUR DE COMPTE PRESTASHOP MULTIPLE */} |
| {prestaResults.length > 1 && ( |
| <div className="bg-tousergo-peach/15 border border-tousergo-peach rounded-2xl p-5 shadow-sm flex flex-col gap-3"> |
| <div className="flex items-start gap-3"> |
| <AlertCircle className="w-5 h-5 text-tousergo-rust mt-0.5 flex-shrink-0" /> |
| <div> |
| <h4 className="text-xs font-black uppercase text-tousergo-navy tracking-wider"> |
| Plusieurs comptes PrestaShop associés à ce numéro |
| </h4> |
| <p className="text-xs text-tousergo-navy/80 mt-0.5"> |
| Veuillez sélectionner la fiche client PrestaShop active pour cet appel : |
| </p> |
| </div> |
| </div> |
| |
| <div className="grid grid-cols-1 gap-2 mt-1"> |
| {prestaResults.map((ps) => { |
| const isSelected = prestaResult?.id === ps.id; |
| return ( |
| <button |
| key={ps.id} |
| type="button" |
| onClick={() => handleSelectPrestaCustomer(ps)} |
| className={`w-full text-left p-3 rounded-xl border transition-all flex items-center justify-between cursor-pointer ${ |
| isSelected |
| ? "bg-tousergo-rust border-tousergo-rust text-white shadow-sm ring-2 ring-tousergo-rust/10" |
| : "bg-white hover:bg-slate-50 border-slate-200 text-slate-800 hover:border-slate-300" |
| }`} |
| > |
| <div className="flex flex-col gap-0.5 min-w-0"> |
| <div className="flex items-center gap-2 flex-wrap"> |
| <span className={`text-xs font-black ${isSelected ? "text-white" : "text-slate-900"}`}> |
| {formatFirstname(ps.firstname)} {formatLastname(ps.lastname)} |
| </span> |
| <span className={`text-[10px] font-mono px-1.5 py-0.2 rounded ${ |
| isSelected ? "bg-tousergo-navy text-tousergo-peach" : "bg-slate-100 text-slate-600" |
| }`}> |
| #{ps.id} |
| </span> |
| {ps.company && ( |
| <span className={`text-[9px] font-bold px-1.5 py-0.2 rounded ${ |
| isSelected ? "bg-tousergo-navy text-tousergo-pink" : "bg-emerald-50 text-emerald-800 border border-emerald-100" |
| }`}> |
| {ps.company} |
| </span> |
| )} |
| </div> |
| <span className={`text-[11px] truncate ${isSelected ? "text-tousergo-peach" : "text-slate-500"}`}> |
| {ps.email} |
| </span> |
| </div> |
| <div className="flex-shrink-0 ml-3"> |
| {isSelected ? ( |
| <CheckCircle className="w-5 h-5 text-white" /> |
| ) : ( |
| <span className="text-[10px] font-extrabold uppercase tracking-wider text-tousergo-rust hover:brightness-110"> |
| Choisir |
| </span> |
| )} |
| </div> |
| </button> |
| ); |
| })} |
| </div> |
| </div> |
| )} |
| |
| {activeProfile ? ( |
| <div className="bg-white border border-slate-200 rounded-2xl shadow-sm overflow-hidden flex flex-col"> |
| |
| {/* Header de la carte client */} |
| <div className="p-6 bg-tousergo-navy text-white flex items-center justify-between"> |
| <div className="flex items-center gap-4"> |
| <div className="w-14 h-14 rounded-2xl bg-tousergo-rust text-white flex items-center justify-center font-black text-2xl shadow-md border border-tousergo-peach/20"> |
| {((activeProfile.firstname?.[0] || "") + (activeProfile.lastname?.[0] || "")).toUpperCase()} |
| </div> |
| <div> |
| <div className="text-[10px] uppercase font-black tracking-widest text-tousergo-peach"> |
| Fiche Client Active |
| </div> |
| <h2 className="text-xl font-extrabold tracking-tight mt-0.5"> |
| {activeProfile.firstname} {activeProfile.lastname} |
| </h2> |
| </div> |
| </div> |
| {isDemoData && ( |
| <span className="px-2.5 py-0.5 bg-amber-500/20 border border-amber-500/30 rounded text-[9px] font-black uppercase tracking-wider text-amber-300"> |
| Mode Secours |
| </span> |
| )} |
| </div> |
| |
| {/* Corps avec les informations principales */} |
| <div className="p-6 space-y-5 flex-1"> |
| |
| {/* ID Client Presta */} |
| <div className="pb-4 border-b border-slate-100"> |
| <div className="p-3.5 bg-tousergo-peach/20 rounded-xl border border-tousergo-peach/60 flex items-center justify-between"> |
| <div className="flex items-center gap-3 text-tousergo-rust"> |
| <Tag className="w-4 h-4 text-tousergo-rust" /> |
| <span className="text-[10px] uppercase font-black text-tousergo-navy tracking-wider">ID Client PrestaShop</span> |
| </div> |
| <div className="flex items-center gap-3"> |
| <span className="text-sm font-black text-tousergo-navy font-mono"> |
| {activeProfile.prestaId ? `#${activeProfile.prestaId}` : "Non synchronisé"} |
| </span> |
| {activeProfile.prestaId && ( |
| <button |
| type="button" |
| onClick={() => handleCopy(String(activeProfile.prestaId), "prestaId")} |
| className="p-1 hover:bg-tousergo-peach/40 rounded text-tousergo-navy hover:text-tousergo-rust transition-colors cursor-pointer" |
| title="Copier l'ID PrestaShop" |
| > |
| {copiedField === "prestaId" ? <Check className="w-3.5 h-3.5 text-emerald-600" /> : <Copy className="w-3.5 h-3.5" />} |
| </button> |
| )} |
| </div> |
| </div> |
| </div> |
| |
| {/* Société */} |
| <div className="flex flex-col gap-2 pb-4 border-b border-slate-100"> |
| <div className="flex items-start gap-3"> |
| <div className="mt-0.5 p-1.5 bg-tousergo-peach/40 text-tousergo-rust rounded-lg"> |
| <Building2 className="w-4 h-4" /> |
| </div> |
| <div className="flex-1"> |
| <span className="text-[10px] uppercase font-black text-slate-400 tracking-wider">Sociétés / Entités</span> |
| |
| <div className="mt-2 space-y-2"> |
| {companiesToDisplay.length > 0 ? ( |
| companiesToDisplay.map((comp, idx) => ( |
| <div key={idx} className="flex items-center gap-2 flex-wrap"> |
| <span className="text-sm font-bold text-slate-800 bg-slate-100 px-2.5 py-1 rounded-lg border border-slate-200 inline-block"> |
| {comp} |
| </span> |
| {idx === 0 && ( |
| <span className="text-[9px] font-extrabold uppercase bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded border border-emerald-200"> |
| Dernière active |
| </span> |
| )} |
| {idx === 1 && ( |
| <span className="text-[9px] font-semibold bg-slate-100 text-slate-400 px-1.5 py-0.5 rounded border border-slate-200"> |
| Secondaire |
| </span> |
| )} |
| </div> |
| )) |
| ) : ( |
| <div className="text-sm font-bold text-slate-500 italic">Particulier</div> |
| )} |
| </div> |
| </div> |
| </div> |
| |
| {/* Accordéon Autres Sociétés */} |
| {otherCompanies.length > 0 && ( |
| <div className="ml-10 mt-1"> |
| <button |
| type="button" |
| onClick={() => setShowOtherCompanies(!showOtherCompanies)} |
| className="flex items-center gap-1.5 text-xs text-tousergo-rust hover:text-tousergo-rust/85 font-bold transition-colors focus:outline-none" |
| > |
| <span> |
| {showOtherCompanies ? "Masquer les autres sociétés" : `Autres sociétés (${otherCompanies.length})`} |
| </span> |
| {showOtherCompanies ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />} |
| </button> |
| |
| {showOtherCompanies && ( |
| <div className="mt-2 pl-2 border-l-2 border-slate-200 space-y-1 bg-slate-50 p-2 rounded-xl border border-slate-100"> |
| {otherCompanies.map((comp, idx) => ( |
| <div key={idx} className="text-xs font-semibold text-slate-600 flex items-center gap-1.5 py-1"> |
| <span className="w-1.5 h-1.5 rounded-full bg-tousergo-rust" /> |
| <span>{comp}</span> |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| |
| {/* Téléphone */} |
| <div className="flex items-start gap-3 pb-4 border-b border-slate-100"> |
| <div className="mt-0.5 p-1.5 bg-emerald-50 text-emerald-600 rounded-lg"> |
| <Phone className="w-4 h-4" /> |
| </div> |
| <div className="flex-1"> |
| <span className="text-[10px] uppercase font-black text-slate-400 tracking-wider">Téléphone</span> |
| <div className="flex items-center gap-2 mt-0.5"> |
| <span className="text-sm font-bold font-mono text-slate-800"> |
| {activeProfile.phone} |
| </span> |
| <button |
| type="button" |
| onClick={() => handleCopy(activeProfile.phone, "phone")} |
| className="p-1 hover:bg-slate-100 rounded text-slate-400 hover:text-slate-700 transition-colors cursor-pointer" |
| title="Copier le numéro" |
| > |
| {copiedField === "phone" ? <Check className="w-3.5 h-3.5 text-emerald-600" /> : <Copy className="w-3.5 h-3.5" />} |
| </button> |
| </div> |
| </div> |
| </div> |
| |
| {/* E-mail */} |
| <div className="flex items-start gap-3 pb-4 border-b border-slate-100"> |
| <div className="mt-0.5 p-1.5 bg-sky-50 text-sky-600 rounded-lg"> |
| <Mail className="w-4 h-4" /> |
| </div> |
| <div className="flex-1"> |
| <span className="text-[10px] uppercase font-black text-slate-400 tracking-wider">E-mail</span> |
| <div className="flex items-center gap-2 mt-0.5"> |
| <span className="text-sm font-bold text-slate-800 select-all truncate"> |
| {activeProfile.email || "Non spécifié"} |
| </span> |
| {activeProfile.email && ( |
| <button |
| type="button" |
| onClick={() => handleCopy(activeProfile.email, "email")} |
| className="p-1 hover:bg-slate-100 rounded text-slate-400 hover:text-slate-700 transition-colors cursor-pointer" |
| title="Copier l'adresse e-mail" |
| > |
| {copiedField === "email" ? <Check className="w-3.5 h-3.5 text-emerald-600" /> : <Copy className="w-3.5 h-3.5" />} |
| </button> |
| )} |
| </div> |
| </div> |
| </div> |
| |
| {/* Adresse postale */} |
| <div className="flex flex-col gap-2"> |
| <div className="flex items-start gap-3"> |
| <div className="mt-0.5 p-1.5 bg-tousergo-peach/40 text-tousergo-rust rounded-lg"> |
| <MapPin className="w-4 h-4" /> |
| </div> |
| <div className="flex-1"> |
| <span className="text-[10px] uppercase font-black text-slate-400 tracking-wider">Adresse Postale</span> |
| |
| <div className="text-sm font-bold text-slate-800 mt-1"> |
| {latestAddress && (latestAddress.street || latestAddress.city) ? ( |
| <div className="bg-tousergo-peach/15 p-3 rounded-xl border border-tousergo-peach"> |
| {latestAddress.street && <div className="text-slate-800">{latestAddress.street}</div>} |
| {(latestAddress.zip || latestAddress.city) && ( |
| <div className="mt-0.5 text-slate-700"> |
| {latestAddress.zip} {latestAddress.city} |
| </div> |
| )} |
| <span className="text-[9px] font-extrabold uppercase bg-tousergo-rust text-white px-1.5 py-0.5 rounded border border-tousergo-rust inline-block mt-2"> |
| Dernière utilisée |
| </span> |
| </div> |
| ) : ( |
| <span className="text-slate-400 font-normal italic">Adresse non renseignée</span> |
| )} |
| </div> |
| </div> |
| </div> |
| |
| {/* Accordéon Autres Adresses */} |
| {otherAddresses.length > 0 && ( |
| <div className="ml-10 mt-1"> |
| <button |
| type="button" |
| onClick={() => setShowOtherAddresses(!showOtherAddresses)} |
| className="flex items-center gap-1.5 text-xs text-tousergo-rust hover:text-tousergo-rust/85 font-bold transition-colors focus:outline-none" |
| > |
| <span> |
| {showOtherAddresses ? "Masquer les autres adresses" : `Autres adresses (${otherAddresses.length})`} |
| </span> |
| {showOtherAddresses ? <ChevronUp className="w-3.5 h-3.5" /> : <ChevronDown className="w-3.5 h-3.5" />} |
| </button> |
| |
| {showOtherAddresses && ( |
| <div className="mt-2 pl-2 border-l-2 border-tousergo-rust space-y-2 bg-slate-50 p-2.5 rounded-xl border border-slate-100"> |
| {otherAddresses.map((addr, idx) => ( |
| <div key={idx} className="text-xs text-slate-600 border-b border-slate-150 last:border-0 pb-1.5 last:pb-0"> |
| {addr.street && <div className="font-semibold text-slate-700">{addr.street}</div>} |
| {(addr.zip || addr.city) && <div className="text-slate-500 mt-0.5">{addr.zip} {addr.city}</div>} |
| </div> |
| ))} |
| </div> |
| )} |
| </div> |
| )} |
| </div> |
| |
| </div> |
| |
| {/* Actions Rapides d'ouverture de fiches directes */} |
| <div className="p-6 bg-slate-50 border-t border-slate-100 grid grid-cols-2 gap-3"> |
| <a |
| href={getPrestashopUrl(activeProfile.prestaId)} |
| target="_blank" |
| rel="noreferrer" |
| className="flex items-center justify-center gap-2 py-3 px-4 bg-tousergo-rust hover:opacity-95 text-white text-xs font-bold rounded-xl shadow-sm transition-all cursor-pointer text-center" |
| id="prestashop-btn" |
| > |
| <ExternalLink className="w-4 h-4 text-tousergo-peach" /> |
| Ouvrir PrestaShop |
| </a> |
| |
| <a |
| href={getOdooUrl(activeProfile.odooId)} |
| target="_blank" |
| rel="noreferrer" |
| className="flex items-center justify-center gap-2 py-3 px-4 bg-tousergo-navy hover:opacity-95 text-white text-xs font-bold rounded-xl shadow-sm transition-all cursor-pointer text-center" |
| id="odoo-btn" |
| > |
| <ExternalLink className="w-4 h-4 text-tousergo-peach" /> |
| Ouvrir Odoo |
| </a> |
| </div> |
| |
| </div> |
| ) : ( |
| <div className="bg-white border border-slate-200 rounded-2xl shadow-sm p-8 text-center flex flex-col items-center justify-center gap-3"> |
| <AlertCircle className="w-12 h-12 text-slate-400" /> |
| <div> |
| <h3 className="font-extrabold text-slate-800 text-sm">Aucune fiche sélectionnée</h3> |
| <p className="text-xs text-slate-400 mt-1 max-w-sm"> |
| Saisissez un numéro de téléphone dans la barre de recherche ci-dessus pour afficher la fiche client. |
| </p> |
| </div> |
| </div> |
| )} |
| |
| </div> |
| |
| {/* COLONNE DROITE : SYNCHRONISATION ET RECHERCHES ASSOCIEES (2/5) */} |
| <div className="md:col-span-2 flex flex-col gap-5"> |
| |
| {/* Résultat PrestaShop */} |
| <div className="bg-white border border-slate-200 rounded-2xl shadow-sm p-5"> |
| <div className="flex items-center justify-between pb-3 border-b border-slate-100 mb-3"> |
| <div className="flex items-center gap-2"> |
| <div className="w-2.5 h-2.5 rounded-full bg-tousergo-rust" /> |
| <h3 className="text-xs font-black uppercase text-slate-500 tracking-wider"> |
| Fiches PrestaShop ({prestaResults.length}) |
| </h3> |
| </div> |
| <span className="text-[10px] font-bold text-slate-400">Status API</span> |
| </div> |
| |
| {prestaResults.length > 0 ? ( |
| <div className="space-y-3"> |
| {prestaResults.map((ps) => { |
| const isActive = prestaResult?.id === ps.id; |
| return ( |
| <div key={ps.id} className={`p-3 rounded-xl border transition-all text-xs ${ |
| isActive |
| ? "bg-tousergo-peach/15 border-tousergo-peach ring-1 ring-tousergo-rust/10" |
| : "bg-slate-50 border-slate-100" |
| }`}> |
| <div className="flex items-center justify-between"> |
| <span className="font-bold text-slate-800"> |
| {formatFirstname(ps.firstname)} {formatLastname(ps.lastname)} |
| </span> |
| <span className="text-[9px] px-1.5 py-0.2 bg-tousergo-peach border border-tousergo-peach text-tousergo-rust rounded uppercase font-extrabold"> |
| ID: #{ps.id} |
| </span> |
| </div> |
| <div className="text-slate-500 text-[11px] mt-0.5">{ps.email}</div> |
| {ps.company && ( |
| <div className="text-slate-500 text-[11px] mt-1 font-semibold flex items-center gap-1"> |
| <Building2 className="w-3 h-3 text-slate-400" /> |
| {ps.company} |
| </div> |
| )} |
| |
| <div className="mt-2.5 pt-2 border-t border-slate-200/50 flex justify-between items-center gap-2"> |
| {isActive ? ( |
| <span className="text-[10px] text-emerald-600 font-extrabold flex items-center gap-1"> |
| <CheckCircle className="w-3 h-3" /> Fiche active |
| </span> |
| ) : ( |
| <span className="text-[10px] text-slate-400"> |
| Autre compte PrestaShop |
| </span> |
| )} |
| |
| <a |
| href={getPrestashopUrl(ps.id)} |
| target="_blank" |
| rel="noreferrer" |
| className="inline-flex items-center gap-0.5 text-[10px] text-tousergo-rust hover:brightness-110 font-bold transition-colors" |
| > |
| Ouvrir PrestaShop <ExternalLink className="w-2.5 h-2.5" /> |
| </a> |
| </div> |
| </div> |
| ); |
| })} |
| </div> |
| ) : ( |
| <div className="p-3 bg-tousergo-peach/20 border border-tousergo-peach/40 rounded-xl text-xs text-tousergo-rust italic"> |
| Aucun résultat PrestaShop trouvé pour ce numéro. |
| </div> |
| )} |
| </div> |
| |
| {/* Résultat Odoo */} |
| <div className="bg-white border border-slate-200 rounded-2xl shadow-sm p-5"> |
| <div className="flex items-center justify-between pb-3 border-b border-slate-100 mb-3"> |
| <div className="flex items-center gap-2"> |
| <div className="w-2.5 h-2.5 rounded-full bg-tousergo-navy" /> |
| <h3 className="text-xs font-black uppercase text-slate-500 tracking-wider"> |
| Fiches Odoo ({odooResults.length}) |
| </h3> |
| </div> |
| <span className="text-[10px] font-bold text-slate-400">Status API</span> |
| </div> |
| |
| {odooResults.length > 0 ? ( |
| <div className="space-y-2.5"> |
| {odooResults.map((partner) => { |
| const isActiveOdoo = partner.id === activeProfile?.odooId; |
| return ( |
| <div key={partner.id} className={`p-3 rounded-xl border transition-all text-xs ${ |
| isActiveOdoo |
| ? "bg-tousergo-pink/15 border-tousergo-pink/30 ring-1 ring-tousergo-navy/10" |
| : "bg-slate-50 border-slate-100" |
| }`}> |
| <div className="flex items-center justify-between"> |
| <span className="font-bold text-slate-800">{partner.name}</span> |
| <span className="text-[9px] px-1.5 py-0.2 bg-tousergo-peach border border-tousergo-peach text-tousergo-rust rounded uppercase font-extrabold"> |
| ID: #{partner.id} |
| </span> |
| </div> |
| {partner.email && <div className="text-slate-500 text-[11px] mt-0.5">{partner.email}</div>} |
| {partner.parent_id && ( |
| <div className="text-slate-500 text-[11px] mt-1 font-semibold flex items-center gap-1"> |
| <Building2 className="w-3 h-3 text-slate-400" /> |
| {partner.parent_id[1]} |
| </div> |
| )} |
| <div className="mt-2 pt-2 border-t border-slate-200/50 flex justify-between items-center"> |
| {isActiveOdoo ? ( |
| <span className="text-[10px] text-emerald-600 font-extrabold flex items-center gap-1"> |
| <CheckCircle className="w-3 h-3" /> Fiche active |
| </span> |
| ) : ( |
| <span className="text-[10px] text-slate-400"> |
| Autre fiche Odoo |
| </span> |
| )} |
| |
| <a |
| href={getOdooUrl(partner.id)} |
| target="_blank" |
| rel="noreferrer" |
| className="inline-flex items-center gap-0.5 text-[10px] text-tousergo-navy hover:text-tousergo-rust font-bold transition-colors" |
| > |
| Ouvrir Odoo <ExternalLink className="w-2.5 h-2.5" /> |
| </a> |
| </div> |
| </div> |
| ); |
| })} |
| </div> |
| ) : ( |
| <div className="p-3 bg-tousergo-pink/15 border border-tousergo-pink/20 rounded-xl text-xs text-tousergo-navy italic"> |
| Aucun partenaire Odoo trouvé pour ce numéro. |
| </div> |
| )} |
| </div> |
| |
| </div> |
| |
| </div> |
| )} |
| |
| </main> |
| |
| </div> |
| ); |
| } |
|
|