feat: version 4.7.0 -- Agenda medica
Modulo clinic (habilitado por usuario): citas de pacientes para medicos con choques bloqueantes (medico, paciente con cualquier medico, quirofano/bloqueos recurrentes y horario de atencion), validados en transaccion con bloqueo de filas para que dos recepcionistas no tomen el mismo hueco. Recepcion (alcance todos) y medico (alcance propio). Vista Dia por columnas de medico, Semana y Lista; pacientes con historial; bloqueos; configuracion de personal, horarios y tipos solo admin/superadmin. Capa de citas en el calendario personal del medico. Migracion 036. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { DatePicker } from './DatePicker.jsx';
|
||||
import { ComboBox } from './ComboBox.jsx';
|
||||
const hours = Array.from({length:24},(_,i) => ({value:String(i).padStart(2,'0'),label:String(i).padStart(2,'0')}));
|
||||
const minutes = Array.from({length:12},(_,i) => ({value:String(i*5).padStart(2,'0'),label:String(i*5).padStart(2,'0')}));
|
||||
export function DateTimeEditor({ value, onChange, allDay, label, isEnd = false }) {
|
||||
const date = value.slice(0,10), hour = value.slice(11,13), minute = value.slice(14,16);
|
||||
const setDate = (next) => onChange(`${next} ${allDay ? (isEnd ? '23:59' : '00:00') : `${hour}:${minute}`}:00`);
|
||||
const setTime = (h,m) => onChange(`${date} ${h}:${m}:00`);
|
||||
return <div className="meeting-datetime"><DatePicker value={date} onChange={setDate} label={label} />{!allDay && <><ComboBox options={hours} value={hour} onChange={(h) => setTime(h,minute)} placeholder={`${label} HH`} /><span>:</span><ComboBox options={minutes} value={minute} onChange={(m) => setTime(hour,m)} placeholder={`${label} MM`} /></>}</div>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { useUserDrafts } from '../hooks/useDraftForm.js';
|
||||
import { DraftMark } from './NavigationGuard.jsx';
|
||||
import { LayoutDashboard, FolderKanban, Building2, Settings, Repeat, StickyNote, KeyRound, Monitor, CalendarDays } from 'lucide-react';
|
||||
import { LayoutDashboard, FolderKanban, Building2, Settings, Repeat, StickyNote, KeyRound, Monitor, CalendarDays, Stethoscope } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { useAuth } from '../context/auth-context.jsx';
|
||||
@@ -15,6 +15,7 @@ const ITEMS = [
|
||||
{ to: '/', icon: LayoutDashboard, labelKey: 'nav_dashboard', end: true },
|
||||
{ to: '/proyectos', module: 'projects', icon: FolderKanban, labelKey: 'nav_projects' },
|
||||
{ to: '/calendario', module: 'calendar', icon: CalendarDays, labelKey: 'nav_calendar' },
|
||||
{ to: '/agenda-medica', module: 'clinic', icon: Stethoscope, labelKey: 'nav_clinic' },
|
||||
{ to: '/tareas-recurrentes', module: 'recurring_tasks', icon: Repeat, labelKey: 'nav_recurring_tasks' },
|
||||
{ to: '/notas', module: 'notes', icon: StickyNote, labelKey: 'nav_notes' },
|
||||
{ to: '/contrasenas', module: 'vault', icon: KeyRound, labelKey: 'nav_passwords' },
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useUi } from '../context/ui-context.jsx';
|
||||
import { Field, FieldRow } from './Field.jsx';
|
||||
import { ComboBox } from './ComboBox.jsx';
|
||||
import { DatePicker } from './DatePicker.jsx';
|
||||
|
||||
// Reuniones y bloqueos clínicos comparten las mismas reglas de recurrencia del backend.
|
||||
export function RecurrenceEditor({
|
||||
value,
|
||||
start,
|
||||
onChange
|
||||
}) {
|
||||
const {
|
||||
t
|
||||
} = useUi();
|
||||
const weekday = (new Date(`${start.slice(0, 10)}T00:00:00Z`).getUTCDay() + 6) % 7;
|
||||
const labels = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
||||
const patch = fields => onChange({
|
||||
...value,
|
||||
...fields
|
||||
});
|
||||
return <>
|
||||
<Field label={t('calendar_recurrence')}>
|
||||
<ComboBox options={['none', 'daily', 'weekly', 'monthly'].map(x => ({
|
||||
value: x,
|
||||
label: t(`calendar_${x}`)
|
||||
}))} value={value?.frequency || 'none'} onChange={x => onChange(x === 'none' ? null : {
|
||||
frequency: x,
|
||||
interval: 1,
|
||||
weekdays: x === 'weekly' ? [weekday] : [],
|
||||
count: 10
|
||||
})} />
|
||||
</Field>
|
||||
{value && <>
|
||||
<FieldRow>
|
||||
<Field label={t('calendar_interval')}><input className="input" type="number" min="1" max="365" value={value.interval} onChange={e => patch({
|
||||
interval: Number(e.target.value)
|
||||
})} /></Field>
|
||||
<Field label={t('calendar_count')}><input className="input" type="number" min="1" max="365" value={value.count || ''} onChange={e => patch({
|
||||
count: e.target.value ? Number(e.target.value) : null,
|
||||
until: null
|
||||
})} /></Field>
|
||||
<Field label={t('calendar_until')}><DatePicker value={value.until || ''} onChange={x => patch({
|
||||
until: x || null,
|
||||
count: null
|
||||
})} label={t('calendar_until')} /></Field>
|
||||
</FieldRow>
|
||||
{value.frequency === 'weekly' && <div className="calendar-weekdays">{labels.map((name, index) => <label key={name}>
|
||||
<input type="checkbox" checked={value.weekdays.includes(index)} onChange={() => patch({
|
||||
weekdays: value.weekdays.includes(index) ? value.weekdays.filter(x => x !== index) : [...value.weekdays, index]
|
||||
})} />
|
||||
{' '}{t(`calendar_${name}`)}
|
||||
</label>)}</div>}
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
Reference in New Issue
Block a user