import { useState, useEffect, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import Layout from '../../components/Layout'; import { supabase } from '../../lib/supabase'; import { deleteCompanyData } from '../../lib/deleteHelpers'; import { restoreCompanyArchive } from '../../lib/archiveHelpers'; import { readPageCache, writePageCache } from '../../lib/pageCache'; import { syncSeafileFolders } from '../../lib/seafileFolders'; function getRoleLabel(role) { if (role === 'external') return 'Subcontractor'; if (role === 'client') return 'Client'; if (role === 'team') return 'Team'; return role || 'โ€”'; } export default function Companies() { const navigate = useNavigate(); const cached = readPageCache('team_companies'); const [companies, setCompanies] = useState(() => cached?.companies || []); const [profiles, setProfiles] = useState(() => cached?.profiles || []); const [companyMemberships, setCompanyMemberships] = useState(() => cached?.companyMemberships || []); const [loading, setLoading] = useState(() => !cached); const [showNew, setShowNew] = useState(false); const [newForm, setNewForm] = useState({ name: '', phone: '', address: '' }); const [showNewUser, setShowNewUser] = useState(false); const [userForm, setUserForm] = useState({ name: '', email: '', password: '', company_id: '', role: 'client' }); const [saving, setSaving] = useState(false); const [userError, setUserError] = useState(''); const [editingUserId, setEditingUserId] = useState(null); const [editUserVal, setEditUserVal] = useState(''); const [deletingUserId, setDeletingUserId] = useState(null); const [restoringArchive, setRestoringArchive] = useState(false); const [restoreStatus, setRestoreStatus] = useState(''); const [filterCompany, setFilterCompany] = useState(''); const [activeTab, setActiveTab] = useState('companies'); const restoreInputRef = useRef(null); async function load() { const [{ data: co }, { data: prof }, { data: memberships }] = await Promise.all([ supabase.from('companies').select('*').order('name'), supabase.from('profiles').select('id, name, email, company_id, role').in('role', ['client', 'external']).order('name'), supabase.from('company_members').select('company_id, profile_id'), ]); setCompanies(co || []); setProfiles(prof || []); setCompanyMemberships(memberships || []); writePageCache('team_companies', { companies: co || [], profiles: prof || [], companyMemberships: memberships || [] }); setLoading(false); } useEffect(() => { load(); }, []); const handleCreate = async (e) => { e.preventDefault(); if (!newForm.name.trim()) return; setSaving(true); const { data } = await supabase.from('companies').insert({ name: newForm.name.trim(), phone: newForm.phone.trim(), address: newForm.address.trim(), }).select().single(); setSaving(false); if (data) { syncSeafileFolders().catch((error) => console.warn('Seafile folder sync failed:', error.message)); setShowNew(false); setNewForm({ name: '', phone: '', address: '' }); navigate(`/companies/${data.id}`); } }; const handleDeleteCompany = async (company) => { if (!window.confirm(`Delete "${company.name}"? This will permanently delete all projects, jobs, files, and data for this company. This cannot be undone.`)) return; await deleteCompanyData(company.id); setCompanies(prev => prev.filter(c => c.id !== company.id)); load(); }; const handleRestoreArchive = async (e) => { const file = e.target.files?.[0]; e.target.value = ''; if (!file) return; setRestoringArchive(true); try { const result = await restoreCompanyArchive(file, { onProgress: setRestoreStatus }); await load(); const missing = []; if (result.missingProfiles.companyProfiles) missing.push(`${result.missingProfiles.companyProfiles} client profiles were not re-linked`); if (result.missingProfiles.projectMembers) missing.push(`${result.missingProfiles.projectMembers} external project memberships were skipped`); if (result.missingProfiles.taskAssignments) missing.push(`${result.missingProfiles.taskAssignments} task assignments were cleared`); if (result.missingProfiles.submissions) missing.push(`${result.missingProfiles.submissions} submission user links were cleared`); if (result.missingProfiles.invoices) missing.push(`${result.missingProfiles.invoices} invoice creator links were cleared`); alert( missing.length ? `Restored ${result.companyCount || 1} compan${(result.companyCount || 1) === 1 ? 'y' : 'ies'}.\n\nNote: ${missing.join('; ')}.` : `Restored ${result.companyCount || 1} compan${(result.companyCount || 1) === 1 ? 'y' : 'ies'} successfully.` ); } catch (error) { alert(`Restore failed: ${error.message}`); } finally { setRestoringArchive(false); setRestoreStatus(''); } }; const handleEditUserSave = async (userId) => { if (!editUserVal.trim()) return; await supabase.from('profiles').update({ name: editUserVal.trim() }).eq('id', userId); setProfiles(prev => prev.map(u => u.id === userId ? { ...u, name: editUserVal.trim() } : u)); setEditingUserId(null); }; const handleDeleteUser = async (user) => { if (!window.confirm(`Delete "${user.name}"? This will permanently remove their account. This cannot be undone.`)) return; setDeletingUserId(user.id); const { data, error } = await supabase.functions.invoke('delete-user', { body: { user_id: user.id } }); const errBody = error?.context ? await error.context.json().catch(() => null) : null; const errMsg = errBody?.error || data?.error || error?.message; if (errMsg) { alert(`Failed to delete user: ${errMsg}`); setDeletingUserId(null); return; } setProfiles(prev => prev.filter(u => u.id !== user.id)); setDeletingUserId(null); }; const handleCreateUser = async (e) => { e.preventDefault(); setUserError(''); setSaving(true); const { data, error } = await supabase.functions.invoke('create-user', { body: { name: userForm.name.trim(), email: userForm.email.trim(), password: userForm.password, role: userForm.role, company_id: userForm.role === 'client' ? (userForm.company_id || null) : null, }, }); setSaving(false); const errBody = error?.context ? await error.context.json().catch(() => null) : null; const errMsg = errBody?.error || data?.error || error?.message; if (errMsg) { setUserError(errMsg); return; } setShowNewUser(false); setUserForm({ name: '', email: '', password: '', company_id: '', role: 'client' }); syncSeafileFolders().catch((syncError) => console.warn('Seafile folder sync failed:', syncError.message)); load(); }; if (loading) return

Loading...

; const getProfileCompanyIds = (profile) => { const ids = new Set( companyMemberships .filter(membership => membership.profile_id === profile.id && profile.role === 'client') .map(membership => membership.company_id) ); if (profile.role === 'client' && profile.company_id) ids.add(profile.company_id); return [...ids]; }; const clientProfiles = profiles.filter(profile => profile.role === 'client'); const subcontractors = profiles.filter(profile => profile.role === 'external'); const unassigned = clientProfiles.filter(profile => getProfileCompanyIds(profile).length === 0); return (
Clients & Users
{companies.length} {companies.length !== 1 ? 'companies' : 'company'} ยท {clientProfiles.length} client user{clientProfiles.length !== 1 ? 's' : ''} ยท {subcontractors.length} subcontractor{subcontractors.length !== 1 ? 's' : ''} {unassigned.length > 0 && ( ยท {unassigned.length} unassigned client{unassigned.length !== 1 ? 's' : ''} )}
๐Ÿข
{companies.length}
Companies
๐Ÿ‘ฅ
{clientProfiles.length}
Client Users
๐Ÿงพ
{subcontractors.length}
Subcontractors
โš ๏ธ
{unassigned.length}
Unassigned Clients
{showNewUser && (
New User
setUserForm(f => ({ ...f, name: e.target.value }))} required autoFocus />
setUserForm(f => ({ ...f, email: e.target.value }))} required />
setUserForm(f => ({ ...f, password: e.target.value }))} required minLength={6} />
{userForm.role === 'client' && (
)} {userError &&

{userError}

}
)} {showNew && (
New Company
setNewForm(f => ({ ...f, name: e.target.value }))} required autoFocus />
setNewForm(f => ({ ...f, phone: e.target.value }))} />
setNewForm(f => ({ ...f, address: e.target.value }))} />
)}
{[ { id: 'companies', label: 'Companies' }, { id: 'clients', label: 'Clients' }, { id: 'subcontractors', label: 'Subcontractors' }, ].map((tab, index) => ( {index > 0 && |} ))}
{activeTab === 'companies' && ( )} {activeTab === 'clients' && ( )} {activeTab === 'subcontractors' && ( )}
{activeTab === 'companies' && ( <>
Restore Archive
{restoreStatus &&
{restoreStatus}
}
{companies.length === 0 ? (

No companies yet

Create a company to get started.

) : (
{companies.map(company => { const companyProfiles = clientProfiles.filter(profile => getProfileCompanyIds(profile).includes(company.id)); return ( navigate(`/companies/${company.id}`)} style={{ cursor: 'pointer' }}> ); })}
Company Clients Phone Address Actions
{company.name}
{companyProfiles.length > 0 && (
{companyProfiles.map(profile => (
โ€ข
{profile.name || 'โ€”'}
))}
)}
{companyProfiles.length} {company.phone || 'โ€”'} {company.address || 'โ€”'} e.stopPropagation()}>
)} )} {activeTab === 'clients' && ( <>
{companies.length > 0 && (
Filter By Company
{companies.map(company => ( ))}
)}
{unassigned.length > 0 && (
Unassigned Client Users

These client users are not linked to any company yet.

{unassigned.map(user => (
{editingUserId === user.id ? (
setEditUserVal(e.target.value)} autoFocus style={{ margin: 0, fontSize: 13, padding: '3px 8px', width: 180 }} onKeyDown={e => { if (e.key === 'Enter') handleEditUserSave(user.id); if (e.key === 'Escape') setEditingUserId(null); }} />
) : ( <>
{user.name}
{user.email}
)}
{editingUserId !== user.id && (
No company
)}
))}
)} {clientProfiles.filter(profile => !filterCompany || getProfileCompanyIds(profile).includes(filterCompany)).length === 0 ? (

No client users

Create a client user to link them to a company.

) : (
{clientProfiles .filter(profile => !filterCompany || getProfileCompanyIds(profile).includes(filterCompany)) .map(user => { const companyNames = getProfileCompanyIds(user) .map(companyId => companies.find(company => company.id === companyId)?.name) .filter(Boolean); return ( ); })}
Name Email Company Role Actions
{editingUserId === user.id ? (
setEditUserVal(e.target.value)} autoFocus style={{ margin: 0, fontSize: 13, padding: '3px 8px', width: 180 }} onKeyDown={e => { if (e.key === 'Enter') handleEditUserSave(user.id); if (e.key === 'Escape') setEditingUserId(null); }} />
) : ( user.name || 'โ€”' )}
{user.email || 'โ€”'} {companyNames.length ? companyNames.join(', ') : 'โ€”'} {getRoleLabel(user.role)} {editingUserId !== user.id && (
)}
)} )} {activeTab === 'subcontractors' && (
{subcontractors.length === 0 ? (

No subcontractors yet

Create a subcontractor user to manage external access and POs.

) : (
{subcontractors.map(user => ( ))}
Name Email Role Actions
{editingUserId === user.id ? (
setEditUserVal(e.target.value)} autoFocus style={{ margin: 0, fontSize: 13, padding: '3px 8px', width: 180 }} onKeyDown={e => { if (e.key === 'Enter') handleEditUserSave(user.id); if (e.key === 'Escape') setEditingUserId(null); }} />
) : ( user.name || 'โ€”' )}
{user.email || 'โ€”'} {getRoleLabel(user.role)} {editingUserId !== user.id && (
)}
)}
)}
); }