fix: version 4.8.1 -- Agenda medica abre en vista Mes como el Calendario

Barra igual a la del Calendario (Hoy, anterior/siguiente, mes y ano; vista a la
derecha), vista Mes por defecto y aviso cuando no hay medicos configurados (antes
la vista Dia quedaba vacia).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-28 13:45:58 -05:00
parent 91e15e8318
commit 00aaf1ebef
11 changed files with 38 additions and 15 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog - project.linkedcorp.net # Changelog - project.linkedcorp.net
## [4.8.1] - 2026-10-28
### Fixed
- Agenda médica: abre en vista **Mes** con la misma barra que el Calendario (Hoy, anterior/siguiente,
mes y año; vista Mes/Semana/Día/Agenda a la derecha). Antes abría en Día y, sin médicos
configurados, la pantalla quedaba vacía. Aviso cuando aún no hay médicos registrados.
## [4.8.0] - 2026-10-28 ## [4.8.0] - 2026-10-28
### Added ### Added
- Agenda médica: vista **Mes** tipo calendario (hasta 4 citas/bloqueos por día, clic abre el día) y - Agenda médica: vista **Mes** tipo calendario (hasta 4 citas/bloqueos por día, clic abre el día) y
+2
View File
@@ -6,6 +6,8 @@ autorregistro/aprobacion/SMTP.
## Agenda médica (v4.7.0, sin desplegar) ## Agenda médica (v4.7.0, sin desplegar)
- v4.8.1: abre en Mes con barra igual al Calendario; aviso si no hay médicos (en prod se veía vacío
porque la 4.8.0 abría en Día y aún no hay médicos configurados).
- v4.8.0: vistas Mes (calendario) y Semana con todos los médicos filtrados, color por médico. Sin - v4.8.0: vistas Mes (calendario) y Semana con todos los médicos filtrados, color por médico. Sin
migración. Revisado en dev con datos temporales (ya borrados), sin errores de consola. migración. Revisado en dev con datos temporales (ya borrados), sin errores de consola.
+1 -1
View File
@@ -100,7 +100,7 @@ tiene cada cosa — sin que cargar la información sea tedioso para quien lo usa
Los médicos conservan el resto de sus módulos y no se crea un rol del sistema. Los médicos conservan el resto de sus módulos y no se crea un rol del sistema.
- Admin/superadmin con acceso a `clinic` y fila de staff configuran personal, horarios por tramos - Admin/superadmin con acceso a `clinic` y fila de staff configuran personal, horarios por tramos
y tipos de cita desde la pestaña Configuración de agenda. y tipos de cita desde la pestaña Configuración de agenda.
- Vistas (v4.8.0, la elegida se recuerda): **Día** con una columna por médico; **Semana** como - Vistas (v4.8.0; desde v4.8.1 abre en Mes con la barra del Calendario; la elegida se recuerda): **Día** con una columna por médico; **Semana** como
calendario de 7 días con las citas de todos los médicos filtrados, coloreadas por médico; **Mes** calendario de 7 días con las citas de todos los médicos filtrados, coloreadas por médico; **Mes**
como calendario mensual (hasta 4 entradas por día + "N más"; clic en un día abre su vista Día); como calendario mensual (hasta 4 entradas por día + "N más"; clic en un día abre su vista Día);
**Lista** con citas y bloqueos. Leyenda de colores por médico en Semana y Mes. **Lista** con citas y bloqueos. Leyenda de colores por médico en Semana y Mes.
+1 -1
View File
@@ -1 +1 @@
4.8.0 4.8.1
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "project-linkedcorp-backend", "name": "project-linkedcorp-backend",
"version": "4.8.0", "version": "4.8.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "project-linkedcorp-backend", "name": "project-linkedcorp-backend",
"version": "4.8.0", "version": "4.8.1",
"dependencies": { "dependencies": {
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "project-linkedcorp-backend", "name": "project-linkedcorp-backend",
"version": "4.8.0", "version": "4.8.1",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/server.js", "main": "src/server.js",
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "project-linkedcorp-frontend", "name": "project-linkedcorp-frontend",
"version": "4.8.0", "version": "4.8.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "project-linkedcorp-frontend", "name": "project-linkedcorp-frontend",
"version": "4.8.0", "version": "4.8.1",
"dependencies": { "dependencies": {
"dompurify": "^3.4.16", "dompurify": "^3.4.16",
"highlight.js": "^11.12.0", "highlight.js": "^11.12.0",
@@ -2748,7 +2748,7 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.8.0", "version": "4.8.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
"dev": true, "dev": true,
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "project-linkedcorp-frontend", "name": "project-linkedcorp-frontend",
"private": true, "private": true,
"version": "4.8.0", "version": "4.8.1",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+2 -2
View File
@@ -21,7 +21,7 @@ export const messages = {
clinic_block: 'Bloqueo', clinic_block: 'Bloqueo',
clinic_view_day: 'Día', clinic_view_day: 'Día',
clinic_view_week: 'Semana', clinic_view_week: 'Semana',
clinic_view_list: 'Lista', clinic_view_month: 'Mes', clinic_more: 'más', clinic_view_list: 'Agenda', clinic_view_month: 'Mes', clinic_no_doctors: 'Aún no hay médicos configurados en la agenda. Pide a un administrador que los registre.', clinic_no_doctors_admin: 'Aún no hay médicos configurados. Regístralos con su horario en la pestaña Configuración de agenda.', clinic_more: 'más',
clinic_doctors: 'Médicos', clinic_doctors: 'Médicos',
clinic_doctor: 'Médico', clinic_doctor: 'Médico',
clinic_status: 'Estado', clinic_status: 'Estado',
@@ -713,7 +713,7 @@ export const messages = {
clinic_block: 'Block', clinic_block: 'Block',
clinic_view_day: 'Day', clinic_view_day: 'Day',
clinic_view_week: 'Week', clinic_view_week: 'Week',
clinic_view_list: 'List', clinic_view_month: 'Month', clinic_more: 'more', clinic_view_list: 'Agenda', clinic_view_month: 'Month', clinic_no_doctors: 'No doctors are set up in the schedule yet. Ask an administrator to add them.', clinic_no_doctors_admin: 'No doctors are set up yet. Add them with their hours in the Schedule settings tab.', clinic_more: 'more',
clinic_doctors: 'Doctors', clinic_doctors: 'Doctors',
clinic_doctor: 'Doctor', clinic_doctor: 'Doctor',
clinic_status: 'Status', clinic_status: 'Status',
@@ -100,6 +100,7 @@ const readStored = (key, fallback) => {
export default function AgendaMedicaPage() { export default function AgendaMedicaPage() {
const { const {
t, t,
lang,
openDialog, openDialog,
pushToast pushToast
} = useUi(); } = useUi();
@@ -113,7 +114,7 @@ export default function AgendaMedicaPage() {
[types, setTypes] = useState([]), [types, setTypes] = useState([]),
[allTypes, setAllTypes] = useState([]); [allTypes, setAllTypes] = useState([]);
const [tab, setTab] = useState('agenda'), const [tab, setTab] = useState('agenda'),
[view, setView] = useState(() => { const v = readStored('plc_clinic_view', 'day'); return ['day', 'week', 'month', 'list'].includes(v) ? v : 'day'; }), [view, setView] = useState(() => { const v = readStored('plc_clinic_view_v2', 'month'); return ['day', 'week', 'month', 'list'].includes(v) ? v : 'month'; }),
[day, setDay] = useState(today); [day, setDay] = useState(today);
const [doctorFilter, setDoctorFilter] = useState(() => { const [doctorFilter, setDoctorFilter] = useState(() => {
const value = readStored('plc_clinic_doctors', []); const value = readStored('plc_clinic_doctors', []);
@@ -239,7 +240,7 @@ export default function AgendaMedicaPage() {
}, [me?.user_id, period.from, period.to]); }, [me?.user_id, period.from, period.to]);
useEffect(() => { useEffect(() => {
try { try {
localStorage.setItem('plc_clinic_view', JSON.stringify(view)); localStorage.setItem('plc_clinic_view_v2', JSON.stringify(view));
} catch {} } catch {}
}, [view]); }, [view]);
useEffect(() => { useEffect(() => {
@@ -429,6 +430,13 @@ export default function AgendaMedicaPage() {
actions actions
}); });
} }
const periodTitle = (() => {
const d = new Date(`${day}T12:00:00`);
if (view === 'month' || view === 'list') return d.toLocaleDateString(lang, { month: 'long', year: 'numeric' });
if (view === 'day') return d.toLocaleDateString(lang, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
const a = new Date(`${period.from.slice(0, 10)}T12:00:00`), b = new Date(`${move(period.to.slice(0, 10), -1)}T12:00:00`);
return `${a.toLocaleDateString(lang, { day: 'numeric', month: 'short' })} – ${b.toLocaleDateString(lang, { day: 'numeric', month: 'short', year: 'numeric' })}`;
})();
const doctorColor = id => doctors.find(d => d.id === id)?.color || 'rgb(var(--accent))'; const doctorColor = id => doctors.find(d => d.id === id)?.color || 'rgb(var(--accent))';
const doctorShort = id => (doctors.find(d => d.id === id)?.name || '').split(' ').slice(0, 2).join(' '); const doctorShort = id => (doctors.find(d => d.id === id)?.name || '').split(' ').slice(0, 2).join(' ');
const weekLabels = [t('calendar_mon'), t('calendar_tue'), t('calendar_wed'), t('calendar_thu'), t('calendar_fri'), t('calendar_sat'), t('calendar_sun')]; const weekLabels = [t('calendar_mon'), t('calendar_tue'), t('calendar_wed'), t('calendar_thu'), t('calendar_fri'), t('calendar_sat'), t('calendar_sun')];
@@ -475,7 +483,7 @@ export default function AgendaMedicaPage() {
const current = popup ? draft : tab === 'config' ? [staffDraft, typeDraft, scheduleDraft].find(x => x.isDirty) : null; const current = popup ? draft : tab === 'config' ? [staffDraft, typeDraft, scheduleDraft].find(x => x.isDirty) : null;
void guardedClose(current, () => setTab(key)); void guardedClose(current, () => setTab(key));
}} /> }} />
{tab === 'agenda' && <><div className="clinic-controls"><Button icon={ChevronLeft} onClick={() => setDay(step(-1))}>{t('calendar_previous')}</Button><DatePicker value={day} onChange={setDay} label={t('calendar_date')} /><Button icon={ChevronRight} onClick={() => setDay(step(1))}>{t('calendar_next')}</Button><ComboBox value={view} onChange={setView} options={['day', 'week', 'month', 'list'].map(x => ({ {tab === 'agenda' && <><div className="toolbar clinic-toolbar"><div className="toolbar-left"><Button onClick={() => setDay(today())}>{t('calendar_today')}</Button><Button icon={ChevronLeft} onClick={() => setDay(step(-1))} aria-label={t('calendar_previous')} /><Button icon={ChevronRight} onClick={() => setDay(step(1))} aria-label={t('calendar_next')} /><strong className="clinic-period">{periodTitle}</strong></div><div className="toolbar-left clinic-controls"><ComboBox value={view} onChange={setView} options={['month', 'week', 'day', 'list'].map(x => ({
value: x, value: x,
label: t(`clinic_view_${x}`) label: t(`clinic_view_${x}`)
}))} /><CheckListDropdown label={t('clinic_doctors')} options={doctors.map(d => ({ }))} /><CheckListDropdown label={t('clinic_doctors')} options={doctors.map(d => ({
@@ -484,7 +492,8 @@ export default function AgendaMedicaPage() {
}))} values={doctorFilter} onChange={setDoctorFilter} /><CheckListDropdown label={t('clinic_status')} options={['scheduled', 'confirmed', 'attended', 'no_show'].map(x => ({ }))} values={doctorFilter} onChange={setDoctorFilter} /><CheckListDropdown label={t('clinic_status')} options={['scheduled', 'confirmed', 'attended', 'no_show'].map(x => ({
value: x, value: x,
label: t(`clinic_status_${x}`) label: t(`clinic_status_${x}`)
}))} values={statusFilter} onChange={setStatusFilter} /></div> }))} values={statusFilter} onChange={setStatusFilter} /><DatePicker value={day} onChange={setDay} label={t('calendar_date')} /></div></div>
{me && doctors.length === 0 && <InfoBar tone="warning">{isAdmin ? t('clinic_no_doctors_admin') : t('clinic_no_doctors')}</InfoBar>}
{(view === 'week' || view === 'month') && <div className="clinic-legend">{selectedDoctors.map(d => <span key={d.id}><span className="clinic-swatch" style={{ background: d.color || 'rgb(var(--accent))' }} />{d.name}</span>)}<span><span className="clinic-swatch block" />{t('clinic_blocks')}</span></div>} {(view === 'week' || view === 'month') && <div className="clinic-legend">{selectedDoctors.map(d => <span key={d.id}><span className="clinic-swatch" style={{ background: d.color || 'rgb(var(--accent))' }} />{d.name}</span>)}<span><span className="clinic-swatch block" />{t('clinic_blocks')}</span></div>}
{view === 'day' ? <div className="clinic-day-grid">{selectedDoctors.map(d => <div className="clinic-doctor" key={d.id}><h3 style={{ {view === 'day' ? <div className="clinic-day-grid">{selectedDoctors.map(d => <div className="clinic-doctor" key={d.id}><h3 style={{
borderTop: `3px solid ${d.color || 'rgb(var(--accent))'}` borderTop: `3px solid ${d.color || 'rgb(var(--accent))'}`
@@ -14,3 +14,9 @@
.clinic-swatch{width:12px;height:12px;border-radius:3px;display:inline-block} .clinic-swatch{width:12px;height:12px;border-radius:3px;display:inline-block}
.clinic-swatch.block{background:repeating-linear-gradient(135deg,rgb(var(--warning)) 0 3px,transparent 3px 6px);border:1px solid rgb(var(--warning)/.6)} .clinic-swatch.block{background:repeating-linear-gradient(135deg,rgb(var(--warning)) 0 3px,transparent 3px 6px);border:1px solid rgb(var(--warning)/.6)}
.clinic-page .calendar-cell{min-height:104px} .clinic-page .calendar-cell{min-height:104px}
/* Barra igual a la del Calendario: Hoy, anterior/siguiente y titulo del periodo; a la derecha vista y filtros. */
.clinic-toolbar{margin:12px 0}
.clinic-toolbar .clinic-controls{margin:0}
.clinic-period{display:inline-block}.clinic-period::first-letter{text-transform:uppercase}
.clinic-toolbar .clinic-controls .combobox{width:140px;min-width:0;flex:0 0 140px}
.clinic-toolbar .clinic-controls .datepicker-trigger{flex:0 0 150px;width:150px}