Project deleting

This commit is contained in:
2026-02-03 01:53:49 -06:00
parent 5529739749
commit 040f3f97dd
6 changed files with 195 additions and 7 deletions

View File

@@ -224,10 +224,18 @@ async function parseResponse<T>(response: Response): Promise<T> {
if (data && typeof data === 'object') {
if ('success' in data) {
if (data.success === false) {
const errorMessage = typeof data.error === 'string'
? data.error
: (data.error?.message || 'Request failed');
const errorDetails = typeof data.error === 'object'
? data.error?.errors
: undefined;
throw new ApiError(
data.error?.message || 'Request failed',
errorMessage,
response.status,
data.error?.errors
errorDetails
);
}
// If response has pagination, return object with data and pagination

View File

@@ -130,6 +130,16 @@ export async function deleteProject(id: string): Promise<void> {
return apiClient.delete<void>(`/api/projects/${id}`);
}
/**
* Deactivate a project and unassign users
* @param id - The project ID
* @returns Promise resolving when the project is deactivated
*/
export async function deactivateProject(id: string): Promise<Project> {
const response = await apiClient.post<Record<string, unknown>>(`/api/projects/${id}/deactivate`, {});
return transformKeys<Project>(response);
}
/**
* Fetch unique area names from all projects
* @returns Promise resolving to an array of unique area names

View File

@@ -8,6 +8,7 @@ import {
createProject as apiCreateProject,
updateProject as apiUpdateProject,
deleteProject as apiDeleteProject,
deactivateProject as apiDeactivateProject,
} from "../../api/projects";
import { fetchMeterTypes, type MeterType } from "../../api/meterTypes";
import { getCurrentUserRole, getCurrentUserProjectId } from "../../api/auth";
@@ -27,6 +28,10 @@ export default function ProjectsPage() {
const [meterTypes, setMeterTypes] = useState<MeterType[]>([]);
const [showDeactivateModal, setShowDeactivateModal] = useState(false);
const [projectToDeactivate, setProjectToDeactivate] = useState<Project | null>(null);
const [usersAssignedCount, setUsersAssignedCount] = useState(0);
const emptyForm: ProjectInput = {
name: "",
description: "",
@@ -119,11 +124,33 @@ export default function ProjectsPage() {
setActiveProject(null);
} catch (error) {
console.error("Error deleting project:", error);
alert(
`Error deleting project: ${
error instanceof Error ? error.message : "Please try again."
}`
);
// Get error message - ApiError extends Error
let errorMessage = "";
if (error instanceof Error) {
errorMessage = error.message;
} else if (typeof error === 'string') {
errorMessage = error;
}
console.log("Error message:", errorMessage); // Debug log
// Check if error is about users assigned to the project
if (errorMessage.includes("user(s) are assigned to this project")) {
// Extract number of users from error message
const match = errorMessage.match(/(\d+) user\(s\)/);
const userCount = match ? parseInt(match[1]) : 1;
console.log("Users assigned:", userCount); // Debug log
setUsersAssignedCount(userCount);
setProjectToDeactivate(activeProject);
setShowDeactivateModal(true);
} else {
alert(
`Error deleting project: ${errorMessage || "Please try again."}`
);
}
}
};
@@ -147,6 +174,31 @@ export default function ProjectsPage() {
setShowModal(true);
};
const handleConfirmDeactivate = async () => {
if (!projectToDeactivate) return;
try {
const deactivatedProject = await apiDeactivateProject(projectToDeactivate.id);
setProjects((prev) =>
prev.map((p) => (p.id === deactivatedProject.id ? deactivatedProject : p))
);
setShowDeactivateModal(false);
setProjectToDeactivate(null);
setActiveProject(null);
alert(`Proyecto "${projectToDeactivate.name}" ha sido desactivado y los usuarios han sido desasignados.`);
} catch (error) {
console.error("Error deactivating project:", error);
alert(
`Error deactivating project: ${
error instanceof Error ? error.message : "Please try again."
}`
);
}
};
const filtered = visibleProjects.filter((p) =>
`${p.name} ${p.areaName} ${p.description ?? ""}`
.toLowerCase()
@@ -383,6 +435,60 @@ export default function ProjectsPage() {
</div>
</div>
)}
{showDeactivateModal && projectToDeactivate && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
<div className="bg-white rounded-xl p-6 w-[500px] space-y-4">
<h2 className="text-lg font-semibold text-gray-900">
Proyecto con Usuarios Asignados
</h2>
<div className="space-y-3">
<p className="text-sm text-gray-700">
El proyecto <span className="font-semibold">"{projectToDeactivate.name}"</span> tiene{" "}
<span className="font-semibold text-blue-600">{usersAssignedCount} usuario(s)</span> asignado(s).
</p>
<p className="text-sm text-gray-700">
No se puede eliminar directamente. ¿Deseas continuar con las siguientes acciones?
</p>
<ul className="text-sm text-gray-700 space-y-2 bg-amber-50 border border-amber-200 rounded-lg p-4">
<li className="flex items-start gap-2">
<span className="text-amber-600 font-bold"></span>
<span>El proyecto será <span className="font-semibold">desactivado</span> (status = INACTIVE)</span>
</li>
<li className="flex items-start gap-2">
<span className="text-amber-600 font-bold"></span>
<span>Los <span className="font-semibold">{usersAssignedCount} usuario(s)</span> serán desasignados (project_id = null)</span>
</li>
</ul>
<p className="text-xs text-gray-500">
Nota: El proyecto no será eliminado de la base de datos, solo desactivado.
</p>
</div>
<div className="flex justify-end gap-2 pt-3 border-t">
<button
onClick={() => {
setShowDeactivateModal(false);
setProjectToDeactivate(null);
}}
className="px-4 py-2 rounded hover:bg-gray-100"
>
Cancelar
</button>
<button
onClick={handleConfirmDeactivate}
className="bg-amber-600 text-white px-4 py-2 rounded hover:bg-amber-700"
>
, Desactivar Proyecto
</button>
</div>
</div>
</div>
)}
</div>
);
}