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:
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Generated
+2
-2
@@ -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,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",
|
||||||
|
|||||||
Generated
+3
-3
@@ -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,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",
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user