Skip to content

Commit fd9e2bd

Browse files
authored
Merge pull request #381 from openscript-ch/334-data-entry-in-calendar-view-clear-all-function
334 data entry in calendar view clear all function
2 parents 2af058e + b199ecb commit fd9e2bd

File tree

9 files changed

+117
-37
lines changed

9 files changed

+117
-37
lines changed

.changeset/shy-hotels-tie.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@quassel/frontend": minor
3+
"@quassel/backend": minor
4+
"@quassel/ui": minor
5+
---
6+
7+
Allow clearing all entries from a questionnaire

apps/backend/src/research/entries/entries.controller.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Body, Controller, Delete, Get, Param, Patch, Post } from "@nestjs/common";
2-
import { ApiTags, ApiOperation, ApiUnprocessableEntityResponse } from "@nestjs/swagger";
1+
import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from "@nestjs/common";
2+
import { ApiTags, ApiOperation, ApiUnprocessableEntityResponse, ApiQuery } from "@nestjs/swagger";
33
import { ErrorResponseDto } from "../../common/dto/error.dto";
44
import { EntriesService } from "./entries.service";
55
import { EntryCreationDto, EntryResponseDto, EntryMutationDto } from "./entry.dto";
@@ -44,4 +44,11 @@ export class EntriesController {
4444
delete(@Param("id") id: string) {
4545
return this.entriesService.remove(+id);
4646
}
47+
48+
@Delete()
49+
@ApiQuery({ name: "questionnaireId", required: true, type: Number })
50+
@ApiOperation({ summary: "Delete all entries of a questionnaire" })
51+
deleteAll(@Query("questionnaireId") questionnaireId: number) {
52+
return this.entriesService.removeAllFromQuestionnaire(questionnaireId);
53+
}
4754
}

apps/backend/src/research/entries/entries.service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,8 @@ export class EntriesService {
7979
remove(id: number) {
8080
return this.em.remove(this.entryRepository.getReference(id)).flush();
8181
}
82+
83+
removeAllFromQuestionnaire(questionnaireId: number) {
84+
return this.entryRepository.nativeDelete({ questionnaire: questionnaireId });
85+
}
8286
}

apps/frontend/src/api.gen.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,8 @@ export interface paths {
268268
put?: never;
269269
/** Create a entry */
270270
post: operations["EntriesController_create"];
271-
delete?: never;
271+
/** Delete all entries of a questionnaire */
272+
delete: operations["EntriesController_deleteAll"];
272273
options?: never;
273274
head?: never;
274275
patch?: never;
@@ -1842,6 +1843,27 @@ export interface operations {
18421843
};
18431844
};
18441845
};
1846+
EntriesController_deleteAll: {
1847+
parameters: {
1848+
query: {
1849+
questionnaireId: number;
1850+
};
1851+
header?: never;
1852+
path?: never;
1853+
cookie?: never;
1854+
};
1855+
requestBody?: never;
1856+
responses: {
1857+
200: {
1858+
headers: {
1859+
[name: string]: unknown;
1860+
};
1861+
content: {
1862+
"application/json": number;
1863+
};
1864+
};
1865+
};
1866+
};
18451867
EntriesController_get: {
18461868
parameters: {
18471869
query?: never;

apps/frontend/src/routes/_auth/questionnaire/_questionnaire/$id/entries.tsx

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Button, Group, Modal, Stack, Title, useDisclosure, useForm } from "@quassel/ui";
1+
import { Button, Group, IconClearAll, modals, Stack, Title, useForm, Text } from "@quassel/ui";
22
import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
33
import { i18n } from "../../../../../stores/i18n";
44
import { useStore } from "@nanostores/react";
@@ -14,9 +14,14 @@ const messages = i18n("questionnaireEntries", {
1414
addEntityLabel: "Add",
1515
notificationSuccessCreateLanguage: "Successfully add a new language.",
1616
notificationSuccessCreateCarer: "Successfully add a new carer.",
17-
gapsDialogTitle: "Gaps detected in the calendar",
17+
gapsDialogTitle: "Continue with gaps?",
18+
gapsDialogDescription: "There were gaps detected in the calendar. Do you want to continue anyway or highlight the gaps?",
1819
gapsDialogContinueAnyway: "Continue anyway",
1920
gapsDialogHighlightGaps: "Highlight gaps",
21+
confirmClearDialogTitle: "Clear all entries from this questionnaire?",
22+
confirmClearDialogDescription: "When confirming, all entries from this questionnaires will be removed. This action can't be undone.",
23+
confirmClearDialogCancel: "Cancel",
24+
confirmClearDialogConfirm: "Clear all",
2025
});
2126

2227
export function Entries() {
@@ -25,11 +30,12 @@ export function Entries() {
2530

2631
const t = useStore(messages);
2732

28-
const { data: questionnaire } = $api.useSuspenseQuery("get", "/questionnaires/{id}", { params: { path: p } });
33+
const { data: questionnaire, refetch } = $api.useSuspenseQuery("get", "/questionnaires/{id}", { params: { path: p } });
34+
35+
const removeAllEntriesMutation = $api.useMutation("delete", "/entries", { onSuccess: () => refetch() });
2936

3037
const [gaps, setGaps] = useState<GapsPerDay>();
3138
const [highlightGaps, setHighlightGaps] = useState(false);
32-
const [gapsDialogOpened, { open, close }] = useDisclosure();
3339

3440
const f = useForm<{ entries: components["schemas"]["EntryResponseDto"][] }>({
3541
initialValues: {
@@ -50,43 +56,52 @@ export function Entries() {
5056
n({ to: "/questionnaire/$id/remarks", params: p });
5157
};
5258

59+
const handleGapValidation = () => {
60+
modals.openConfirmModal({
61+
title: t.gapsDialogTitle,
62+
children: <Text size="sm">{t.gapsDialogDescription}</Text>,
63+
labels: { cancel: t.gapsDialogHighlightGaps, confirm: t.gapsDialogContinueAnyway },
64+
confirmProps: { variant: "light" },
65+
cancelProps: { variant: "filled" },
66+
onConfirm: handleSubmit,
67+
onCancel: () => setHighlightGaps(true),
68+
});
69+
};
70+
71+
const handleClearEntries = () => {
72+
modals.openConfirmModal({
73+
title: t.confirmClearDialogTitle,
74+
children: <Text size="sm">{t.confirmClearDialogDescription}</Text>,
75+
labels: { cancel: t.confirmClearDialogCancel, confirm: t.confirmClearDialogConfirm },
76+
onConfirm: () => removeAllEntriesMutation.mutate({ params: { query: { questionnaireId: questionnaire.id } } }),
77+
});
78+
};
79+
5380
useEffect(() => {
5481
f.setValues({ entries: questionnaire.entries });
5582

5683
if (highlightGaps) f.validate();
5784
}, [questionnaire]);
5885

5986
return (
60-
<>
61-
<Modal opened={gapsDialogOpened} onClose={close} centered title={t.gapsDialogTitle}>
62-
<Group justify="flex-end">
63-
<Button onClick={handleSubmit} variant="light" type="submit">
64-
{t.gapsDialogContinueAnyway}
65-
</Button>
66-
<Button
67-
onClick={() => {
68-
setHighlightGaps(true);
69-
close();
70-
}}
71-
>
72-
{t.gapsDialogHighlightGaps}
87+
<form onSubmit={f.onSubmit(handleSubmit, handleGapValidation)}>
88+
<Stack>
89+
<Group justify="space-between">
90+
<Title order={3}>{questionnaire.title}</Title>
91+
<Button variant="default" onClick={handleClearEntries} rightSection={<IconClearAll />}>
92+
Clear all
7393
</Button>
7494
</Group>
75-
</Modal>
76-
<form onSubmit={f.onSubmit(handleSubmit, open)}>
77-
<Stack>
78-
<Title order={3}>{questionnaire.title}</Title>
79-
<QuestionnaireEntries gaps={highlightGaps ? gaps : undefined} questionnaire={questionnaire} />
80-
81-
<Group>
82-
<Link to="/questionnaire/$id/period" params={p}>
83-
<Button variant="light">{t.backAction}</Button>
84-
</Link>
85-
<Button type="submit">{t.formAction}</Button>
86-
</Group>
87-
</Stack>
88-
</form>
89-
</>
95+
<QuestionnaireEntries gaps={highlightGaps ? gaps : undefined} questionnaire={questionnaire} />
96+
97+
<Group>
98+
<Link to="/questionnaire/$id/period" params={p}>
99+
<Button variant="light">{t.backAction}</Button>
100+
</Link>
101+
<Button type="submit">{t.formAction}</Button>
102+
</Group>
103+
</Stack>
104+
</form>
90105
);
91106
}
92107

libs/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"@mantine/dates": "7.17.2",
3434
"@mantine/form": "7.17.2",
3535
"@mantine/hooks": "7.17.2",
36+
"@mantine/modals": "^7.17.2",
3637
"@mantine/notifications": "7.17.2",
3738
"@tabler/icons-react": "^3.31.0",
3839
"dayjs": "^1.11.13",

libs/ui/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export { useDisclosure, useFullscreen } from "@mantine/hooks";
9999

100100
export { notifications, type NotificationData } from "@mantine/notifications";
101101

102+
export { modals } from "@mantine/modals";
103+
102104
export { useForm, isInRange, isNotEmpty } from "@mantine/form";
103105

104106
export {
@@ -123,6 +125,7 @@ export {
123125
IconReportAnalytics,
124126
IconX,
125127
IconInfoCircle,
128+
IconClearAll,
126129
} from "@tabler/icons-react";
127130

128131
export { uzhColors } from "./theme/uzh";

libs/ui/src/theme/ThemeProvider.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import utc from "dayjs/plugin/utc";
66
import { DefaultMantineColor, MantineColorsTuple } from "@mantine/core";
77
import { Notifications } from "@mantine/notifications";
88
import { convertUZHColorsToMantine, UZHColor, uzhColors } from "./uzh";
9+
import { ModalsProvider } from "@mantine/modals";
910

1011
dayjs.extend(utc);
1112

@@ -37,8 +38,10 @@ export function ThemeProvider({ children, ...args }: ThemeProviderProps) {
3738
return (
3839
<DatesProvider settings={{ timezone: "UTC" }}>
3940
<MantineProvider {...args} theme={theme}>
40-
<Notifications />
41-
{children}
41+
<ModalsProvider>
42+
<Notifications />
43+
{children}
44+
</ModalsProvider>
4245
</MantineProvider>
4346
</DatesProvider>
4447
);

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)