Merge branch 'backup/visual-adjustments' into fix/visual-adjustments

This commit is contained in:
M-Gabrielly 2025-12-03 21:20:24 -03:00
commit ba41468f37
5 changed files with 119 additions and 23 deletions

View File

@ -111,8 +111,11 @@ export default function ConsultasPage() {
const baseDate = scheduledBase ? new Date(scheduledBase) : new Date(); const baseDate = scheduledBase ? new Date(scheduledBase) : new Date();
const duration = appointment.duration_minutes ?? appointment.duration ?? 30; const duration = appointment.duration_minutes ?? appointment.duration ?? 30;
// compute start and end times (HH:MM) // compute start and end times (HH:MM) and date using local time to avoid timezone issues
const appointmentDateStr = baseDate.toISOString().split("T")[0]; const year = baseDate.getFullYear();
const month = String(baseDate.getMonth() + 1).padStart(2, '0');
const day = String(baseDate.getDate()).padStart(2, '0');
const appointmentDateStr = `${year}-${month}-${day}`;
const startTime = `${String(baseDate.getHours()).padStart(2, '0')}:${String(baseDate.getMinutes()).padStart(2, '0')}`; const startTime = `${String(baseDate.getHours()).padStart(2, '0')}:${String(baseDate.getMinutes()).padStart(2, '0')}`;
const endDate = new Date(baseDate.getTime() + duration * 60000); const endDate = new Date(baseDate.getTime() + duration * 60000);
const endTime = `${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`; const endTime = `${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`;
@ -811,7 +814,7 @@ export default function ConsultasPage() {
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Tipo</Label> <Label className="text-right">Tipo</Label>
<span className="col-span-3">{capitalize(viewingAppointment?.type || "")}</span> <span className="col-span-3">{capitalize(viewingAppointment?.appointment_type || viewingAppointment?.type || "")}</span>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Observações</Label> <Label className="text-right">Observações</Label>

View File

@ -131,6 +131,7 @@ export default function DoutoresPage() {
const [availabilityOpenFor, setAvailabilityOpenFor] = useState<Medico | null>(null); const [availabilityOpenFor, setAvailabilityOpenFor] = useState<Medico | null>(null);
const [availabilityViewingFor, setAvailabilityViewingFor] = useState<Medico | null>(null); const [availabilityViewingFor, setAvailabilityViewingFor] = useState<Medico | null>(null);
const [availabilities, setAvailabilities] = useState<DoctorAvailability[]>([]); const [availabilities, setAvailabilities] = useState<DoctorAvailability[]>([]);
const [availabilitiesForCreate, setAvailabilitiesForCreate] = useState<DoctorAvailability[]>([]);
const [availLoading, setAvailLoading] = useState(false); const [availLoading, setAvailLoading] = useState(false);
const [editingAvailability, setEditingAvailability] = useState<DoctorAvailability | null>(null); const [editingAvailability, setEditingAvailability] = useState<DoctorAvailability | null>(null);
const [exceptions, setExceptions] = useState<DoctorException[]>([]); const [exceptions, setExceptions] = useState<DoctorException[]>([]);
@ -633,7 +634,17 @@ export default function DoutoresPage() {
Ver pacientes atribuídos Ver pacientes atribuídos
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setAvailabilityOpenFor(doctor)}> <DropdownMenuItem onClick={async () => {
try {
const list = await listarDisponibilidades({ doctorId: doctor.id, active: true });
setAvailabilitiesForCreate(list || []);
setAvailabilityOpenFor(doctor);
} catch (e) {
console.warn('Erro ao carregar disponibilidades:', e);
setAvailabilitiesForCreate([]);
setAvailabilityOpenFor(doctor);
}
}}>
<Plus className="mr-2 h-4 w-4" /> <Plus className="mr-2 h-4 w-4" />
Criar disponibilidade Criar disponibilidade
</DropdownMenuItem> </DropdownMenuItem>
@ -869,6 +880,7 @@ export default function DoutoresPage() {
open={!!availabilityOpenFor} open={!!availabilityOpenFor}
onOpenChange={(open) => { if (!open) setAvailabilityOpenFor(null); }} onOpenChange={(open) => { if (!open) setAvailabilityOpenFor(null); }}
doctorId={availabilityOpenFor?.id} doctorId={availabilityOpenFor?.id}
existingAvailabilities={availabilitiesForCreate}
onSaved={(saved) => { console.log('Disponibilidade salva', saved); setAvailabilityOpenFor(null); /* optionally reload list */ reloadAvailabilities(availabilityOpenFor?.id); }} onSaved={(saved) => { console.log('Disponibilidade salva', saved); setAvailabilityOpenFor(null); /* optionally reload list */ reloadAvailabilities(availabilityOpenFor?.id); }}
/> />
)} )}
@ -890,6 +902,7 @@ export default function DoutoresPage() {
doctorId={editingAvailability?.doctor_id ?? availabilityViewingFor?.id} doctorId={editingAvailability?.doctor_id ?? availabilityViewingFor?.id}
availability={editingAvailability} availability={editingAvailability}
mode="edit" mode="edit"
existingAvailabilities={availabilities}
onSaved={(saved) => { console.log('Disponibilidade atualizada', saved); setEditingAvailability(null); reloadAvailabilities(editingAvailability?.doctor_id ?? availabilityViewingFor?.id); }} onSaved={(saved) => { console.log('Disponibilidade atualizada', saved); setEditingAvailability(null); reloadAvailabilities(editingAvailability?.doctor_id ?? availabilityViewingFor?.id); }}
/> />
)} )}
@ -910,14 +923,35 @@ export default function DoutoresPage() {
<div>Carregando disponibilidades</div> <div>Carregando disponibilidades</div>
) : availabilities && availabilities.length ? ( ) : availabilities && availabilities.length ? (
<div className="space-y-2"> <div className="space-y-2">
{availabilities.map((a) => ( {availabilities
.sort((a, b) => {
// Define a ordem dos dias da semana (Segunda a Domingo)
const weekdayOrder: Record<string, number> = {
'segunda': 1, 'segunda-feira': 1, 'mon': 1, 'monday': 1, '1': 1,
'terca': 2, 'terça': 2, 'terça-feira': 2, 'tue': 2, 'tuesday': 2, '2': 2,
'quarta': 3, 'quarta-feira': 3, 'wed': 3, 'wednesday': 3, '3': 3,
'quinta': 4, 'quinta-feira': 4, 'thu': 4, 'thursday': 4, '4': 4,
'sexta': 5, 'sexta-feira': 5, 'fri': 5, 'friday': 5, '5': 5,
'sabado': 6, 'sábado': 6, 'sat': 6, 'saturday': 6, '6': 6,
'domingo': 7, 'dom': 7, 'sun': 7, 'sunday': 7, '0': 7, '7': 7
};
const getWeekdayOrder = (weekday: any) => {
if (typeof weekday === 'number') return weekday === 0 ? 7 : weekday;
const normalized = String(weekday).toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
return weekdayOrder[normalized] || 999;
};
return getWeekdayOrder(a.weekday) - getWeekdayOrder(b.weekday);
})
.map((a) => (
<div key={String(a.id)} className="p-2 border rounded flex justify-between items-start"> <div key={String(a.id)} className="p-2 border rounded flex justify-between items-start">
<div> <div>
<div className="font-medium">{translateWeekday(a.weekday)} {a.start_time} {a.end_time}</div> <div className="font-medium">{translateWeekday(a.weekday)} {a.start_time} {a.end_time}</div>
<div className="text-xs text-muted-foreground">Duração: {a.slot_minutes} min Tipo: {a.appointment_type || '—'} {a.active ? 'Ativa' : 'Inativa'}</div> <div className="text-xs text-muted-foreground">Duração: {a.slot_minutes} min Tipo: {a.appointment_type || '—'} {a.active ? 'Ativa' : 'Inativa'}</div>
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<Button size="sm" variant="outline" onClick={() => setEditingAvailability(a)}>Editar</Button> <Button size="sm" variant="outline" onClick={() => setEditingAvailability(a)} className="hover:bg-muted hover:text-foreground">Editar</Button>
<Button size="sm" variant="destructive" onClick={async () => { <Button size="sm" variant="destructive" onClick={async () => {
if (!confirm('Excluir esta disponibilidade?')) return; if (!confirm('Excluir esta disponibilidade?')) return;
try { try {

View File

@ -175,8 +175,17 @@ export default function EditarLaudoPage() {
mostrarAssinatura: !r.hide_signature, mostrarAssinatura: !r.hide_signature,
}); });
// Preencher conteúdo // Preencher conteúdo - verificar todos os possíveis nomes de campo
const contentHtml = r.content_html || r.conteudo_html || ''; const contentHtml = r.content_html || r.conteudo_html || r.contentHtml || r.conteudo || r.content || '';
console.log('[EditarLaudoPage] Loading content - report:', r);
console.log('[EditarLaudoPage] Content fields check:', {
content_html: r.content_html,
conteudo_html: r.conteudo_html,
contentHtml: r.contentHtml,
conteudo: r.conteudo,
content: r.content,
finalContent: contentHtml
});
// Verificar se existe rascunho salvo no localStorage // Verificar se existe rascunho salvo no localStorage
let finalContent = contentHtml; let finalContent = contentHtml;
@ -206,10 +215,9 @@ export default function EditarLaudoPage() {
setCampos(finalCampos); setCampos(finalCampos);
setContent(finalContent); setContent(finalContent);
console.log('[EditarLaudoPage] Setting content state with length:', finalContent.length);
if (editorRef.current) { // O innerHTML será setado no useEffect separado abaixo
editorRef.current.innerHTML = finalContent;
}
} catch (err) { } catch (err) {
console.warn('Erro ao carregar laudo:', err); console.warn('Erro ao carregar laudo:', err);
toast({ toast({
@ -224,6 +232,14 @@ export default function EditarLaudoPage() {
fetchLaudo(); fetchLaudo();
}, [laudoId, token, toast]); }, [laudoId, token, toast]);
// UseEffect separado para injetar o conteúdo no editor quando estiver pronto
useEffect(() => {
if (content && editorRef.current && !loading) {
console.log('[EditarLaudoPage] Injecting content into editor, length:', content.length);
editorRef.current.innerHTML = content;
}
}, [content, loading]);
// Formatação com contenteditable // Formatação com contenteditable
const applyFormat = (command: string, value?: string) => { const applyFormat = (command: string, value?: string) => {
document.execCommand(command, false, value || undefined); document.execCommand(command, false, value || undefined);

View File

@ -1865,7 +1865,15 @@ const ProfissionalPage = () => {
function LaudoEditor({ pacientes, laudo, onClose, isNewLaudo, preSelectedPatient, createNewReport, updateExistingReport, reloadReports, onSaved }: { pacientes?: any[]; laudo?: any; onClose: () => void; isNewLaudo?: boolean; preSelectedPatient?: any; createNewReport?: (data: any) => Promise<any>; updateExistingReport?: (id: string, data: any) => Promise<any>; reloadReports?: () => Promise<void>; onSaved?: (r:any) => void }) { function LaudoEditor({ pacientes, laudo, onClose, isNewLaudo, preSelectedPatient, createNewReport, updateExistingReport, reloadReports, onSaved }: { pacientes?: any[]; laudo?: any; onClose: () => void; isNewLaudo?: boolean; preSelectedPatient?: any; createNewReport?: (data: any) => Promise<any>; updateExistingReport?: (id: string, data: any) => Promise<any>; reloadReports?: () => Promise<void>; onSaved?: (r:any) => void }) {
const { toast } = useToast(); const { toast } = useToast();
const [activeTab, setActiveTab] = useState("editor"); const [activeTab, setActiveTab] = useState("editor");
const [content, setContent] = useState(laudo?.conteudo || ""); // Initialize content checking all possible field names
const initialContent = laudo?.conteudo ?? laudo?.content_html ?? laudo?.contentHtml ?? laudo?.content ?? "";
console.log('[LaudoEditor] Initializing content - laudo:', laudo, 'initialContent length:', initialContent?.length, 'fields:', {
conteudo: laudo?.conteudo,
content_html: laudo?.content_html,
contentHtml: laudo?.contentHtml,
content: laudo?.content
});
const [content, setContent] = useState(initialContent);
const [showPreview, setShowPreview] = useState(false); const [showPreview, setShowPreview] = useState(false);
const [pacienteSelecionado, setPacienteSelecionado] = useState<any>(preSelectedPatient || null); const [pacienteSelecionado, setPacienteSelecionado] = useState<any>(preSelectedPatient || null);
const [listaPacientes, setListaPacientes] = useState<any[]>([]); const [listaPacientes, setListaPacientes] = useState<any[]>([]);
@ -1952,8 +1960,10 @@ const ProfissionalPage = () => {
// Carregar dados do laudo existente quando disponível (mais robusto: suporta vários nomes de campo) // Carregar dados do laudo existente quando disponível (mais robusto: suporta vários nomes de campo)
useEffect(() => { useEffect(() => {
if (laudo && !isNewLaudo) { if (laudo && !isNewLaudo) {
console.log('[LaudoEditor useEffect] Loading existing laudo data:', laudo);
// Conteúdo: aceita 'conteudo', 'content_html', 'contentHtml', 'content' // Conteúdo: aceita 'conteudo', 'content_html', 'contentHtml', 'content'
const contentValue = laudo.conteudo ?? laudo.content_html ?? laudo.contentHtml ?? laudo.content ?? ""; const contentValue = laudo.conteudo ?? laudo.content_html ?? laudo.contentHtml ?? laudo.content ?? "";
console.log('[LaudoEditor useEffect] Content value length:', contentValue?.length, 'Setting content...');
setContent(contentValue); setContent(contentValue);
// Campos: use vários fallbacks // Campos: use vários fallbacks

View File

@ -18,9 +18,11 @@ export interface AvailabilityFormProps {
// when editing, pass the existing availability and set mode to 'edit' // when editing, pass the existing availability and set mode to 'edit'
availability?: DoctorAvailability | null availability?: DoctorAvailability | null
mode?: 'create' | 'edit' mode?: 'create' | 'edit'
// existing availabilities to prevent duplicate weekday selection
existingAvailabilities?: DoctorAvailability[]
} }
export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved, availability = null, mode = 'create' }: AvailabilityFormProps) { export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved, availability = null, mode = 'create', existingAvailabilities = [] }: AvailabilityFormProps) {
const [weekday, setWeekday] = useState<string>('segunda') const [weekday, setWeekday] = useState<string>('segunda')
const [startTime, setStartTime] = useState<string>('09:00') const [startTime, setStartTime] = useState<string>('09:00')
const [endTime, setEndTime] = useState<string>('17:00') const [endTime, setEndTime] = useState<string>('17:00')
@ -31,6 +33,26 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
const { toast } = useToast() const { toast } = useToast()
const [blockedException, setBlockedException] = useState<null | { date: string; reason?: string; times?: string }>(null) const [blockedException, setBlockedException] = useState<null | { date: string; reason?: string; times?: string }>(null)
// Normalize weekday to standard format for comparison
const normalizeWeekdayForComparison = (w?: string) => {
if (!w) return w;
const k = String(w).toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').replace(/[^a-z0-9]/g, '');
const map: Record<string,string> = {
'segunda':'segunda','terca':'terca','quarta':'quarta','quinta':'quinta','sexta':'sexta','sabado':'sabado','domingo':'domingo',
'monday':'segunda','tuesday':'terca','wednesday':'quarta','thursday':'quinta','friday':'sexta','saturday':'sabado','sunday':'domingo',
'1':'segunda','2':'terca','3':'quarta','4':'quinta','5':'sexta','6':'sabado','0':'domingo','7':'domingo'
};
return map[k] ?? k;
};
// Get list of already used weekdays (excluding current one in edit mode)
const usedWeekdays = new Set(
(existingAvailabilities || [])
.filter(a => mode === 'edit' ? a.id !== availability?.id : true)
.map(a => normalizeWeekdayForComparison(a.weekday))
.filter(Boolean)
);
// When editing, populate state from availability prop // When editing, populate state from availability prop
useEffect(() => { useEffect(() => {
if (mode === 'edit' && availability) { if (mode === 'edit' && availability) {
@ -47,6 +69,17 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
} }
}, [mode, availability]) }, [mode, availability])
// When creating and modal opens, set the first available weekday
useEffect(() => {
if (mode === 'create' && open) {
const allWeekdays = ['segunda', 'terca', 'quarta', 'quinta', 'sexta', 'sabado', 'domingo'];
const firstAvailable = allWeekdays.find(day => !usedWeekdays.has(day));
if (firstAvailable) {
setWeekday(firstAvailable);
}
}
}, [mode, open, usedWeekdays])
async function handleSubmit(e?: React.FormEvent) { async function handleSubmit(e?: React.FormEvent) {
e?.preventDefault() e?.preventDefault()
if (!doctorId) { if (!doctorId) {
@ -181,25 +214,25 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Criar disponibilidade</DialogTitle> <DialogTitle>{mode === 'edit' ? 'Editar disponibilidade' : 'Criar disponibilidade'}</DialogTitle>
</DialogHeader> </DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 py-4"> <form onSubmit={handleSubmit} className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<Label>Dia da semana</Label> <Label>Dia da semana</Label>
<Select value={weekday} onValueChange={(v) => setWeekday(v)}> <Select value={weekday} onValueChange={(v) => setWeekday(v)} disabled={mode === 'edit'}>
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="segunda">Segunda</SelectItem> <SelectItem value="segunda" disabled={usedWeekdays.has('segunda')}>Segunda</SelectItem>
<SelectItem value="terca">Terça</SelectItem> <SelectItem value="terca" disabled={usedWeekdays.has('terca')}>Terça</SelectItem>
<SelectItem value="quarta">Quarta</SelectItem> <SelectItem value="quarta" disabled={usedWeekdays.has('quarta')}>Quarta</SelectItem>
<SelectItem value="quinta">Quinta</SelectItem> <SelectItem value="quinta" disabled={usedWeekdays.has('quinta')}>Quinta</SelectItem>
<SelectItem value="sexta">Sexta</SelectItem> <SelectItem value="sexta" disabled={usedWeekdays.has('sexta')}>Sexta</SelectItem>
<SelectItem value="sabado">Sábado</SelectItem> <SelectItem value="sabado" disabled={usedWeekdays.has('sabado')}>Sábado</SelectItem>
<SelectItem value="domingo">Domingo</SelectItem> <SelectItem value="domingo" disabled={usedWeekdays.has('domingo')}>Domingo</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
@ -242,7 +275,7 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
<DialogFooter> <DialogFooter>
<Button variant="ghost" onClick={() => onOpenChange(false)} disabled={submitting}>Cancelar</Button> <Button variant="ghost" onClick={() => onOpenChange(false)} disabled={submitting}>Cancelar</Button>
<Button type="submit" disabled={submitting}>{submitting ? 'Salvando...' : 'Criar disponibilidade'}</Button> <Button type="submit" disabled={submitting}>{submitting ? 'Salvando...' : (mode === 'edit' ? 'Salvar alterações' : 'Criar disponibilidade')}</Button>
</DialogFooter> </DialogFooter>
</form> </form>
</DialogContent> </DialogContent>