fix(principal): integra auth, agenda e laudos com a api
This commit is contained in:
@@ -16,30 +16,38 @@ const viewFilters = ['Dia', 'Semana', 'Mês']
|
||||
|
||||
export function AgendaPage({ navigate }) {
|
||||
const [patients, setPatients] = useState([])
|
||||
const professionals = professionalRepository.getAll()
|
||||
const [professionals, setProfessionals] = useState([])
|
||||
const queue = appointmentRepository.getPredictiveQueueSummary()
|
||||
const timeline = appointmentRepository.getTodayTimeline()
|
||||
const weekDays = appointmentRepository.getWeekDays()
|
||||
const [activeView, setActiveView] = useState('Dia')
|
||||
const [status, setStatus] = useState('Todos')
|
||||
const [modalOpen, setModalOpen] = useState(false)
|
||||
const [localAppointments, setLocalAppointments] = useState(() => appointmentRepository.getAll())
|
||||
const [localAppointments, setLocalAppointments] = useState([])
|
||||
const [form, setForm] = useState({
|
||||
patientId: '',
|
||||
professional: professionals[0]?.name || '',
|
||||
professionalId: '',
|
||||
type: 'Retorno',
|
||||
time: '15:30',
|
||||
mode: 'Teleconsulta',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
patientRepository.getAll().then((data) => {
|
||||
setPatients(data)
|
||||
Promise.all([
|
||||
patientRepository.getAll(),
|
||||
appointmentRepository.getAll(),
|
||||
professionalRepository.getAll()
|
||||
]).then(([patientsData, appointmentsData, professionalsData]) => {
|
||||
setPatients(patientsData)
|
||||
setLocalAppointments(appointmentsData || [])
|
||||
setProfessionals(professionalsData || [])
|
||||
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
patientId: data[0]?.id || '',
|
||||
patientId: patientsData?.length ? patientsData[0].id : '',
|
||||
professionalId: professionalsData?.length ? professionalsData[0].id : '',
|
||||
}))
|
||||
})
|
||||
}).catch(e => console.error(e))
|
||||
}, [])
|
||||
|
||||
const visibleAppointments = useMemo(() => {
|
||||
@@ -54,26 +62,28 @@ useEffect(() => {
|
||||
setForm((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
|
||||
function handleCreate(event) {
|
||||
async function handleCreate(event) {
|
||||
event.preventDefault()
|
||||
const patient = patients.find((item) => item.id === form.patientId) || patients[0]
|
||||
|
||||
setLocalAppointments((current) => [
|
||||
...current,
|
||||
{
|
||||
id: `apt-local-${current.length + 1}`,
|
||||
date: '2026-04-07',
|
||||
patient: patient.name,
|
||||
patientId: patient.id,
|
||||
professional: form.professional,
|
||||
room: form.mode === 'Teleconsulta' ? 'Sala virtual 3' : 'Sala 02',
|
||||
status: 'Confirmada',
|
||||
|
||||
// Fallback date and time
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
|
||||
try {
|
||||
const created = await appointmentRepository.create({
|
||||
patientId: form.patientId,
|
||||
date: today,
|
||||
time: form.time,
|
||||
type: form.type,
|
||||
mode: form.mode,
|
||||
},
|
||||
])
|
||||
setModalOpen(false)
|
||||
room: form.mode === 'Teleconsulta' ? 'Virtual' : 'Consultório 1',
|
||||
professionalId: form.professionalId,
|
||||
})
|
||||
|
||||
setLocalAppointments((current) => [...current, created])
|
||||
setModalOpen(false)
|
||||
} catch(err) {
|
||||
alert(err.message || 'Erro ao criar agendamento.')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -244,7 +254,7 @@ useEffect(() => {
|
||||
>
|
||||
{patients.map((patient) => (
|
||||
<option key={patient.id} value={patient.id}>
|
||||
{patient.name}
|
||||
{patient.name || patient.full_name || patient.nome}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -274,11 +284,11 @@ useEffect(() => {
|
||||
<DarkField label="Profissional">
|
||||
<select
|
||||
className="h-11 rounded-md border border-[#404040] bg-[#303030] px-3 text-sm text-[#e5e5e5] outline-none focus:border-[#3b82f6]"
|
||||
onChange={(event) => updateForm('professional', event.target.value)}
|
||||
value={form.professional}
|
||||
onChange={(event) => updateForm('professionalId', event.target.value)}
|
||||
value={form.professionalId}
|
||||
>
|
||||
{professionals.map((professional) => (
|
||||
<option key={professional.id}>{professional.name}</option>
|
||||
<option key={professional.id} value={professional.id}>{professional.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</DarkField>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { authRepository } from '../repositories/authRepository.js'
|
||||
|
||||
import { BrandLogo } from '../components/Brand.jsx'
|
||||
import loginClinicImage from '../assets/figma/login-clinic.png'
|
||||
|
||||
@@ -9,14 +11,26 @@ export function LoginPage({ navigate }) {
|
||||
password: '',
|
||||
})
|
||||
const [showPassword, setShowPassword] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState('')
|
||||
|
||||
function updateField(field, value) {
|
||||
setForm((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
|
||||
function handleSubmit(event) {
|
||||
async function handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
navigate('/inicio')
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
await authRepository.login(form)
|
||||
navigate('/inicio')
|
||||
} catch (err) {
|
||||
setError(err.message || 'Erro de autenticação')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -74,6 +88,12 @@ export function LoginPage({ navigate }) {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="mt-4 rounded bg-red-500/10 p-3 text-sm font-semibold text-red-500 border border-red-500/20">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form className="mt-8 grid gap-5" onSubmit={handleSubmit}>
|
||||
<LoginField htmlFor="login-email" label="E-mail">
|
||||
<input
|
||||
@@ -122,10 +142,11 @@ export function LoginPage({ navigate }) {
|
||||
</LoginField>
|
||||
|
||||
<button
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-[6px] border border-[#3b82f6] bg-[#3b82f6] px-4 py-2 text-sm font-semibold text-white shadow-[0_10px_15px_rgba(59,130,246,0.2),0_4px_6px_rgba(59,130,246,0.2)] transition hover:bg-[#3478ed] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#3b82f6]"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-[6px] border border-[#3b82f6] bg-[#3b82f6] px-4 py-2 text-sm font-semibold text-white shadow-[0_10px_15px_rgba(59,130,246,0.2),0_4px_6px_rgba(59,130,246,0.2)] transition hover:bg-[#3478ed] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#3b82f6] disabled:opacity-50"
|
||||
disabled={loading}
|
||||
type="submit"
|
||||
>
|
||||
Entrar
|
||||
{loading ? 'Entrando...' : 'Entrar'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -204,29 +225,52 @@ export function RegisterPage({ navigate }) {
|
||||
|
||||
export function ForgotPasswordPage({ navigate }) {
|
||||
const [sent, setSent] = useState(false)
|
||||
const [email, setEmail] = useState('recepcao@mediconnect.com')
|
||||
const [error, setError] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
async function handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
setLoading(true)
|
||||
setError('')
|
||||
try {
|
||||
await authRepository.requestPasswordReset(email)
|
||||
setSent(true)
|
||||
} catch (err) {
|
||||
setError(err.message || 'Erro ao comunicar com o servidor.')
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthLayout
|
||||
description="Informe o e-mail cadastrado para receber um link mockado."
|
||||
description="Informe o e-mail cadastrado para receber o link de acesso."
|
||||
title="Recuperar senha"
|
||||
>
|
||||
{sent ? (
|
||||
<div className="mt-8 rounded-[6px] border border-emerald-500/30 bg-emerald-500/10 p-4 text-sm leading-6 text-emerald-300">
|
||||
Link de recuperação mockado enviado para o e-mail informado.
|
||||
Link de recuperação enviado para o e-mail informado. Siga as instruções do link!
|
||||
</div>
|
||||
) : (
|
||||
<form
|
||||
className="mt-8 grid gap-5"
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault()
|
||||
setSent(true)
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{error && (
|
||||
<div className="rounded bg-red-500/10 p-3 text-sm font-semibold text-red-500 border border-red-500/20">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
<AuthField label="E-mail cadastrado">
|
||||
<input autoComplete="email" className={authInputClass} defaultValue="recepcao@mediconnect.com" type="email" />
|
||||
<input autoComplete="email" className={authInputClass} onChange={e => setEmail(e.target.value)} value={email} type="email" />
|
||||
</AuthField>
|
||||
<button className="inline-flex h-11 w-full items-center justify-center rounded-[6px] bg-[#3b82f6] text-sm font-semibold text-white shadow-[0_10px_15px_rgba(59,130,246,0.2)] transition hover:bg-[#3478ed]" type="submit">
|
||||
Enviar link
|
||||
<button
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-[6px] bg-[#3b82f6] text-sm font-semibold text-white shadow-[0_10px_15px_rgba(59,130,246,0.2)] transition hover:bg-[#3478ed] disabled:opacity-50"
|
||||
disabled={loading}
|
||||
type="submit"
|
||||
>
|
||||
{loading ? "Enviando..." : "Enviar link"}
|
||||
</button>
|
||||
</form>
|
||||
)}
|
||||
|
||||
@@ -18,6 +18,7 @@ const statusConfig = {
|
||||
|
||||
const emptyMessage = {
|
||||
patient: '',
|
||||
phone: '',
|
||||
channel: 'whatsapp',
|
||||
template: 'Lembrete 48h',
|
||||
content: '',
|
||||
@@ -79,6 +80,7 @@ export function MessagesPage() {
|
||||
function openTemplate(template) {
|
||||
setComposer({
|
||||
patient: '',
|
||||
phone: '',
|
||||
channel: template.channel,
|
||||
template: template.name,
|
||||
content: template.content,
|
||||
@@ -86,13 +88,33 @@ export function MessagesPage() {
|
||||
setComposerOpen(true)
|
||||
}
|
||||
|
||||
function submitMessage(event) {
|
||||
async function submitMessage(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!composer.patient.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
let smsSent = false
|
||||
|
||||
if (composer.channel === 'sms') {
|
||||
if (!composer.phone.trim()) {
|
||||
alert('Informe o telefone para enviar SMS.')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await communicationRepository.sendSms({
|
||||
patientName: composer.patient.trim(),
|
||||
phone: composer.phone.trim(),
|
||||
content: composer.content,
|
||||
})
|
||||
smsSent = true
|
||||
} catch (e) {
|
||||
alert('Falha ao disparar SMS: ' + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
setMessages((current) => [
|
||||
{
|
||||
id: `local-${Date.now()}`,
|
||||
@@ -100,7 +122,7 @@ export function MessagesPage() {
|
||||
channel: composer.channel,
|
||||
template: composer.template.trim() || 'Mensagem avulsa',
|
||||
sentAt: 'Agora',
|
||||
status: 'pendente',
|
||||
status: composer.channel === 'sms' ? (smsSent ? 'entregue' : 'falha') : 'pendente',
|
||||
response: '',
|
||||
},
|
||||
...current,
|
||||
@@ -300,6 +322,7 @@ export function MessagesPage() {
|
||||
onClick={() => {
|
||||
setComposer({
|
||||
patient: campaign.count,
|
||||
phone: '',
|
||||
channel: 'whatsapp',
|
||||
template: campaign.title,
|
||||
content: campaign.desc,
|
||||
@@ -470,6 +493,17 @@ function MessageComposer({ draft, onChange, onClose, onSubmit, templates }) {
|
||||
</DarkField>
|
||||
</div>
|
||||
|
||||
{draft.channel === 'sms' ? (
|
||||
<DarkField label="Telefone">
|
||||
<input
|
||||
className={inputClass}
|
||||
onChange={(event) => update('phone', event.target.value)}
|
||||
placeholder="(81) 99999-9999"
|
||||
value={draft.phone}
|
||||
/>
|
||||
</DarkField>
|
||||
) : null}
|
||||
|
||||
<DarkField label="Template">
|
||||
<select className={inputClass} onChange={(event) => applyTemplate(event.target.value)} value={draft.template}>
|
||||
<option value="Mensagem avulsa">Mensagem avulsa</option>
|
||||
|
||||
@@ -1,20 +1,62 @@
|
||||
import { useState } from 'react'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
|
||||
import { profileRepository } from '../repositories/profileRepository.js'
|
||||
import { authRepository } from '../repositories/authRepository.js'
|
||||
|
||||
const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
|
||||
const inputClass =
|
||||
'h-10 rounded-sm border border-[#404040] bg-[#171717] px-3 text-sm text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-2 focus:ring-[#3b82f6]/20'
|
||||
|
||||
export function ProfilePage() {
|
||||
export function ProfilePage({ navigate }) {
|
||||
const [saved, setSaved] = useState(false)
|
||||
const [profile, setProfile] = useState(() => profileRepository.getCurrentUserProfile())
|
||||
const [profile, setProfile] = useState({ name: '', role: '', email: '', phone: '', unit: '', avatarUrl: '' })
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [uploadingAvatar, setUploadingAvatar] = useState(false)
|
||||
const [avatarError, setAvatarError] = useState('')
|
||||
const fileInputRef = useRef(null)
|
||||
|
||||
useEffect(() => {
|
||||
profileRepository.getCurrentUserProfile().then(data => {
|
||||
setProfile(data)
|
||||
setLoading(false)
|
||||
}).catch(() => setLoading(false))
|
||||
}, [])
|
||||
|
||||
function update(field, value) {
|
||||
setSaved(false)
|
||||
setProfile((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
await authRepository.logout()
|
||||
navigate('/login')
|
||||
}
|
||||
|
||||
async function handleAvatarChange(event) {
|
||||
const file = event.target.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
setUploadingAvatar(true)
|
||||
setAvatarError('')
|
||||
|
||||
try {
|
||||
const result = await profileRepository.updateAvatar(file)
|
||||
setProfile((current) => ({
|
||||
...current,
|
||||
avatarUrl: result.avatarUrl || URL.createObjectURL(file),
|
||||
}))
|
||||
} catch (err) {
|
||||
setAvatarError(err.message || 'Erro ao enviar avatar.')
|
||||
} finally {
|
||||
setUploadingAvatar(false)
|
||||
event.target.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return <div className="text-center pt-20 text-[#a3a3a3]">Localizando dados do paciente...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-6xl space-y-6">
|
||||
<header>
|
||||
@@ -25,15 +67,36 @@ export function ProfilePage() {
|
||||
<div className="grid gap-6 lg:grid-cols-[1fr_360px]">
|
||||
<section className={`${cardClass} p-6`}>
|
||||
<div className="mb-6 flex items-center gap-4">
|
||||
<div className="grid size-16 place-items-center rounded-full border border-[#3b82f6]/30 bg-[#3b82f6]/10 text-xl font-bold text-[#3b82f6]">
|
||||
HC
|
||||
</div>
|
||||
{profile.avatarUrl ? (
|
||||
<img
|
||||
alt=""
|
||||
className="size-16 rounded-full border border-[#3b82f6]/30 object-cover"
|
||||
src={profile.avatarUrl}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid size-16 place-items-center rounded-full border border-[#3b82f6]/30 bg-[#3b82f6]/10 text-xl font-bold text-[#3b82f6]">
|
||||
{initials(profile.name)}
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h2 className="text-lg font-bold text-[#f5f5f5]">{profile.name}</h2>
|
||||
<p className="mt-1 text-sm text-[#a3a3a3]">{profile.role}</p>
|
||||
<button className="mt-1 text-xs font-semibold text-[#3b82f6]" type="button">
|
||||
Alterar foto
|
||||
<button
|
||||
className="mt-1 text-xs font-semibold text-[#3b82f6] disabled:opacity-60"
|
||||
disabled={uploadingAvatar}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
type="button"
|
||||
>
|
||||
{uploadingAvatar ? 'Enviando...' : 'Alterar foto'}
|
||||
</button>
|
||||
<input
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleAvatarChange}
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
/>
|
||||
{avatarError ? <p className="mt-1 text-xs font-semibold text-red-400">{avatarError}</p> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,10 +142,18 @@ export function ProfilePage() {
|
||||
<aside className={`${cardClass} p-6`}>
|
||||
<h2 className="text-xl font-bold text-[#f5f5f5]">Resumo de acesso</h2>
|
||||
<dl className="mt-5 grid gap-4 text-sm">
|
||||
<Info label="Perfil" value="Administrador da clínica" />
|
||||
<Info label="Último acesso" value="07 abr 2026, 09:15" />
|
||||
<Info label="Perfil" value={profile.role} />
|
||||
<Info label="E-mail principal" value={profile.email} />
|
||||
<Info label="Permissões" value="Agenda, pacientes, comunicação e configurações" />
|
||||
</dl>
|
||||
<div className="mt-8 border-t border-[#404040] pt-6">
|
||||
<button
|
||||
className="w-full h-10 rounded-sm border border-red-500/30 text-red-500 font-semibold text-sm transition hover:bg-red-500/10"
|
||||
onClick={handleLogout}
|
||||
>
|
||||
Sair da conta
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,3 +177,13 @@ function Info({ label, value }) {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function initials(name) {
|
||||
return String(name || 'US')
|
||||
.split(' ')
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.map((part) => part[0])
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { reportRepository } from '../repositories/reportRepository.js'
|
||||
import { patientRepository } from '../repositories/patientRepository.js'
|
||||
|
||||
|
||||
const statusConfig = {
|
||||
@@ -43,7 +44,14 @@ const labelClass = 'mb-1.5 block text-xs font-medium text-[#e5e5e5]'
|
||||
const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
|
||||
|
||||
export function ReportsPage() {
|
||||
const [reports, setReports] = useState(() => reportRepository.getInitialReports())
|
||||
const [reports, setReports] = useState([])
|
||||
const [patients, setPatients] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
reportRepository.getInitialReports().then(setReports).catch(console.error)
|
||||
patientRepository.getAll().then(setPatients).catch(console.error)
|
||||
}, [])
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const [filterStatus, setFilterStatus] = useState('')
|
||||
const [openMenuId, setOpenMenuId] = useState(null)
|
||||
@@ -104,64 +112,34 @@ export function ReportsPage() {
|
||||
setEditorOpen(true)
|
||||
}
|
||||
|
||||
function saveReport(status) {
|
||||
if (!editor.patient.trim() || !editor.content.trim()) {
|
||||
return
|
||||
}
|
||||
async function saveReport(status) {
|
||||
if (!editor.patient.trim() || !editor.content.trim()) return
|
||||
|
||||
try {
|
||||
const selectedPatient = patients.find(p => p.name === editor.patient || p.full_name === editor.patient)
|
||||
const patientId = selectedPatient?.id || null
|
||||
|
||||
const date = new Date().toLocaleDateString('pt-BR')
|
||||
setReports((currentReports) => {
|
||||
if (editor.id) {
|
||||
return currentReports.map((report) =>
|
||||
report.id === editor.id
|
||||
? {
|
||||
...report,
|
||||
type: editor.type,
|
||||
patient: editor.patient,
|
||||
doctor: editor.doctor,
|
||||
content: editor.content,
|
||||
showDate: editor.showDate,
|
||||
signDigital: editor.signDigital,
|
||||
status,
|
||||
versions: [
|
||||
...report.versions,
|
||||
{
|
||||
version: report.versions.length + 1,
|
||||
action: status === 'finalizado' ? 'Liberado' : 'Rascunho',
|
||||
user: currentUser,
|
||||
summary: status === 'finalizado' ? 'Laudo liberado' : 'Rascunho salvo',
|
||||
},
|
||||
],
|
||||
}
|
||||
: report,
|
||||
)
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: `report-${Date.now()}`,
|
||||
const updated = await reportRepository.update(editor.id, {
|
||||
type: editor.type,
|
||||
patient: editor.patient,
|
||||
doctor: editor.doctor,
|
||||
date,
|
||||
status,
|
||||
content: editor.content,
|
||||
showDate: editor.showDate,
|
||||
signDigital: editor.signDigital,
|
||||
versions: [
|
||||
{ version: 1, action: 'Criado', user: currentUser, summary: 'Laudo criado localmente' },
|
||||
{
|
||||
version: 2,
|
||||
action: status === 'finalizado' ? 'Liberado' : 'Rascunho',
|
||||
user: currentUser,
|
||||
summary: status === 'finalizado' ? 'Laudo liberado' : 'Rascunho salvo',
|
||||
},
|
||||
],
|
||||
},
|
||||
...currentReports,
|
||||
]
|
||||
})
|
||||
setEditorOpen(false)
|
||||
patientId: patientId,
|
||||
status,
|
||||
})
|
||||
setReports(curr => curr.map(r => r.id == updated.id ? { ...updated, status } : r))
|
||||
} else {
|
||||
const created = await reportRepository.create({
|
||||
type: editor.type,
|
||||
content: editor.content,
|
||||
patientId: patientId,
|
||||
status,
|
||||
})
|
||||
setReports(curr => [{ ...created, status }, ...curr])
|
||||
}
|
||||
setEditorOpen(false)
|
||||
} catch(e) {
|
||||
alert(e.message || 'Erro ao persistir na Base de Dados')
|
||||
}
|
||||
}
|
||||
|
||||
function releaseReport(reportId) {
|
||||
@@ -391,7 +369,7 @@ function ReportRow({
|
||||
type="button"
|
||||
>
|
||||
<ReportIcon className="size-3.5" name="history" />
|
||||
v{report.versions.length}
|
||||
v{report.versions ? report.versions.length : 1}
|
||||
</button>
|
||||
</td>
|
||||
<td className="relative px-4 py-3 text-right">
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { professionalRepository } from '../repositories/professionalRepository.js'
|
||||
|
||||
const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
|
||||
|
||||
export function TeamPage({ navigate }) {
|
||||
const professionals = professionalRepository.getAll()
|
||||
const [professionals, setProfessionals] = useState([])
|
||||
const { slots, weekdays } = professionalRepository.getCoverageMap()
|
||||
|
||||
useEffect(() => {
|
||||
professionalRepository.getAll().then(setProfessionals).catch(console.error)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl space-y-6">
|
||||
<header className="flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
|
||||
|
||||
Reference in New Issue
Block a user