mirror of
https://github.com/f-eld-ch/sitrep.git
synced 2026-07-16 16:47:05 +02:00
feat(ui): allow incidents to be deleted when closed (#1156)
* feat(ui): allow incidents to be deleted * fix(incidents): fix broken Incident list view tests
This commit is contained in:
committed by
GitHub
parent
fcdee40074
commit
e6cbb9287f
@@ -62,6 +62,7 @@ update_permissions:
|
||||
permission:
|
||||
columns:
|
||||
- closed_at
|
||||
- deleted_at
|
||||
- location_id
|
||||
- name
|
||||
filter: {}
|
||||
|
||||
@@ -28,6 +28,7 @@ select_permissions:
|
||||
- created_at
|
||||
- deleted_at
|
||||
- id
|
||||
- incident_id
|
||||
- name
|
||||
- updated_at
|
||||
filter: {}
|
||||
@@ -38,6 +39,7 @@ select_permissions:
|
||||
- created_at
|
||||
- deleted_at
|
||||
- id
|
||||
- incident_id
|
||||
- name
|
||||
- updated_at
|
||||
filter: {}
|
||||
@@ -46,6 +48,7 @@ update_permissions:
|
||||
permission:
|
||||
columns:
|
||||
- closed_at
|
||||
- deleted_at
|
||||
- name
|
||||
filter: {}
|
||||
check: null
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"createNewRequest": "Neuen Antrag erstellen",
|
||||
"createNewTask": "Pendenz erfassen",
|
||||
"createdAt": "Eröffnet",
|
||||
"delete": "löschen",
|
||||
"divisions": "Fachbereiche",
|
||||
"edit": "bearbeiten",
|
||||
"editIncident": "Ereignis bearbeiten",
|
||||
@@ -270,4 +271,4 @@
|
||||
"styleController": {
|
||||
"maps": "Karten"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"createNewTask": "create new task",
|
||||
"createdAt": "opened",
|
||||
"divisions": "divisions",
|
||||
"delete": "delete",
|
||||
"edit": "edit",
|
||||
"editIncident": "edit incident",
|
||||
"editor": "editor",
|
||||
@@ -270,4 +271,4 @@
|
||||
"styleController": {
|
||||
"maps": "Maps"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"createNewTask": "créer une nouvelle tâche",
|
||||
"createdAt": "ouvert",
|
||||
"divisions": "domaine",
|
||||
"delete": "supprimer",
|
||||
"edit": "éditer",
|
||||
"editIncident": "modifier l'incident",
|
||||
"editor": "éditeur",
|
||||
@@ -269,4 +270,4 @@
|
||||
},
|
||||
"write": "écrivez",
|
||||
"updateNotification": "Une nouvelle version de l'application est disponible. Veuillez actualiser la page en cliquant sur le bouton Refresh."
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"createNewTask": "crea una nuova attività",
|
||||
"createdAt": "aperto",
|
||||
"divisions": "dipartimenti",
|
||||
"delete": "eliminare",
|
||||
"edit": "modificare",
|
||||
"editIncident": "modifica incidente",
|
||||
"editor": "editore",
|
||||
@@ -269,4 +270,4 @@
|
||||
},
|
||||
"write": "scrivere",
|
||||
"updateNotification": "È disponibile una nuova versione dell'applicazione. Si prega di aggiornare la pagina con il pulsante di aggiornamento."
|
||||
}
|
||||
}
|
||||
@@ -81,3 +81,14 @@ export type CloseIncidentMutationVariables = {
|
||||
incidentId?: string;
|
||||
closedAt?: Date | null;
|
||||
};
|
||||
|
||||
export type DeleteIncidentMutation = {
|
||||
updateIncidents: {
|
||||
affectedRows: number;
|
||||
returning: { id: string; deletedAt: Date | null }[];
|
||||
} | null;
|
||||
};
|
||||
export type DeleteIncidentMutationVariables = {
|
||||
incidentId?: string;
|
||||
deletedAt?: Date | null;
|
||||
};
|
||||
|
||||
+160
-122
@@ -1,151 +1,189 @@
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { IncidentCard, IncidentCards } from "./List";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
import type { Incident } from "../../types";
|
||||
import { IncidentCard, IncidentCards } from "./List";
|
||||
|
||||
// Mock useNavigate
|
||||
const mockNavigate = vi.fn();
|
||||
vi.mock("react-router", () => ({
|
||||
useNavigate: () => mockNavigate,
|
||||
useNavigate: () => mockNavigate,
|
||||
}));
|
||||
|
||||
// Mock useTranslation
|
||||
vi.mock("react-i18next", () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
|
||||
// Mock dayjs
|
||||
vi.mock("dayjs", () => {
|
||||
const actual = vi.importActual("dayjs");
|
||||
return {
|
||||
__esModule: true,
|
||||
default: (_date: string | Date) => ({ format: () => "formatted-date" }),
|
||||
...actual,
|
||||
};
|
||||
const actual = vi.importActual("dayjs");
|
||||
return {
|
||||
__esModule: true,
|
||||
default: (_date: string | Date) => ({ format: () => "formatted-date" }),
|
||||
...actual,
|
||||
};
|
||||
});
|
||||
|
||||
describe("IncidentCard", () => {
|
||||
const baseIncident: Incident = {
|
||||
id: "1",
|
||||
name: "Test Incident",
|
||||
location: { name: "Test Location", id: "loc1", coordinates: "0,0" },
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(0),
|
||||
closedAt: new Date(0),
|
||||
divisions: [],
|
||||
journals: [],
|
||||
layers: [],
|
||||
};
|
||||
const mockCloseIncident = vi.fn();
|
||||
const baseIncident: Incident = {
|
||||
id: "1",
|
||||
name: "Test Incident",
|
||||
location: { name: "Test Location", id: "loc1", coordinates: "0,0" },
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
closedAt: null,
|
||||
divisions: [],
|
||||
journals: [],
|
||||
layers: [],
|
||||
};
|
||||
const mockCloseIncident = vi.fn();
|
||||
const mockDeleteIncident = vi.fn();
|
||||
|
||||
it("renders incident name and location", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Test Incident")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Location")).toBeInTheDocument();
|
||||
});
|
||||
it("renders incident name and location", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Test Incident")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Location")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders close button when incident is open", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: null }}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("close-button")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("open-button")).not.toBeInTheDocument();
|
||||
});
|
||||
it("renders close button when incident is open", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: null }}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("close-button")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("open-button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("delete-button")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders open button when incident is closed", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: new Date() }}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("open-button")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("close-button")).not.toBeInTheDocument();
|
||||
});
|
||||
it("renders open button when incident is closed", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: new Date() }}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("open-button")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("close-button")).not.toBeInTheDocument();
|
||||
});
|
||||
it("renders delete button when incident is closed", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: new Date() }}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId("open-button")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("delete-button")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls navigate when enter button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("enter-button"));
|
||||
expect(mockNavigate).toHaveBeenCalled();
|
||||
});
|
||||
it("calls navigate when enter button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("enter-button"));
|
||||
expect(mockNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls navigate when edit button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("edit-button"));
|
||||
expect(mockNavigate).toHaveBeenCalled();
|
||||
});
|
||||
it("calls navigate when edit button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={baseIncident}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("edit-button"));
|
||||
expect(mockNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls closeIncident when close button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: null }}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("close-button"));
|
||||
expect(mockCloseIncident).toHaveBeenCalled();
|
||||
});
|
||||
it("calls closeIncident when close button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: null }}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByTestId("close-button"));
|
||||
expect(mockCloseIncident).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("calls closeIncident when open button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: new Date() }}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
const openButton = screen.getByTestId("open-button");
|
||||
fireEvent.click(openButton);
|
||||
expect(mockCloseIncident).toHaveBeenCalled();
|
||||
});
|
||||
it("calls closeIncident when open button is clicked", () => {
|
||||
render(
|
||||
<IncidentCard
|
||||
incident={{ ...baseIncident, closedAt: new Date() }}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
const openButton = screen.getByTestId("open-button");
|
||||
fireEvent.click(openButton);
|
||||
expect(mockCloseIncident).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("IncidentCards", () => {
|
||||
const baseIncident: Incident = {
|
||||
id: "1",
|
||||
name: "Test Incident",
|
||||
location: { name: "Test Location", id: "loc1", coordinates: "0,0" },
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(0),
|
||||
closedAt: new Date(0),
|
||||
divisions: [],
|
||||
journals: [],
|
||||
layers: [],
|
||||
};
|
||||
const mockCloseIncident = vi.fn();
|
||||
const baseIncident: Incident = {
|
||||
id: "1",
|
||||
name: "Test Incident",
|
||||
location: { name: "Test Location", id: "loc1", coordinates: "0,0" },
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
closedAt: null,
|
||||
divisions: [],
|
||||
journals: [],
|
||||
layers: [],
|
||||
};
|
||||
const mockCloseIncident = vi.fn();
|
||||
const mockDeleteIncident = vi.fn();
|
||||
|
||||
it("renders multiple IncidentCard components", () => {
|
||||
render(
|
||||
<IncidentCards
|
||||
incidents={[
|
||||
baseIncident,
|
||||
{ ...baseIncident, id: "2", name: "Another Incident" },
|
||||
]}
|
||||
closeIncident={mockCloseIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Test Incident")).toBeInTheDocument();
|
||||
expect(screen.getByText("Another Incident")).toBeInTheDocument();
|
||||
// Assert the number of IncidentCard children by counting a unique test id
|
||||
expect(screen.getAllByTestId("edit-button")).toHaveLength(2);
|
||||
});
|
||||
it("renders multiple IncidentCard components", () => {
|
||||
render(
|
||||
<IncidentCards
|
||||
incidents={[
|
||||
baseIncident,
|
||||
{ ...baseIncident, id: "2", name: "Another Incident" },
|
||||
]}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Test Incident")).toBeInTheDocument();
|
||||
expect(screen.getByText("Another Incident")).toBeInTheDocument();
|
||||
// Assert the number of IncidentCard children by counting a unique test id
|
||||
expect(screen.getAllByTestId("edit-button")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders only non-deleted IncidentCard components", () => {
|
||||
render(
|
||||
<IncidentCards
|
||||
incidents={[
|
||||
baseIncident,
|
||||
{ ...baseIncident, id: "2", name: "Another Incident", deletedAt: new Date() },
|
||||
]}
|
||||
closeIncident={mockCloseIncident}
|
||||
deleteIncident={mockDeleteIncident}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Test Incident")).toBeInTheDocument();
|
||||
// Assert the number of IncidentCard children by counting a unique test id
|
||||
expect(screen.getAllByTestId("edit-button")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
+203
-158
@@ -1,12 +1,13 @@
|
||||
import { useMutation, useQuery } from "@apollo/client/react";
|
||||
import {
|
||||
faArrowRightFromBracket,
|
||||
faEdit,
|
||||
faEye,
|
||||
faEyeLowVision,
|
||||
faFolderClosed,
|
||||
faFolderOpen,
|
||||
faPlusCircle,
|
||||
faArrowRightFromBracket,
|
||||
faEdit,
|
||||
faEye,
|
||||
faEyeLowVision,
|
||||
faFolderClosed,
|
||||
faFolderOpen,
|
||||
faPlusCircle,
|
||||
faTrash,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import classNames from "classnames";
|
||||
@@ -17,187 +18,231 @@ import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router";
|
||||
import type {
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables,
|
||||
Incident,
|
||||
DeleteIncidentMutation,
|
||||
DeleteIncidentMutationVariables,
|
||||
} from "types/incident";
|
||||
import type {
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables,
|
||||
Incident,
|
||||
} from "../../types";
|
||||
import { CloseIncident, GetIncidentDetails, GetIncidents } from "./graphql";
|
||||
import {
|
||||
CloseIncident,
|
||||
DeleteIncident,
|
||||
GetIncidentDetails,
|
||||
GetIncidents,
|
||||
} from "./graphql";
|
||||
|
||||
function List() {
|
||||
const [filterClosed, setFilterClosed] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
const [filterClosed, setFilterClosed] = useState(true);
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const { loading, error, data } = useQuery(GetIncidents, {
|
||||
pollInterval: 10000,
|
||||
});
|
||||
const [closeIncident] = useMutation(CloseIncident, {
|
||||
refetchQueries: [{ query: GetIncidents }, { query: GetIncidentDetails }],
|
||||
});
|
||||
const { loading, error, data } = useQuery(GetIncidents, {
|
||||
pollInterval: 10000,
|
||||
});
|
||||
const [closeIncident] = useMutation(CloseIncident, {
|
||||
refetchQueries: [{ query: GetIncidents }, { query: GetIncidentDetails }],
|
||||
});
|
||||
|
||||
if (error)
|
||||
return <div className="notification is-danger">{error.message}</div>;
|
||||
if (loading && !data) return <Spinner />;
|
||||
const [deleteIncident] = useMutation(DeleteIncident, {
|
||||
refetchQueries: [{ query: GetIncidents }, { query: GetIncidentDetails }],
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="title is-size-3 is-capitalized">{t("incidents")}</h3>
|
||||
<div className="buttons">
|
||||
<button
|
||||
type="button"
|
||||
className="button is-success is-small is-responsive is-rounded is-light is-capitalized"
|
||||
onClick={() => navigate("../new")}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<FontAwesomeIcon icon={faPlusCircle} />
|
||||
</span>
|
||||
<span>{t("create")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="button is-primary is-small is-responsive is-rounded is-light"
|
||||
onClick={() => setFilterClosed(!filterClosed)}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<FontAwesomeIcon icon={filterClosed ? faEye : faEyeLowVision} />
|
||||
</span>
|
||||
<span>{filterClosed ? t("showClosed") : t("hideClosed")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<IncidentCards
|
||||
incidents={
|
||||
data?.incidents.filter(
|
||||
(incident) => !filterClosed || incident.closedAt === null,
|
||||
) || []
|
||||
}
|
||||
closeIncident={closeIncident}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (error)
|
||||
return <div className="notification is-danger">{error.message}</div>;
|
||||
if (loading && !data) return <Spinner />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h3 className="title is-size-3 is-capitalized">{t("incidents")}</h3>
|
||||
<div className="buttons">
|
||||
<button
|
||||
type="button"
|
||||
className="button is-success is-small is-responsive is-rounded is-light is-capitalized"
|
||||
onClick={() => navigate("../new")}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<FontAwesomeIcon icon={faPlusCircle} />
|
||||
</span>
|
||||
<span>{t("create")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="button is-primary is-small is-responsive is-rounded is-light"
|
||||
onClick={() => setFilterClosed(!filterClosed)}
|
||||
>
|
||||
<span className="icon is-small">
|
||||
<FontAwesomeIcon icon={filterClosed ? faEye : faEyeLowVision} />
|
||||
</span>
|
||||
<span>{filterClosed ? t("showClosed") : t("hideClosed")}</span>
|
||||
</button>
|
||||
</div>
|
||||
<IncidentCards
|
||||
incidents={
|
||||
data?.incidents.filter(
|
||||
(incident) => !filterClosed || incident.closedAt === null,
|
||||
) || []
|
||||
}
|
||||
closeIncident={closeIncident}
|
||||
deleteIncident={deleteIncident}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function IncidentCards(props: {
|
||||
incidents: Incident[];
|
||||
closeIncident: useMutation.MutationFunction<
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables
|
||||
>;
|
||||
incidents: Incident[];
|
||||
closeIncident: useMutation.MutationFunction<
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables
|
||||
>;
|
||||
deleteIncident: useMutation.MutationFunction<
|
||||
DeleteIncidentMutation,
|
||||
DeleteIncidentMutationVariables
|
||||
>;
|
||||
}) {
|
||||
const { incidents, closeIncident } = props;
|
||||
const { incidents, closeIncident, deleteIncident } = props;
|
||||
|
||||
return (
|
||||
<div className="container-flex">
|
||||
{incidents.map((incident) => (
|
||||
<IncidentCard
|
||||
key={incident.id}
|
||||
incident={incident}
|
||||
closeIncident={closeIncident}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="container-flex">
|
||||
{incidents.filter((incident) => !incident.deletedAt).map((incident) => (
|
||||
<IncidentCard
|
||||
key={incident.id}
|
||||
incident={incident}
|
||||
closeIncident={closeIncident}
|
||||
deleteIncident={deleteIncident}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function IncidentCard(props: {
|
||||
incident: Incident;
|
||||
closeIncident: useMutation.MutationFunction<
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables
|
||||
>;
|
||||
incident: Incident;
|
||||
closeIncident: useMutation.MutationFunction<
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables
|
||||
>;
|
||||
deleteIncident: useMutation.MutationFunction<
|
||||
DeleteIncidentMutation,
|
||||
DeleteIncidentMutationVariables
|
||||
>;
|
||||
}) {
|
||||
const { incident, closeIncident } = props;
|
||||
const navigate = useNavigate();
|
||||
const { incident, closeIncident, deleteIncident } = props;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const cardClass = classNames({
|
||||
card: true,
|
||||
"mb-3": true,
|
||||
"has-background-primary-light": incident.closedAt,
|
||||
});
|
||||
return (
|
||||
<div className={cardClass}>
|
||||
<div className="card-content">
|
||||
<div className="content has-text-small">
|
||||
<h4 className="title is-5">{incident.name}</h4>
|
||||
<div className="columns">
|
||||
<div className="column is-one-third">
|
||||
<strong>{t("location")}: </strong>
|
||||
{incident.location.name}
|
||||
</div>
|
||||
<div className="column is-one-third">
|
||||
<strong>{t("createdAt")}: </strong>
|
||||
{dayjs(incident.createdAt).format("LLL")}
|
||||
</div>
|
||||
{incident.closedAt && (
|
||||
<div className="column">
|
||||
<strong>{t("closedAt")}: </strong>
|
||||
{dayjs(incident.closedAt).format("LLL")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="enter-button"
|
||||
className="card-footer-item is-ahref is-capitalized"
|
||||
onClick={() => navigate(`../${props.incident.id}/journal/view`)}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faArrowRightFromBracket} />
|
||||
</span>
|
||||
<span>{t("enter")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="edit-button"
|
||||
className="card-footer-item is-ahref is-capitalized"
|
||||
onClick={() => navigate(`../${incident.id}/edit`)}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</span>
|
||||
<span>{t("edit")}</span>
|
||||
</button>
|
||||
{incident.closedAt === null ? (
|
||||
const cardClass = classNames({
|
||||
card: true,
|
||||
"mb-3": true,
|
||||
"has-background-primary-light": incident.closedAt,
|
||||
});
|
||||
return (
|
||||
<div className={cardClass}>
|
||||
<div className="card-content">
|
||||
<div className="content has-text-small">
|
||||
<h4 className="title is-5">{incident.name}</h4>
|
||||
<div className="columns">
|
||||
<div className="column is-one-third">
|
||||
<strong>{t("location")}: </strong>
|
||||
{incident.location.name}
|
||||
</div>
|
||||
<div className="column is-one-third">
|
||||
<strong>{t("createdAt")}: </strong>
|
||||
{dayjs(incident.createdAt).format("LLL")}
|
||||
</div>
|
||||
{incident.closedAt && (
|
||||
<div className="column">
|
||||
<strong>{t("closedAt")}: </strong>
|
||||
{dayjs(incident.closedAt).format("LLL")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer className="card-footer">
|
||||
<button
|
||||
type="button"
|
||||
data-testid="enter-button"
|
||||
className="card-footer-item is-ahref is-capitalized"
|
||||
onClick={() => navigate(`../${props.incident.id}/journal/view`)}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faArrowRightFromBracket} />
|
||||
</span>
|
||||
<span>{t("enter")}</span>
|
||||
</button>
|
||||
{incident.closedAt === null ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="close-button"
|
||||
className="card-footer-item is-ahref is-capitalized is-danger"
|
||||
onClick={() => {
|
||||
closeIncident({
|
||||
variables: {
|
||||
incidentId: incident.id,
|
||||
closedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}}
|
||||
data-testid="edit-button"
|
||||
className="card-footer-item is-ahref is-capitalized"
|
||||
onClick={() => navigate(`../${incident.id}/edit`)}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faFolderClosed} />
|
||||
<FontAwesomeIcon icon={faEdit} />
|
||||
</span>
|
||||
<span>{t("close")}</span>
|
||||
<span>{t("edit")}</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="card-footer-item is-ahref is-capitalized is-success"
|
||||
data-testid="open-button"
|
||||
onClick={() => {
|
||||
closeIncident({
|
||||
variables: { incidentId: incident.id, closedAt: null },
|
||||
});
|
||||
}}
|
||||
data-testid="delete-button"
|
||||
className="card-footer-item is-ahref is-capitalized"
|
||||
onClick={() => {
|
||||
deleteIncident({
|
||||
variables: {
|
||||
incidentId: incident.id,
|
||||
deletedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faFolderOpen} />
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</span>
|
||||
<span>{t("open")}</span>
|
||||
<span>{t("delete")}</span>
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
{incident.closedAt === null ? (
|
||||
<button
|
||||
type="button"
|
||||
data-testid="close-button"
|
||||
className="card-footer-item is-ahref is-capitalized is-danger"
|
||||
onClick={() => {
|
||||
closeIncident({
|
||||
variables: {
|
||||
incidentId: incident.id,
|
||||
closedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faFolderClosed} />
|
||||
</span>
|
||||
<span>{t("close")}</span>
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="card-footer-item is-ahref is-capitalized is-success"
|
||||
data-testid="open-button"
|
||||
onClick={() => {
|
||||
closeIncident({
|
||||
variables: { incidentId: incident.id, closedAt: null },
|
||||
});
|
||||
}}
|
||||
>
|
||||
<span className="icon">
|
||||
<FontAwesomeIcon icon={faFolderOpen} />
|
||||
</span>
|
||||
<span>{t("open")}</span>
|
||||
</button>
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default List;
|
||||
|
||||
@@ -2,6 +2,8 @@ import { gql, type TypedDocumentNode } from "@apollo/client";
|
||||
import type {
|
||||
CloseIncidentMutation,
|
||||
CloseIncidentMutationVariables,
|
||||
DeleteIncidentMutation,
|
||||
DeleteIncidentMutationVariables,
|
||||
IncidentDetailsData,
|
||||
IncidentDetailsVars,
|
||||
IncidentListData,
|
||||
@@ -16,7 +18,7 @@ const GET_INCIDENTS: TypedDocumentNode<
|
||||
Record<string, never>
|
||||
> = gql`
|
||||
query FetchIncidents {
|
||||
incidents(orderBy: { createdAt: DESC }) {
|
||||
incidents(orderBy: {createdAt: DESC}, where: {deletedAt: {_isNull: true}}) {
|
||||
id
|
||||
name
|
||||
createdAt
|
||||
@@ -137,11 +139,48 @@ const CLOSE_INCIDENT: TypedDocumentNode<
|
||||
CloseIncidentMutationVariables
|
||||
> = gql`
|
||||
mutation CloseIncident($incidentId: uuid, $closedAt: timestamptz) {
|
||||
updateJournals(where: { incidentId: { _eq: $incidentId }, closedAt: { _isNull: true } }, _set: { closedAt: $closedAt }) {
|
||||
affectedRows
|
||||
returning {
|
||||
id
|
||||
closedAt
|
||||
}
|
||||
}
|
||||
updateIncidents(where: { id: { _eq: $incidentId } }, _set: { closedAt: $closedAt }) {
|
||||
affectedRows
|
||||
returning {
|
||||
id
|
||||
closedAt
|
||||
journals {
|
||||
id
|
||||
closedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const DELETE_INCIDENT: TypedDocumentNode<
|
||||
DeleteIncidentMutation,
|
||||
DeleteIncidentMutationVariables
|
||||
> = gql`
|
||||
mutation DeleteIncident($incidentId: uuid, $deletedAt: timestamptz) {
|
||||
updateJournals(where: { incidentId: { _eq: $incidentId }, deletedAt: { _isNull: true } }, _set: { deletedAt: $deletedAt }) {
|
||||
affectedRows
|
||||
returning {
|
||||
id
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
updateIncidents(where: { id: { _eq: $incidentId }, deletedAt: {_isNull: true}, closedAt: {_isNull: false} }, _set: { deletedAt: $deletedAt }) {
|
||||
affectedRows
|
||||
returning {
|
||||
id
|
||||
deletedAt
|
||||
journals {
|
||||
id
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,4 +192,5 @@ export {
|
||||
GET_INCIDENTS as GetIncidents,
|
||||
INSERT_INCIDENT as InsertIncident,
|
||||
UPDATE_INCIDENT as UpdateIncident,
|
||||
DELETE_INCIDENT as DeleteIncident,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user