// ============================================================
// JuFlow — Client Detail (5-tab full profile)
// ============================================================
const { useState: useStCD, useMemo: useMemoCD } = React;
const UCD = window.JFUtils;
const ClientDetail = ({ client, appointments, onClose, onNewAppt, addToast }) => {
const [tab, setTab] = useStCD('dados');
const [editMode, setEditMode] = useStCD(false);
const [formData, setFormData] = useStCD({ ...client });
if (!client) return null;
const setF = (k, v) => setFormData(f => ({ ...f, [k]: v }));
const setAnamnese = (k, v) => setFormData(f => ({ ...f, anamnese: { ...f.anamnese, [k]: v } }));
const clientAppts = useMemoCD(() =>
appointments
.filter(a => a.clientId === client.id)
.sort((a, b) => new Date(b.datetime) - new Date(a.datetime))
, [appointments, client.id]);
const completedAppts = clientAppts.filter(a => a.status === 'completed');
const clientPkgs = window.JF.CLIENT_PACKAGES.filter(p => p.clientId === client.id);
const totalSpent = completedAppts.reduce((s, a) => s + a.price, 0);
const avgTicket = completedAppts.length ? totalSpent / completedAppts.length : 0;
// ── TAB: Dados Pessoais ─────────────────────────────────
const TabDados = () => (
{/* Profile header */}
{client.name}
{client.tags.map(tag => )}
{client.status === 'inactive' && }
{[
{ label:'Sessões', value: completedAppts.length, icon:'Calendar' },
{ label:'Total gasto', value: UCD.formatCurrency(totalSpent), icon:'DollarSign' },
{ label:'Ticket médio', value: UCD.formatCurrency(avgTicket), icon:'TrendingUp' },
].map(stat => (
{stat.value}
{stat.label}
))}
{/* Personal data grid */}
Dados Pessoais
{[
{ label:'Nome completo', key:'name', icon:'User' },
{ label:'Telefone/WhatsApp', key:'phone', icon:'Phone' },
{ label:'E-mail', key:'email', icon:'Mail' },
{ label:'CPF', key:'cpf', icon:'CreditCard' },
{ label:'Data de nascimento', key:'birthdate', icon:'Cake', format:v => v ? `${UCD.formatDate(v)} (${UCD.age(v)} anos)` : '—' },
{ label:'Endereço', key:'address', icon:'MapPin', span:2 },
].map(field => (
{field.label}
{editMode
?
setF(field.key, v)} icon={field.icon} />
:
{field.format ? field.format(client[field.key]) : (client[field.key] || '—')}
}
))}
{/* Tags */}
Tags
{client.tags.map(tag => )}
{['VIP','Aniversariante','Inadimplente','Regular'].filter(t => !client.tags.includes(t)).map(tag => (
))}
{/* Notes */}
);
// ── TAB: Anamnese ───────────────────────────────────────
const TabAnamnese = () => {
const fields = [
{ key:'healthHistory', label:'Histórico de saúde', icon:'Heart', rows:3 },
{ key:'allergies', label:'Alergias', icon:'AlertTriangle', rows:2 },
{ key:'medications', label:'Medicamentos em uso', icon:'Pill', rows:2 },
{ key:'contraindications', label:'Contraindicações', icon:'ShieldAlert', rows:2 },
{ key:'goals', label:'Objetivos do cliente', icon:'Target', rows:3 },
];
return (
Ficha de Anamnese
Histórico médico e informações clínicas
{fields.map(f => (
= 3 ? 'span 2' : 'span 1',
}}>
{f.label}
{editMode
?
))}
{/* Before/After photos placeholder */}
📸 Fotos Antes e Depois
{['Antes (Frente)','Antes (Lado)','Depois (Frente)','Depois (Lado)'].map(label => (
{ e.currentTarget.style.borderColor='#FF6B35'; e.currentTarget.style.background='#FFF0EA'; }}
onMouseLeave={e => { e.currentTarget.style.borderColor='#D1D5DB'; e.currentTarget.style.background='#F9FAFB'; }}
>
{label}
))}
);
};
// ── TAB: Histórico ──────────────────────────────────────
const TabHistorico = () => (
Histórico de Atendimentos
{completedAppts.length} atendimentos realizados
{clientAppts.length === 0 ? (
) : (
{/* Timeline line */}
{clientAppts.map((appt, i) => {
const service = UCD.getService(appt.serviceId);
const prof = UCD.getProfessional(appt.professionalId);
return (
{/* Timeline dot */}
e.currentTarget.style.borderColor='#C4B5FD'}
onMouseLeave={e => e.currentTarget.style.borderColor='#E2E8F0'}
>
{service?.name}
{UCD.formatDateTime(appt.datetime)}
{prof?.name}
{UCD.formatCurrency(appt.price)}
{appt.sessionNotes && (
{appt.sessionNotes}
)}
{appt.paymentMethod && (
{appt.paymentMethod} · {appt.duration}min
)}
);
})}
)}
);
// ── TAB: Financeiro ─────────────────────────────────────
const TabFinanceiro = () => {
const pending = clientAppts.filter(a => a.status==='confirmed' && !a.paymentMethod);
return (
{pending.length > 0 && (
{pending.length} pagamento(s) pendente(s)
{pending.map(a => {
const svc = UCD.getService(a.serviceId);
return (
{svc?.name} — {UCD.formatDate(a.datetime)}
{UCD.formatCurrency(a.price)}
);
})}
)}
Transações
{['Data','Serviço','Profissional','Valor','Pagamento','Status'].map(h => (
| {h} |
))}
{completedAppts.slice(0, 10).map((a, i) => {
const svc = UCD.getService(a.serviceId);
const prof = UCD.getProfessional(a.professionalId);
return (
| {UCD.formatDate(a.datetime)} |
{svc?.name} |
{prof?.name} |
{UCD.formatCurrency(a.price)} |
{a.paymentMethod || '—'} |
|
);
})}
);
};
// ── TAB: Pacotes ────────────────────────────────────────
const TabPacotes = () => (
Pacotes e Sessões
{clientPkgs.length === 0 ? (
Adicionar pacote} />
) : (
clientPkgs.map(cp => {
const pkg = UCD.getPackage(cp.packageId);
if (!pkg) return null;
const remaining = pkg.totalSessions - cp.sessionsUsed;
const pct = (cp.sessionsUsed / pkg.totalSessions) * 100;
const svc = UCD.getService(pkg.serviceId);
return (
{pkg.name}
{svc?.name} · Vence em {UCD.formatDate(cp.expiryDate)}
{remaining}
sessões restantes
{cp.sessionsUsed} de {pkg.totalSessions} sessões utilizadas
{Math.round(pct)}% concluído
);
})
)}
{/* Available packages to purchase */}
Pacotes disponíveis
{window.JF.PACKAGES.slice(0, 4).map(pkg => (
{pkg.name}
{pkg.totalSessions} sessões
{UCD.formatCurrency(pkg.price)}
{UCD.formatCurrency(pkg.originalPrice)}
))}
);
const tabContent = { dados:, anamnese:, historico:, financeiro:, pacotes: };
return (
{/* Close button */}
{/* Tabs */}
{/* Tab content */}
{tabContent[tab]}
);
};
Object.assign(window, { ClientDetail });