feat: support editing plan details and deleting drafts with backend endpoints
This commit is contained in:
parent
bff2c3974e
commit
08fe3954a2
5 changed files with 141 additions and 18 deletions
|
|
@ -149,3 +149,54 @@ export const PUT = withAuth(async (req, { session, prisma, params }) => {
|
||||||
return NextResponse.json({ plan: updatedPlan, versioned: false });
|
return NextResponse.json({ plan: updatedPlan, versioned: false });
|
||||||
}
|
}
|
||||||
}, ['admin', 'director']);
|
}, ['admin', 'director']);
|
||||||
|
|
||||||
|
// DELETE: Delete a plan (restricted to admin and director, and only allowed for DRAFT plans)
|
||||||
|
export const DELETE = withAuth(async (req, { session, prisma, params }) => {
|
||||||
|
const unwrappedParams = await params;
|
||||||
|
const id = parseInt(unwrappedParams.id);
|
||||||
|
|
||||||
|
if (isNaN(id)) {
|
||||||
|
return NextResponse.json({ error: 'ID no válido.' }, { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const plan = await prisma.compensationPlan.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { settlements: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!plan) {
|
||||||
|
return NextResponse.json({ error: 'Plan no encontrado.' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.status !== 'DRAFT') {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Solo se pueden eliminar planes en estado BORRADOR (DRAFT).' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan.settlements && plan.settlements.length > 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'No se puede eliminar el plan porque tiene liquidaciones asociadas.' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$transaction(async (tx: any) => {
|
||||||
|
await createAuditLog(tx, {
|
||||||
|
userId: session.userId,
|
||||||
|
action: 'DELETE',
|
||||||
|
targetTable: 'compensation_plans',
|
||||||
|
targetId: id,
|
||||||
|
previousValue: plan,
|
||||||
|
newValue: null,
|
||||||
|
ipAddress: req.headers.get('x-forwarded-for') || '127.0.0.1'
|
||||||
|
});
|
||||||
|
|
||||||
|
await tx.compensationPlan.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
}, ['admin', 'director']);
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,7 @@
|
||||||
|
|
||||||
.cardFooter {
|
.cardFooter {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: var(--space-2);
|
gap: var(--space-2);
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,22 @@ export default function PlansPage() {
|
||||||
const [maxCap, setMaxCap] = useState('');
|
const [maxCap, setMaxCap] = useState('');
|
||||||
const [status, setStatus] = useState('DRAFT');
|
const [status, setStatus] = useState('DRAFT');
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [editPlanId, setEditPlanId] = useState<number | null>(null);
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
setName('');
|
||||||
|
setCode('');
|
||||||
|
setValidityStart('');
|
||||||
|
setValidityEnd('');
|
||||||
|
setType('PERCENTAGE');
|
||||||
|
setMetaAmount('');
|
||||||
|
setPercentageRate('');
|
||||||
|
setMaxCap('');
|
||||||
|
setStatus('DRAFT');
|
||||||
|
setEditPlanId(null);
|
||||||
|
setError(null);
|
||||||
|
setShowModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
const fetchPlans = async () => {
|
const fetchPlans = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -80,8 +96,10 @@ export default function PlansPage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/plans', {
|
const url = editPlanId ? `/api/plans/${editPlanId}` : '/api/plans';
|
||||||
method: 'POST',
|
const method = editPlanId ? 'PUT' : 'POST';
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method,
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
name,
|
name,
|
||||||
|
|
@ -99,27 +117,53 @@ export default function PlansPage() {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
setError(data.error || 'Failed to create plan');
|
setError(data.error || 'Failed to submit plan');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset form & reload
|
handleCloseModal();
|
||||||
setName('');
|
|
||||||
setCode('');
|
|
||||||
setValidityStart('');
|
|
||||||
setValidityEnd('');
|
|
||||||
setType('PERCENTAGE');
|
|
||||||
setMetaAmount('');
|
|
||||||
setPercentageRate('');
|
|
||||||
setMaxCap('');
|
|
||||||
setStatus('DRAFT');
|
|
||||||
setShowModal(false);
|
|
||||||
fetchPlans();
|
fetchPlans();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError('An error occurred during submission.');
|
setError('An error occurred during submission.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditClick = (plan: Plan) => {
|
||||||
|
setName(plan.name);
|
||||||
|
setCode(plan.code);
|
||||||
|
setValidityStart(plan.validityStart ? plan.validityStart.substring(0, 10) : '');
|
||||||
|
setValidityEnd(plan.validityEnd ? plan.validityEnd.substring(0, 10) : '');
|
||||||
|
setType(plan.type);
|
||||||
|
setMetaAmount(plan.metaAmount ? plan.metaAmount.toString() : '');
|
||||||
|
setPercentageRate(plan.percentageRate ? plan.percentageRate.toString() : '');
|
||||||
|
setMaxCap(plan.maxCap ? plan.maxCap.toString() : '');
|
||||||
|
setStatus(plan.status);
|
||||||
|
setEditPlanId(plan.id);
|
||||||
|
setShowModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeletePlan = async (planId: number) => {
|
||||||
|
if (!confirm(t('plans.confirmDelete'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/plans/${planId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
alert(data.error || 'Failed to delete plan');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPlans();
|
||||||
|
} catch (err) {
|
||||||
|
alert('An error occurred while deleting the plan.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleToggleStatus = async (plan: Plan) => {
|
const handleToggleStatus = async (plan: Plan) => {
|
||||||
const nextStatus = plan.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
|
const nextStatus = plan.status === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE';
|
||||||
try {
|
try {
|
||||||
|
|
@ -207,6 +251,23 @@ export default function PlansPage() {
|
||||||
>
|
>
|
||||||
{plan.status === 'ACTIVE' ? t('plans.deactivateVersion') : t('plans.activate')}
|
{plan.status === 'ACTIVE' ? t('plans.deactivateVersion') : t('plans.activate')}
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleEditClick(plan)}
|
||||||
|
className={styles.btnSecondary}
|
||||||
|
id={`btn-edit-${plan.id}`}
|
||||||
|
>
|
||||||
|
{t('plans.editPlan')}
|
||||||
|
</button>
|
||||||
|
{plan.status === 'DRAFT' && (
|
||||||
|
<button
|
||||||
|
onClick={() => handleDeletePlan(plan.id)}
|
||||||
|
className={styles.btnSecondary}
|
||||||
|
style={{ borderColor: 'hsl(0, 75%, 60%)', color: 'hsl(0, 75%, 60%)' }}
|
||||||
|
id={`btn-delete-${plan.id}`}
|
||||||
|
>
|
||||||
|
{t('plans.deletePlan')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Link href={`/plans/${plan.id}/rules`} className={styles.btnSecondary} id={`btn-rules-${plan.id}`}>
|
<Link href={`/plans/${plan.id}/rules`} className={styles.btnSecondary} id={`btn-rules-${plan.id}`}>
|
||||||
|
|
@ -224,7 +285,7 @@ export default function PlansPage() {
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className={styles.modalOverlay} role="dialog">
|
<div className={styles.modalOverlay} role="dialog">
|
||||||
<div className={styles.modal}>
|
<div className={styles.modal}>
|
||||||
<h2 className={styles.modalTitle}>Crear {t('plans.newPlan')}</h2>
|
<h2 className={styles.modalTitle}>{editPlanId ? t('plans.modalEditTitle') : t('plans.modalCreateTitle')}</h2>
|
||||||
<form onSubmit={handleCreatePlan} className={styles.form}>
|
<form onSubmit={handleCreatePlan} className={styles.form}>
|
||||||
{error && <div className={styles.errorText}>{error}</div>}
|
{error && <div className={styles.errorText}>{error}</div>}
|
||||||
|
|
||||||
|
|
@ -332,7 +393,7 @@ export default function PlansPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.formActions}>
|
<div className={styles.formActions}>
|
||||||
<button type="button" onClick={() => setShowModal(false)} className={styles.btnSecondary}>
|
<button type="button" onClick={handleCloseModal} className={styles.btnSecondary}>
|
||||||
{t('plans.modalCancel')}
|
{t('plans.modalCancel')}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" className={styles.btnPrimary} id="btn-save-plan">
|
<button type="submit" className={styles.btnPrimary} id="btn-save-plan">
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,12 @@
|
||||||
},
|
},
|
||||||
"errorFind": "Could not find the plan.",
|
"errorFind": "Could not find the plan.",
|
||||||
"errorLoad": "Error loading plan information.",
|
"errorLoad": "Error loading plan information.",
|
||||||
"modalValidityEnd": "End Validity (Optional)"
|
"modalValidityEnd": "End Validity (Optional)",
|
||||||
|
"editPlan": "Edit Details",
|
||||||
|
"deletePlan": "Delete Plan",
|
||||||
|
"confirmDelete": "Are you sure you want to delete this plan? This action cannot be undone.",
|
||||||
|
"modalEditTitle": "Edit Plan",
|
||||||
|
"modalCreateTitle": "Create New Plan"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"back": "Back to Plans",
|
"back": "Back to Plans",
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,12 @@
|
||||||
},
|
},
|
||||||
"errorFind": "No se pudo encontrar el plan.",
|
"errorFind": "No se pudo encontrar el plan.",
|
||||||
"errorLoad": "Error al cargar la información del plan.",
|
"errorLoad": "Error al cargar la información del plan.",
|
||||||
"modalValidityEnd": "Fin de Vigencia (Opcional)"
|
"modalValidityEnd": "Fin de Vigencia (Opcional)",
|
||||||
|
"editPlan": "Editar Detalles",
|
||||||
|
"deletePlan": "Eliminar Plan",
|
||||||
|
"confirmDelete": "¿Está seguro de que desea eliminar este plan? Esta acción no se puede deshacer.",
|
||||||
|
"modalEditTitle": "Editar Plan",
|
||||||
|
"modalCreateTitle": "Crear Nuevo Plan"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"back": "Volver a Planes",
|
"back": "Volver a Planes",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue