Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue/#94 wachtperiodes #385

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Badge, field2Title } from "@maykin-ui/admin-ui";
import React from "react";

import { ProcessingStatus } from "../../lib/api/processingStatus";
import { timeAgo } from "../../lib/format/date";
import {
PROCESSING_STATUS_ICON_MAPPING,
PROCESSING_STATUS_LEVEL_MAPPING,
Expand All @@ -10,17 +11,25 @@ import {

type ProcessingStatusBadgeProps = {
processingStatus: ProcessingStatus;
plannedDestructionDate: string | null;
};

export const ProcessingStatusBadge: React.FC<ProcessingStatusBadgeProps> = ({
processingStatus,
plannedDestructionDate,
}) => {
const getStatusText = () => {
if (processingStatus === "new" && plannedDestructionDate) {
return `Vernietigd ${timeAgo(plannedDestructionDate ?? "", { shortFormat: true })}`;
Xaohs marked this conversation as resolved.
Show resolved Hide resolved
}
return field2Title(PROCESSING_STATUS_MAPPING[processingStatus], {
unHyphen: false,
});
};
return (
<Badge level={PROCESSING_STATUS_LEVEL_MAPPING[processingStatus]}>
{PROCESSING_STATUS_ICON_MAPPING[processingStatus]}
{field2Title(PROCESSING_STATUS_MAPPING[processingStatus], {
unHyphen: false,
})}
{getStatusText()}
</Badge>
);
};
1 change: 1 addition & 0 deletions frontend/src/fixtures/destructionList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const FIXTURE_DESTRUCTION_LIST: DestructionList = {
containsSensitiveInfo: false,
status: "changes_requested",
processingStatus: "new",
plannedDestructionDate: null,
assignees: defaultAssignees,
assignee: defaultAssignees[0].user,
created: "2024-07-11T16:57",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/fixtures/destructionListItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,23 @@ export const FIXTURE_DESTRUCTION_LIST_ITEM: DestructionListItem = {
extraZaakData: null,
zaak: zaakFactory(),
processingStatus: "new",
plannedDestructionDate: null,
};
export const FIXTURE_DESTRUCTION_LIST_ITEM_DELETED: DestructionListItem = {
pk: 2,
status: "suggested",
extraZaakData: null,
zaak: null,
processingStatus: "succeeded",
plannedDestructionDate: null,
Xaohs marked this conversation as resolved.
Show resolved Hide resolved
};
export const FIXTURE_DESTRUCTION_LIST_ITEM_FAILED: DestructionListItem = {
pk: 3,
status: "suggested",
extraZaakData: null,
zaak: zaakFactory(),
processingStatus: "failed",
plannedDestructionDate: null,
Xaohs marked this conversation as resolved.
Show resolved Hide resolved
};

export const destructionListItemFactory = createObjectFactory(
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/lib/api/destructionLists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type DestructionList = {
assignees: DestructionListAssignee[];
author: User;
containsSensitiveInfo: boolean;
plannedDestructionDate: string | null;
created: string;
name: string;
status: DestructionListStatus;
Expand Down Expand Up @@ -220,3 +221,19 @@ export async function reassignDestructionList(
) {
return request("POST", `/destruction-lists/${uuid}/reassign/`, {}, data);
}

/**
* Abort the destruction of a destruction list.
* @param uuid
*/
export async function abortPlannedDestruction(
uuid: string,
data: { comment: string },
) {
return request(
"POST",
`/destruction-lists/${uuid}/abort_destruction/`,
{},
data,
);
}
1 change: 1 addition & 0 deletions frontend/src/lib/api/destructionListsItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type DestructionListItem = {
extraZaakData?: Record<string, unknown> | null;
zaak: Zaak | null;
processingStatus: ProcessingStatus;
plannedDestructionDate: string | null;
};

export interface ZaakItem extends Zaak {
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/lib/auth/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ export function canUpdateDestructionList(
return false;
}

if (
destructionList.status === "ready_to_delete" &&
destructionList.plannedDestructionDate &&
destructionList.processingStatus === "new"
) {
return false;
}

if (!STATUSES_ELIGIBLE_FOR_EDIT.includes(destructionList.status)) {
return false;
}
Expand Down
6 changes: 0 additions & 6 deletions frontend/src/lib/format/date.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,6 @@ describe("timeAgo()", () => {
expect(timeAgo(secondsAgo, { shortFormat: true })).toBe("0m");
});

test("timeAgo() interprets future data as now ", () => {
const yearFromNow = new Date("2024-09-15:00:00");
expect(timeAgo(yearFromNow)).toBe("Nu");
expect(timeAgo(yearFromNow, { shortFormat: true })).toBe("0m");
});

Xaohs marked this conversation as resolved.
Show resolved Hide resolved
test("timeAgo() handles combined scenario ", () => {
const yearAgo = new Date("2022-08-14:23:59:59");
expect(timeAgo(yearAgo)).toBe("1 jaar geleden");
Expand Down
38 changes: 25 additions & 13 deletions frontend/src/lib/format/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ interface TimeAgoOptions {
}

/**
* Calculate how long ago a given date was and return a human-readable string.
* Calculate how long ago or how long until a given date and return a human-readable string in Dutch.
* The date can be provided as a Date object or an ISO 8601 string.
* TODO: Consider using a specialized library.
*
* @param dateInput - The date to calculate the time difference from. It can be a Date object or an ISO 8601 string.
Expand All @@ -34,51 +35,62 @@ export function timeAgo(
dateInput: Date | string,
options: TimeAgoOptions = {},
): string {
// Convert the input to a Date object if it's a string
const date = typeof dateInput === "string" ? new Date(dateInput) : dateInput;

// Check for invalid date input
if (isNaN(date.getTime())) {
throw new Error("Invalid date input");
throw new Error("Ongeldige datum input");
}

const now = new Date();
// Calculate the difference in seconds between the current date and the input date
let seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const { shortFormat = false } = options;

// Define the intervals in seconds for various time units
Xaohs marked this conversation as resolved.
Show resolved Hide resolved
const intervals = [
{ label: "jaar", plural: "jaren", shortFormat: "j", seconds: 31536000 },
{ label: "maand", plural: "maanden", shortFormat: "ma", seconds: 2592000 },
{ label: "week", plural: "weken", shortFormat: "w", seconds: 604800 },
{ label: "dag", plural: "dagen", shortFormat: "d", seconds: 86400 },
{ label: "uur", plural: "uur", shortFormat: "u", seconds: 3600 },
{ label: "minuut", plural: "minuten", shortFormat: "m", seconds: 60 },
{ label: "seconde", plural: "seconden", shortFormat: "s", seconds: 1 },
];

let result = "";
let isFuture = false;

if (seconds < 0) {
isFuture = true;
seconds = Math.abs(seconds);
}

// Special case for "Nu" or "zo meteen"
if (seconds < 60) {
return shortFormat ? "0m" : isFuture ? "zo meteen" : "Nu";
}

// Iterate over the intervals to determine the appropriate time unit
for (const interval of intervals) {
const intervalCount = Math.floor(seconds / interval.seconds);
if (intervalCount >= 1) {
// Format the label based on short or long format
const label = shortFormat
? interval.shortFormat
: intervalCount === 1
? interval.label
: interval.plural;

result += `${intervalCount}${shortFormat ? "" : " "}${label}${shortFormat ? "" : " geleden"}`;
// Update seconds to the remainder for the next interval
seconds %= interval.seconds;
if (isFuture) {
result = `over ${intervalCount}${shortFormat ? "" : " "}${label}`;
} else {
if (shortFormat) {
result = `${intervalCount}${shortFormat ? "" : " "}${label}`;
} else {
result = `${intervalCount}${shortFormat ? "" : " "}${label} geleden`;
}
}
break;
}
}

// Return the result or default to "just now" or "0m" for short format
return result.trim() || (shortFormat ? "0m" : "Nu");
return result.trim();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { JsonValue, TypedAction } from "../../../hooks";
import { User } from "../../../lib/api/auth";
import {
DestructionListItemUpdate,
abortPlannedDestruction,
destroyDestructionList,
markDestructionListAsFinal,
markDestructionListAsReadyToReview,
Expand All @@ -19,6 +20,7 @@ import { clearZaakSelection } from "../../../lib/zaakSelection/zaakSelection";

export type UpdateDestructionListAction<P = JsonValue> = TypedAction<
| "DESTROY"
| "CANCEL_DESTROY"
| "MAKE_FINAL"
| "PROCESS_REVIEW"
| "READY_TO_REVIEW"
Expand Down Expand Up @@ -53,6 +55,8 @@ export async function destructionListUpdateAction({
return await destructionListUpdateReviewerAction({ request, params });
case "UPDATE_ZAKEN":
return await destructionListUpdateZakenAction({ request, params });
case "CANCEL_DESTROY":
return await destructionListCancelDestroyAction({ request, params });
default:
throw new Error("INVALID ACTION TYPE SPECIFIED!");
}
Expand Down Expand Up @@ -181,3 +185,16 @@ export async function destructionListUpdateZakenAction({

return redirect(`/destruction-lists/${params.uuid}/`);
}

export async function destructionListCancelDestroyAction({
request,
}: ActionFunctionArgs) {
const data = await request.json();
const { payload } = data as UpdateDestructionListAction<{
uuid: string;
comment: string;
}>;
const { comment, uuid } = payload;
await abortPlannedDestruction(uuid, { comment });
return redirect(`/destruction-lists/${uuid}/`);
}
Loading
Loading