Skip to content

Commit 5a3dc47

Browse files
committed
feat: add studies management
1 parent 0fb073f commit 5a3dc47

File tree

10 files changed

+296
-22
lines changed

10 files changed

+296
-22
lines changed

.changeset/sixty-wombats-fetch.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@quassel/frontend": patch
3+
"@quassel/backend": patch
4+
---
5+
6+
Add studies management

apps/frontend/src/routeTree.gen.ts

Lines changed: 103 additions & 7 deletions
Large diffs are not rendered by default.

apps/frontend/src/routes/__root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ function Root() {
3333
$session.set({});
3434
n({ to: "/session" });
3535
};
36-
const signOutMutation = $api.useMutation("delete", "/session", { onSettled: () => signOut() });
36+
const signOutMutation = $api.useMutation("delete", "/session", { onSettled: signOut });
3737
const handleSignOut = () => {
3838
signOutMutation.mutate({});
3939
};

apps/frontend/src/routes/_auth/administration/participants/edit.$id.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,7 @@ function AdministrationParticipantsEdit() {
2727
n({ to: "/administration/participants" });
2828
},
2929
});
30-
const f = useForm<FormValues>({
31-
initialValues: {
32-
id: 0,
33-
},
34-
});
30+
const f = useForm<FormValues>();
3531
const handleSubmit = (values: FormValues) => {
3632
editParticipantMutation.mutate({
3733
body: { ...values },

apps/frontend/src/routes/_auth/administration/participants/index.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ function AdministrationParticipantsIndex() {
2323
</Table.Tr>
2424
</Table.Thead>
2525
<Table.Tbody>
26-
{participants.data?.map((c) => (
27-
<Table.Tr key={c.id}>
28-
<Table.Td>{c.id}</Table.Td>
29-
<Table.Td>{c.birthday}</Table.Td>
26+
{participants.data?.map((p) => (
27+
<Table.Tr key={p.id}>
28+
<Table.Td>{p.id}</Table.Td>
29+
<Table.Td>{p.birthday}</Table.Td>
3030
<Table.Td>
31-
<Button variant="default" renderRoot={(props) => <Link to={`/administration/participants/edit/${c.id}`} {...props} />}>
31+
<Button variant="default" renderRoot={(props) => <Link to={`/administration/participants/edit/${p.id}`} {...props} />}>
3232
Edit
3333
</Button>
3434
<Button
3535
variant="default"
3636
onClick={() =>
3737
deleteParticipantMutation.mutate({
38-
params: { path: { id: c.id.toString() } },
38+
params: { path: { id: p.id.toString() } },
3939
})
4040
}
4141
>

apps/frontend/src/routes/_auth/administration/participants/new.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ function AdministrationParticipantsNew() {
2727
return (
2828
<>
2929
<form autoComplete="off" onSubmit={f.onSubmit(handleSubmit)}>
30-
<TextInput label="Id" type="text" {...f.getInputProps("id")} defaultValue={undefined} required />
30+
<TextInput label="Id" type="number" {...f.getInputProps("id")} defaultValue={undefined} required />
3131
<TextInput label="Birthday" type="date" {...f.getInputProps("birthday")} required />
3232

3333
<Button type="submit" fullWidth mt="xl" loading={createParticipantMutation.isPending}>
Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
import { createFileRoute } from "@tanstack/react-router";
1+
import { Title, Paper } from "@quassel/ui";
2+
import { createFileRoute, Outlet } from "@tanstack/react-router";
3+
4+
function AdministrationStudies() {
5+
return (
6+
<>
7+
<Title>Studies</Title>
8+
<Paper my="lg">
9+
<Outlet />
10+
</Paper>
11+
</>
12+
);
13+
}
214

315
export const Route = createFileRoute("/_auth/administration/studies")({
4-
component: () => <div>Hello /_auth/administration/studies!</div>,
16+
component: AdministrationStudies,
517
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { createFileRoute, useNavigate } from "@tanstack/react-router";
2+
import { components } from "../../../../api.gen";
3+
import { $api } from "../../../../stores/api";
4+
import { useForm } from "@mantine/form";
5+
import { useQueryClient, useSuspenseQuery } from "@tanstack/react-query";
6+
import { Button, TextInput } from "@quassel/ui";
7+
import { useEffect } from "react";
8+
9+
type FormValues = components["schemas"]["StudyMutationDto"];
10+
11+
function AdministrationStudiesEdit() {
12+
const p = Route.useParams();
13+
const q = useQueryClient();
14+
const study = useSuspenseQuery(
15+
$api.queryOptions("get", "/studies/{id}", {
16+
params: { path: { id: p.id } },
17+
})
18+
);
19+
const n = useNavigate();
20+
const editStudyMutation = $api.useMutation("patch", "/studies/{id}", {
21+
onSuccess: () => {
22+
q.invalidateQueries(
23+
$api.queryOptions("get", "/studies/{id}", {
24+
params: { path: { id: p.id } },
25+
})
26+
);
27+
n({ to: "/administration/studies" });
28+
},
29+
});
30+
const f = useForm<FormValues>();
31+
const handleSubmit = (values: FormValues) => {
32+
editStudyMutation.mutate({
33+
body: { ...values },
34+
params: { path: { id: p.id } },
35+
});
36+
};
37+
38+
useEffect(() => {
39+
f.setValues(study.data ?? {});
40+
f.resetDirty();
41+
}, [study.isSuccess, study.data]);
42+
43+
return (
44+
<>
45+
<form autoComplete="off" onSubmit={f.onSubmit(handleSubmit)}>
46+
<TextInput label="Id" type="number" {...f.getInputProps("id")} required />
47+
<TextInput label="Title" type="text" {...f.getInputProps("title")} required />
48+
49+
<Button type="submit" fullWidth mt="xl" loading={editStudyMutation.isPending}>
50+
Change
51+
</Button>
52+
</form>
53+
</>
54+
);
55+
}
56+
57+
export const Route = createFileRoute("/_auth/administration/studies/edit/$id")({
58+
loader: ({ params, context: { queryClient } }) =>
59+
queryClient.ensureQueryData(
60+
$api.queryOptions("get", "/studies/{id}", {
61+
params: { path: { id: params.id } },
62+
})
63+
),
64+
component: AdministrationStudiesEdit,
65+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { createFileRoute, Link } from "@tanstack/react-router";
2+
import { $api } from "../../../../stores/api";
3+
import { Button, Table } from "@quassel/ui";
4+
import { useSuspenseQuery } from "@tanstack/react-query";
5+
6+
function AdministrationStudiesIndex() {
7+
const studies = useSuspenseQuery($api.queryOptions("get", "/studies"));
8+
const deleteStudyMutation = $api.useMutation("delete", "/studies/{id}", {
9+
onSuccess: () => studies.refetch(),
10+
});
11+
12+
return (
13+
<>
14+
<Button variant="default" renderRoot={(props) => <Link to="/administration/studies/new" {...props} />}>
15+
New study
16+
</Button>
17+
<Table>
18+
<Table.Thead>
19+
<Table.Tr>
20+
<Table.Th>Id</Table.Th>
21+
<Table.Th>Title</Table.Th>
22+
</Table.Tr>
23+
</Table.Thead>
24+
<Table.Tbody>
25+
{studies.data?.map((s) => (
26+
<Table.Tr key={s.id}>
27+
<Table.Td>{s.id}</Table.Td>
28+
<Table.Td>{s.title}</Table.Td>
29+
<Table.Td>
30+
<Button variant="default" renderRoot={(props) => <Link to={`/administration/studies/edit/${s.id}`} {...props} />}>
31+
Edit
32+
</Button>
33+
<Button
34+
variant="default"
35+
onClick={() =>
36+
deleteStudyMutation.mutate({
37+
params: { path: { id: s.id.toString() } },
38+
})
39+
}
40+
>
41+
Delete
42+
</Button>
43+
</Table.Td>
44+
</Table.Tr>
45+
))}
46+
</Table.Tbody>
47+
</Table>
48+
</>
49+
);
50+
}
51+
52+
export const Route = createFileRoute("/_auth/administration/studies/")({
53+
loader: ({ context: { queryClient } }) => queryClient.ensureQueryData($api.queryOptions("get", "/studies")),
54+
component: () => <AdministrationStudiesIndex />,
55+
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useForm } from "@mantine/form";
2+
import { createFileRoute, useNavigate } from "@tanstack/react-router";
3+
import { Button, TextInput } from "@quassel/ui";
4+
import { $api } from "../../../../stores/api";
5+
import { components } from "../../../../api.gen";
6+
7+
type FormValues = components["schemas"]["StudyCreationDto"];
8+
9+
function AdministrationStudiesNew() {
10+
const n = useNavigate();
11+
const createStudyMutation = $api.useMutation("post", "/studies", {
12+
onSuccess: () => {
13+
n({ to: "/administration/studies" });
14+
},
15+
});
16+
const f = useForm<FormValues>({
17+
mode: "uncontrolled",
18+
initialValues: {
19+
id: 0,
20+
title: "",
21+
questionnaires: [],
22+
},
23+
});
24+
const handleSubmit = (values: FormValues) => {
25+
createStudyMutation.mutate({ body: values });
26+
};
27+
28+
return (
29+
<>
30+
<form autoComplete="off" onSubmit={f.onSubmit(handleSubmit)}>
31+
<TextInput label="Id" type="number" {...f.getInputProps("id")} defaultValue={undefined} required />
32+
<TextInput label="Title" type="text" {...f.getInputProps("title")} required />
33+
34+
<Button type="submit" fullWidth mt="xl" loading={createStudyMutation.isPending}>
35+
Create
36+
</Button>
37+
</form>
38+
</>
39+
);
40+
}
41+
42+
export const Route = createFileRoute("/_auth/administration/studies/new")({
43+
component: AdministrationStudiesNew,
44+
});

0 commit comments

Comments
 (0)