feat: implementar permisos por área de trabajo

- Agregar funciones de verificación de área en useHospitalStore
- canEditInArea, canEditCama, canEditInternacion, canEditPaciente
- Leer datos de tablas individuales en lugar de tabla kv
- Usar sessionStorage para persistir sesión de usuario
- Agregar prop canEdit a componentes de secciones en HC
- Ocultar botones de edición para usuarios sin permisos en el área
- MapaCamas: ocultar botones de edición para camas fuera del área
This commit is contained in:
2026-04-23 02:18:42 -03:00
parent 76e01bfd79
commit e6e1582b25
8 changed files with 411 additions and 118 deletions
+1
View File
@@ -183,6 +183,7 @@ function AppContent() {
onActualizarCama={store.actualizarCama}
onVolver={() => store.setVista('internaciones')}
onEditarIngreso={() => { store.setVista('editaringreso'); }}
canEdit={store.canEditInternacion(internacion.id)}
/>
);
}
+69 -8
View File
@@ -88,6 +88,8 @@ export function useHospitalStore() {
const body = await res.json();
if (mounted && body) {
const defaults = defaultState();
const storedUser = sessionStorage.getItem('hospital_user');
const currentUser = storedUser ? JSON.parse(storedUser) : null;
const normalized = {
...defaults,
...body,
@@ -95,6 +97,8 @@ export function useHospitalStore() {
interconsultas: body.interconsultas || [],
atb: body.atb || [],
movimientosIndicaciones: body.movimientosIndicaciones || [],
currentUser,
isAuthenticated: !!currentUser,
};
setState(normalized);
}
@@ -108,15 +112,24 @@ export function useHospitalStore() {
}, []);
// Persist state to backend when it changes (debounced)
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
useEffect(() => {
if (!isLoaded) return;
if (isLoaded && !initialLoadComplete) {
setInitialLoadComplete(true);
}
}, [isLoaded, initialLoadComplete]);
useEffect(() => {
if (!initialLoadComplete) return;
const t = setTimeout(() => {
(async () => {
try {
const { currentUser, isAuthenticated, ...stateToSave } = state;
await fetch(`${API_BASE}/state`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(state),
body: JSON.stringify(stateToSave),
});
} catch (err) {
// ignore save errors for now
@@ -124,7 +137,7 @@ export function useHospitalStore() {
})();
}, 300);
return () => clearTimeout(t);
}, [state, isLoaded]);
}, [state, initialLoadComplete]);
// Acciones de navegación
const setVista = useCallback((vista: Vista) => {
@@ -570,6 +583,7 @@ const getEstadisticas = useCallback(() => {
throw new Error(err.error || 'Error en autenticación');
}
const user = await res.json();
sessionStorage.setItem('hospital_user', JSON.stringify(user));
setState(prev => ({ ...prev, currentUser: user, isAuthenticated: true }));
return user;
} catch (err) {
@@ -578,13 +592,9 @@ const getEstadisticas = useCallback(() => {
}, []);
const logout = useCallback(() => {
sessionStorage.removeItem('hospital_user');
setState(prev => {
const newState = { ...prev, currentUser: null, isAuthenticated: false };
fetch(`${API_BASE}/state`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...newState }),
}).catch(() => {});
return newState;
});
}, []);
@@ -644,6 +654,50 @@ const getEstadisticas = useCallback(() => {
return rol === 'admin';
}, [state.currentUser, state.internaciones, state.camas]);
const getCamaAreaId = useCallback((camaId: string): string | null => {
const cama = state.camas.find(c => c.id === camaId);
return cama?.areaId || null;
}, [state.camas]);
const getInternacionAreaId = useCallback((internacionId: string): string | null => {
const internacion = state.internaciones.find(i => i.id === internacionId);
if (!internacion) return null;
return getCamaAreaId(internacion.camaId);
}, [state.internaciones, getCamaAreaId]);
const getPacienteAreaId = useCallback((pacienteId: string): string | null => {
const internacion = state.internaciones.find(i => i.pacienteId === pacienteId && i.activa);
if (!internacion) {
const anyInternacion = state.internaciones.find(i => i.pacienteId === pacienteId);
if (anyInternacion) return getCamaAreaId(anyInternacion.camaId);
return null;
}
return getCamaAreaId(internacion.camaId);
}, [state.internaciones, getCamaAreaId]);
const canEditInArea = useCallback((areaId: string | null | undefined): boolean => {
const user = state.currentUser;
if (!user) return false;
if (user.rol === 'admin') return true;
if (!areaId) return false;
return user.areaId === areaId;
}, [state.currentUser]);
const canEditCama = useCallback((camaId: string): boolean => {
const areaId = getCamaAreaId(camaId);
return canEditInArea(areaId);
}, [getCamaAreaId, canEditInArea]);
const canEditInternacion = useCallback((internacionId: string): boolean => {
const areaId = getInternacionAreaId(internacionId);
return canEditInArea(areaId);
}, [getInternacionAreaId, canEditInArea]);
const canEditPaciente = useCallback((pacienteId: string): boolean => {
const areaId = getPacienteAreaId(pacienteId);
return canEditInArea(areaId);
}, [getPacienteAreaId, canEditInArea]);
return {
...state,
isLoaded,
@@ -702,5 +756,12 @@ const getEstadisticas = useCallback(() => {
updateEmail,
hasPermission,
canAccessInternacion,
canEditInArea,
canEditCama,
canEditInternacion,
canEditPaciente,
getCamaAreaId,
getInternacionAreaId,
getPacienteAreaId,
};
}
+61 -46
View File
@@ -69,6 +69,7 @@ interface HistoriaClinicaProps {
interconsultas: Interconsulta[];
atb: ATB[];
indicadores: Indicacion[];
canEdit: boolean;
onAgregarEvolucion: (evolucion: Omit<Evolucion, 'id'>) => void;
onActualizarEvolucion: (id: string, datos: Partial<Evolucion>) => void;
onEliminarEvolucion: (id: string) => void;
@@ -92,11 +93,8 @@ interface HistoriaClinicaProps {
onActualizarATB: (id: string, datos: Partial<ATB>) => void;
onEliminarATB: (id: string) => void;
onAgregarIndicacion: (indicacion: Omit<Indicacion, 'id'>) => void;
onActualizarIndicacion: (id: string, datos: Partial<Indicacion>) => void;
onActualizarIndicacion: (indicacion: Indicacion) => void;
onEliminarIndicacion: (id: string) => void;
movimientos?: any[];
onAgregarMovimiento?: (m: any) => void;
onActualizarInternacion: (id: string, datos: Partial<Internacion>) => void;
onActualizarCama: (id: string, datos: Partial<Cama>) => void;
onVolver: () => void;
onEditarIngreso?: () => void;
@@ -535,19 +533,19 @@ export function HistoriaClinica({
</ScrollArea>
<TabsContent value="laboratorios" className="mt-4 min-w-0 w-full overflow-hidden">
<SeccionLaboratorios lab={laboratorios} patientId={paciente.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} />
<SeccionLaboratorios lab={laboratorios} patientId={paciente.id} add={onAgregarLaboratorio} update={onActualizarLaboratorio} del={onEliminarLaboratorio} addAcidoBase={onAgregarAcidoBase} canEdit={canEdit} />
</TabsContent>
<TabsContent value="evoluciones" className="mt-4">
<SeccionEvoluciones evos={evoluciones} internacionId={internacion.id} add={onAgregarEvolucion} update={onActualizarEvolucion} del={onEliminarEvolucion} />
<SeccionEvoluciones evos={evoluciones} internacionId={internacion.id} add={onAgregarEvolucion} update={onActualizarEvolucion} del={onEliminarEvolucion} canEdit={canEdit} />
</TabsContent>
<TabsContent value="acidobase" className="mt-4">
<SeccionAcidosBase ab={acidosBase} patientId={paciente.id} add={onAgregarAcidoBase} update={onActualizarAcidoBase} del={onEliminarAcidoBase} />
<SeccionAcidosBase ab={acidosBase} patientId={paciente.id} add={onAgregarAcidoBase} update={onActualizarAcidoBase} del={onEliminarAcidoBase} canEdit={canEdit} />
</TabsContent>
<TabsContent value="cultivos" className="mt-4">
<SeccionCultivos cults={cultivos} patient={paciente} add={onAgregarCultivo} update={onActualizarCultivo} del={onEliminarCultivo} />
<SeccionCultivos cults={cultivos} patient={paciente} add={onAgregarCultivo} update={onActualizarCultivo} del={onEliminarCultivo} canEdit={canEdit} />
</TabsContent>
<TabsContent value="atb" className="mt-4">
@@ -558,6 +556,7 @@ export function HistoriaClinica({
add={onAgregarATB}
update={onActualizarATB}
del={onEliminarATB}
canEdit={canEdit}
/>
</TabsContent>
@@ -570,6 +569,7 @@ export function HistoriaClinica({
del={onEliminarIndicacion}
movimientos={movimientos}
onAgregarMovimiento={onAgregarMovimiento}
canEdit={canEdit}
/>
</TabsContent>
@@ -581,6 +581,7 @@ export function HistoriaClinica({
add={onAgregarEstudioComplementario}
update={onActualizarEstudioComplementario}
del={onEliminarEstudioComplementario}
canEdit={canEdit}
/>
</TabsContent>
@@ -592,6 +593,7 @@ export function HistoriaClinica({
add={onAgregarInterconsulta}
update={onActualizarInterconsulta}
del={onEliminarInterconsulta}
canEdit={canEdit}
/>
</TabsContent>
</Tabs>
@@ -601,13 +603,14 @@ export function HistoriaClinica({
);
}
function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase }: {
function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase, canEdit }: {
lab: Laboratorio[];
patientId: string;
add: (l: Omit<Laboratorio, 'id'>) => void;
update: (id: string, data: Partial<Laboratorio>) => void;
del: (id: string) => void;
addAcidoBase?: (a: Omit<AcidoBase, 'id'>) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [obsDialog, setObsDialog] = useState(false);
@@ -954,7 +957,7 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase }:
<div className="space-y-4 w-full max-w-full min-w-0">
<div className="flex justify-between">
<div className="flex gap-2">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo</Button>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo</Button>}
<Button variant="outline" onClick={() => { setSelectedLab(lab[0] || null); setParametroEvolucion('Hematocrito'); setEvolDialog(true); }}><TrendingUp className="h-4 w-4 mr-2" />Evolución</Button>
<Button variant="outline" onClick={() => { setImportFecha(new Date().toISOString().split('T')[0]); setImportHora(new Date().toTimeString().slice(0, 5)); setImportTexto(''); setImportResultados([]); setImportObservaciones(''); setImportDialog(true); }}><FileText className="h-4 w-4 mr-2" />Importar</Button>
</div>
@@ -1279,12 +1282,13 @@ function SeccionLaboratorios({ lab, patientId, add, update, del, addAcidoBase }:
);
}
function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
function SeccionEvoluciones({ evos, internacionId, add, update, del, canEdit }: {
evos: Evolucion[];
internacionId: string;
add: (e: Omit<Evolucion, 'id'>) => void;
update: (id: string, data: Partial<Evolucion>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [edit, setEdit] = useState<Evolucion | null>(null);
@@ -1376,7 +1380,7 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Evolución</Button>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Evolución</Button>}
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-3xl max-h-[90vh] overflow-y-auto">
@@ -1431,8 +1435,8 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
<div><p className="font-medium">{e.fecha} {e.hora}</p><p className="text-sm text-gray-600">Médico: {e.medico}</p></div>
<div className="flex gap-1">
<Button size="sm" variant="outline" onClick={() => setExpanded(isExpanded ? null : e.id)}>{isExpanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}</Button>
<Button size="sm" variant="outline" onClick={() => openEdit(e)}><Pencil /></Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}><Trash2 /></Button>
{canEdit && <Button size="sm" variant="outline" onClick={() => openEdit(e)}><Pencil /></Button>}
{canEdit && <Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}><Trash2 /></Button>}
</div>
</div>
{svText && <div className="bg-blue-50 p-2 rounded-lg"><p className="text-sm font-medium text-blue-800 flex items-center gap-2"><Activity className="h-4 w-4" />Signos Vitales: {svText}</p></div>}
@@ -1459,12 +1463,13 @@ function SeccionEvoluciones({ evos, internacionId, add, update, del }: {
);
}
function SeccionAcidosBase({ ab, patientId, add, update, del }: {
function SeccionAcidosBase({ ab, patientId, add, update, del, canEdit }: {
ab: AcidoBase[];
patientId: string;
add: (a: Omit<AcidoBase, 'id'>) => void;
update: (id: string, datos: Partial<AcidoBase>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [editDialog, setEditDialog] = useState(false);
@@ -1552,7 +1557,7 @@ function SeccionAcidosBase({ ab, patientId, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Gasometría</Button>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nueva Gasometría</Button>}
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-2xl max-h-[90vh] overflow-y-auto">
@@ -1691,12 +1696,13 @@ function SeccionAcidosBase({ ab, patientId, add, update, del }: {
);
}
function SeccionCultivos({ cults, patient, add, update, del }: {
function SeccionCultivos({ cults, patient, add, update, del, canEdit }: {
cults: Cultivo[];
patient: Paciente;
add: (c: Omit<Cultivo, 'id'>) => void;
update: (id: string, data: Partial<Cultivo>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [resDialog, setResDialog] = useState(false);
@@ -1811,7 +1817,7 @@ function SeccionCultivos({ cults, patient, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Cultivo</Button>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}><Plus className="h-4 w-4 mr-2" />Nuevo Cultivo</Button>}
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
<DialogContent className="sm:max-w-lg">
@@ -1966,13 +1972,14 @@ function SeccionCultivos({ cults, patient, add, update, del }: {
);
}
function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, add, update, del }: {
function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, add, update, del, canEdit }: {
estudios: EstudioComplementario[];
internacionId: string;
pacienteId: string;
add: (e: Omit<EstudioComplementario, 'id'>) => void;
update: (id: string, datos: Partial<EstudioComplementario>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [filtro, setFiltro] = useState<'todos' | 'internacion'>('internacion');
@@ -2021,10 +2028,10 @@ function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, a
<div className="space-y-4">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
<div className="flex gap-2">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />
Nuevo Estudio
</Button>
</Button>}
</div>
<Select value={filtro} onValueChange={(v: 'todos' | 'internacion') => setFiltro(v)}>
<SelectTrigger className="w-[180px]">
@@ -2095,13 +2102,15 @@ function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, a
<p className="text-sm text-gray-600 dark:text-gray-300 mt-1 whitespace-pre-wrap">{e.resultado}</p>
</div>
<div className="flex gap-1 self-start">
<Button size="sm" variant="outline" onClick={() => loadEdit(e)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
{canEdit && <>
<Button size="sm" variant="outline" onClick={() => loadEdit(e)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(e.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</>}
</div>
</div>
</CardContent>
</Card>
@@ -2112,13 +2121,14 @@ function SeccionEstudiosComplementarios({ estudios, internacionId, pacienteId, a
);
}
function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add, update, del }: {
function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add, update, del, canEdit }: {
interconsultas: Interconsulta[];
pacienteId: string;
internacionId: string;
add: (ic: Omit<Interconsulta, 'id'>) => void;
update: (id: string, datos: Partial<Interconsulta>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [fecha, setFecha] = useState(new Date().toISOString().split('T')[0]);
@@ -2167,10 +2177,10 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add,
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />
Nueva IC
</Button>
</Button>}
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
@@ -2265,12 +2275,14 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add,
)}
</div>
<div className="flex gap-1 self-start">
<Button size="sm" variant="outline" onClick={() => loadEdit(ic)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ic.id)}>
<Trash2 className="h-4 w-4" />
</Button>
{canEdit && <>
<Button size="sm" variant="outline" onClick={() => loadEdit(ic)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ic.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</>}
</div>
</div>
</CardContent>
@@ -2282,13 +2294,14 @@ function SeccionInterconsultas({ interconsultas, pacienteId, internacionId, add,
);
}
function SeccionATB({ atb, internacionId, pacienteId, add, update, del }: {
function SeccionATB({ atb, internacionId, pacienteId, add, update, del, canEdit }: {
atb: ATB[];
internacionId: string;
pacienteId: string;
add: (a: Omit<ATB, 'id'>) => void;
update: (id: string, data: Partial<ATB>) => void;
del: (id: string) => void;
canEdit?: boolean;
}) {
const [dialog, setDialog] = useState(false);
const [edit, setEdit] = useState<ATB | null>(null);
@@ -2333,9 +2346,9 @@ function SeccionATB({ atb, internacionId, pacienteId, add, update, del }: {
return (
<div className="space-y-4">
<div className="flex justify-between">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />Nuevo
</Button>
</Button>}
</div>
<Dialog open={dialog} onOpenChange={setDialog}>
@@ -2388,12 +2401,14 @@ function SeccionATB({ atb, internacionId, pacienteId, add, update, del }: {
</div>
</div>
<div className="flex gap-1">
<Button size="sm" variant="outline" onClick={() => loadEdit(a)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(a.id)}>
<Trash2 className="h-4 w-4" />
</Button>
{canEdit && <>
<Button size="sm" variant="outline" onClick={() => loadEdit(a)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(a.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</>}
</div>
</CardContent>
</Card>
+1
View File
@@ -20,6 +20,7 @@ export function Login() {
try {
await login(dni, password);
window.location.reload();
} catch (err: any) {
setError(err.message || 'Error de autenticación');
} finally {
+28 -22
View File
@@ -9,6 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import type { Cama, Paciente, Internacion, Area } from '@/types';
import { useHospitalStore } from '@/hooks/useHospitalStore';
interface MapaCamasProps {
camas: Cama[];
@@ -41,6 +42,7 @@ export function MapaCamas({
getPacienteById,
getInternacionById
}: MapaCamasProps) {
const { canEditCama, canEditInArea } = useHospitalStore();
const [filtroSala, setFiltroSala] = useState<string>('todas');
const [filtroTipo, setFiltroTipo] = useState<string>('todos');
const [filtroEstado, setFiltroEstado] = useState<string>('todos');
@@ -324,7 +326,7 @@ export function MapaCamas({
<div className="space-y-2">
<p className="font-medium">Acciones:</p>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{cama.estado === 'Disponible' && (
{cama.estado === 'Disponible' && canEditCama(cama.id) && (
<Dialog open={dialogoAbierto} onOpenChange={setDialogoAbierto}>
<DialogTrigger asChild>
<Button variant="outline"
@@ -409,9 +411,9 @@ export function MapaCamas({
</div>
</DialogContent>
</Dialog>
)}
{cama.estado !== 'Disponible' && (
)}
{cama.estado !== 'Disponible' && canEditCama(cama.id) && (
<Button
variant="outline"
className="w-full"
@@ -422,7 +424,7 @@ export function MapaCamas({
</Button>
)}
{cama.estado !== 'Reparacion' && (
{cama.estado !== 'Reparacion' && canEditCama(cama.id) && (
<Button
variant="outline"
className="w-full"
@@ -433,7 +435,7 @@ export function MapaCamas({
</Button>
)}
{cama.estado !== 'Reservada' && (
{cama.estado !== 'Reservada' && canEditCama(cama.id) && (
<Button
variant="outline"
className="w-full"
@@ -445,22 +447,26 @@ export function MapaCamas({
)}
<Button variant="outline" onClick={() => {
setEditingBed(cama);
setBedNumero(cama.numero);
setBedTipo(cama.tipo);
setBedAreaId(cama.areaId);
setBedDialogOpen(true);
}}>
<Edit2 className="h-4 w-4 mr-1" />Editar
</Button>
<Button variant="destructive" onClick={() => {
if (confirm('Eliminar esta cama?')) {
onEliminarCama(cama.id);
}
}}>
<Trash className="h-4 w-4 mr-1" />Eliminar
</Button>
{canEditCama(cama.id) && (
<Button variant="outline" onClick={() => {
setEditingBed(cama);
setBedNumero(cama.numero);
setBedTipo(cama.tipo);
setBedAreaId(cama.areaId);
setBedDialogOpen(true);
}}>
<Edit2 className="h-4 w-4 mr-1" />Editar
</Button>
)}
{canEditCama(cama.id) && (
<Button variant="destructive" onClick={() => {
if (confirm('Eliminar esta cama?')) {
onEliminarCama(cama.id);
}
}}>
<Trash className="h-4 w-4 mr-1" />Eliminar
</Button>
)}
</div>
</div>
</div>
+8 -7
View File
@@ -10,7 +10,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
import { Plus, Pencil, Trash2, X } from 'lucide-react';
import type { Indicacion, IndicacionTipo, ViaAdministracion, TipoPlanHidratacion, TipoInsulina } from '@/types';
export function SeccionIndicaciones({ recomendaciones, internacionId, add, update, del, movimientos, onAgregarMovimiento }: {
export function SeccionIndicaciones({ recomendaciones, internacionId, add, update, del, movimientos, onAgregarMovimiento, canEdit }: {
recomendaciones: Indicacion[];
internacionId: string;
add: (i: Omit<Indicacion, 'id'>) => void;
@@ -18,6 +18,7 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
del: (id: string) => void;
movimientos?: any[];
onAgregarMovimiento?: (m: any) => void;
canEdit?: boolean;
}) {
const list: Indicacion[] = Array.isArray(recomendaciones) ? recomendaciones : [];
const movs: any[] = Array.isArray(movimientos) ? movimientos : [];
@@ -218,9 +219,9 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
return (
<div className="space-y-4">
<div className="flex justify-between">
<Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
{canEdit && <Button variant="outline" onClick={() => { reset(); setDialog(true); }}>
<Plus className="h-4 w-4 mr-2" />Nueva Indicación
</Button>
</Button>}
<Button variant="ghost" onClick={() => setShowHistorial(!showHistorial)}>
{showHistorial ? 'Ocultar Historial' : `Ver Historial (${movs.length})`}
</Button>
@@ -355,12 +356,12 @@ export function SeccionIndicaciones({ recomendaciones, internacionId, add, updat
}}>
<X className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" onClick={() => loadEdit(ind)}>
{canEdit && <Button size="sm" variant="outline" onClick={() => loadEdit(ind)}>
<Pencil className="h-4 w-4" />
</Button>
<Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ind.id)}>
</Button>}
{canEdit && <Button size="sm" variant="outline" className="text-red-600" onClick={() => del(ind.id)}>
<Trash2 className="h-4 w-4" />
</Button>
</Button>}
</div>
</CardContent>
</Card>