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 duration = appointment.duration_minutes ?? appointment.duration ?? 30;
// compute start and end times (HH:MM)
const appointmentDateStr = baseDate.toISOString().split("T")[0];
// compute start and end times (HH:MM) and date using local time to avoid timezone issues
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 endDate = new Date(baseDate.getTime() + duration * 60000);
const endTime = `${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart(2, '0')}`;
@ -811,7 +814,7 @@ export default function ConsultasPage() {
</div>
<div className="grid grid-cols-4 items-center gap-4">
<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 className="grid grid-cols-4 items-center gap-4">
<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 [availabilityViewingFor, setAvailabilityViewingFor] = useState<Medico | null>(null);
const [availabilities, setAvailabilities] = useState<DoctorAvailability[]>([]);
const [availabilitiesForCreate, setAvailabilitiesForCreate] = useState<DoctorAvailability[]>([]);
const [availLoading, setAvailLoading] = useState(false);
const [editingAvailability, setEditingAvailability] = useState<DoctorAvailability | null>(null);
const [exceptions, setExceptions] = useState<DoctorException[]>([]);
@ -633,7 +634,17 @@ export default function DoutoresPage() {
Ver pacientes atribuídos
</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" />
Criar disponibilidade
</DropdownMenuItem>
@ -869,6 +880,7 @@ export default function DoutoresPage() {
open={!!availabilityOpenFor}
onOpenChange={(open) => { if (!open) setAvailabilityOpenFor(null); }}
doctorId={availabilityOpenFor?.id}
existingAvailabilities={availabilitiesForCreate}
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}
availability={editingAvailability}
mode="edit"
existingAvailabilities={availabilities}
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>
) : availabilities && availabilities.length ? (
<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>
<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>
<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 () => {
if (!confirm('Excluir esta disponibilidade?')) return;
try {

View File

@ -175,8 +175,17 @@ export default function EditarLaudoPage() {
mostrarAssinatura: !r.hide_signature,
});
// Preencher conteúdo
const contentHtml = r.content_html || r.conteudo_html || '';
// Preencher conteúdo - verificar todos os possíveis nomes de campo
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
let finalContent = contentHtml;
@ -206,10 +215,9 @@ export default function EditarLaudoPage() {
setCampos(finalCampos);
setContent(finalContent);
console.log('[EditarLaudoPage] Setting content state with length:', finalContent.length);
if (editorRef.current) {
editorRef.current.innerHTML = finalContent;
}
// O innerHTML será setado no useEffect separado abaixo
} catch (err) {
console.warn('Erro ao carregar laudo:', err);
toast({
@ -224,6 +232,14 @@ export default function EditarLaudoPage() {
fetchLaudo();
}, [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
const applyFormat = (command: string, value?: string) => {
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 }) {
const { toast } = useToast();
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 [pacienteSelecionado, setPacienteSelecionado] = useState<any>(preSelectedPatient || null);
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)
useEffect(() => {
if (laudo && !isNewLaudo) {
console.log('[LaudoEditor useEffect] Loading existing laudo data:', laudo);
// Conteúdo: aceita 'conteudo', 'content_html', 'contentHtml', '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);
// 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'
availability?: DoctorAvailability | null
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 [startTime, setStartTime] = useState<string>('09:00')
const [endTime, setEndTime] = useState<string>('17:00')
@ -31,6 +33,26 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
const { toast } = useToast()
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
useEffect(() => {
if (mode === 'edit' && availability) {
@ -47,6 +69,17 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
}
}, [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) {
e?.preventDefault()
if (!doctorId) {
@ -181,25 +214,25 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>Criar disponibilidade</DialogTitle>
<DialogTitle>{mode === 'edit' ? 'Editar disponibilidade' : 'Criar disponibilidade'}</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 py-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label>Dia da semana</Label>
<Select value={weekday} onValueChange={(v) => setWeekday(v)}>
<Select value={weekday} onValueChange={(v) => setWeekday(v)} disabled={mode === 'edit'}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="segunda">Segunda</SelectItem>
<SelectItem value="terca">Terça</SelectItem>
<SelectItem value="quarta">Quarta</SelectItem>
<SelectItem value="quinta">Quinta</SelectItem>
<SelectItem value="sexta">Sexta</SelectItem>
<SelectItem value="sabado">Sábado</SelectItem>
<SelectItem value="domingo">Domingo</SelectItem>
<SelectItem value="segunda" disabled={usedWeekdays.has('segunda')}>Segunda</SelectItem>
<SelectItem value="terca" disabled={usedWeekdays.has('terca')}>Terça</SelectItem>
<SelectItem value="quarta" disabled={usedWeekdays.has('quarta')}>Quarta</SelectItem>
<SelectItem value="quinta" disabled={usedWeekdays.has('quinta')}>Quinta</SelectItem>
<SelectItem value="sexta" disabled={usedWeekdays.has('sexta')}>Sexta</SelectItem>
<SelectItem value="sabado" disabled={usedWeekdays.has('sabado')}>Sábado</SelectItem>
<SelectItem value="domingo" disabled={usedWeekdays.has('domingo')}>Domingo</SelectItem>
</SelectContent>
</Select>
</div>
@ -242,7 +275,7 @@ export function AvailabilityForm({ open, onOpenChange, doctorId = null, onSaved,
<DialogFooter>
<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>
</form>
</DialogContent>