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

22 design data export interface format selection filtering #220

Merged
6 changes: 6 additions & 0 deletions .changeset/fifty-seas-thank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": patch
"@quassel/ui": patch
---

Add export ui
6 changes: 6 additions & 0 deletions .changeset/many-snails-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@quassel/frontend": patch
"@quassel/ui": patch
---

Add content shell
29 changes: 29 additions & 0 deletions apps/frontend/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { isMatch, Link, useLocation, useMatches } from "@tanstack/react-router";
import { Anchor, Breadcrumbs } from "@quassel/ui";

// Inspired by https://github.com/TanStack/router/blob/main/examples/react/kitchen-sink-file-based/src/components/Breadcrumbs.tsx
export function BreadcrumbsNavigation() {
const matches = useMatches();
const location = useLocation();

Check warning on line 7 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L5-L7

Added lines #L5 - L7 were not covered by tests

if (matches.some((match) => match.status === "pending")) return null;

Check warning on line 9 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L9

Added line #L9 was not covered by tests

const matchesWithTitle = matches.filter((match) => isMatch(match, "context.title"));
const entries = matchesWithTitle.map((match) => ({ label: match.context.title, to: match.fullPath }));
const uniqueEntries = entries.filter((entry, index) => entries.findIndex((e) => e.label === entry.label) === index);

Check warning on line 13 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L11-L13

Added lines #L11 - L13 were not covered by tests

// Remove the last entry if it's the current page
if (uniqueEntries.length > 0 && uniqueEntries[uniqueEntries.length - 1].to.startsWith(location.pathname)) {
uniqueEntries.pop();
}

Check warning on line 18 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L16-L18

Added lines #L16 - L18 were not covered by tests

return (
<Breadcrumbs>
{uniqueEntries.map((e) => (
<Anchor key={e.label} renderRoot={(props) => <Link to={e.to} {...props} />}>
{e.label}
</Anchor>
))}
</Breadcrumbs>

Check warning on line 27 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L20-L27

Added lines #L20 - L27 were not covered by tests
);
}

Check warning on line 29 in apps/frontend/src/components/Breadcrumbs.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/Breadcrumbs.tsx#L29

Added line #L29 was not covered by tests
12 changes: 9 additions & 3 deletions apps/frontend/src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,18 @@
Divider,
FooterLogos,
} from "@quassel/ui";
import { createRootRouteWithContext, Link, Outlet, useNavigate } from "@tanstack/react-router";
import { createRootRouteWithContext, Link, Outlet, RouteContext, useNavigate } from "@tanstack/react-router";
import { version } from "../../package.json";
import { $session } from "../stores/session";
import { useStore } from "@nanostores/react";
import { $layout } from "../stores/layout";
import { $api } from "../stores/api";
import { DefaultError, QueryClient, useQueryClient } from "@tanstack/react-query";
import { DefaultError, useQueryClient } from "@tanstack/react-query";
import { i18n } from "../stores/i18n";

const messages = i18n("RootRoute", {
title: "Home",
});

Check warning on line 31 in apps/frontend/src/routes/__root.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/__root.tsx#L29-L31

Added lines #L29 - L31 were not covered by tests

function Root() {
const n = useNavigate();
Expand Down Expand Up @@ -106,6 +111,7 @@
);
}

export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
export const Route = createRootRouteWithContext<RouteContext>()({
beforeLoad: () => ({ title: messages.get().title }),

Check warning on line 115 in apps/frontend/src/routes/__root.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/__root.tsx#L114-L115

Added lines #L114 - L115 were not covered by tests
component: Root,
});
18 changes: 15 additions & 3 deletions apps/frontend/src/routes/_auth/administration.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { createFileRoute, Outlet, useMatches } from "@tanstack/react-router";
import { useEffect } from "react";
import { $layout } from "../../stores/layout";
import { i18n } from "../../stores/i18n";
import { ContentShell } from "@quassel/ui";
import { BreadcrumbsNavigation } from "../../components/Breadcrumbs";

const messages = i18n("AdministrationRoute", {
title: "Administration",
});

Check warning on line 10 in apps/frontend/src/routes/_auth/administration.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration.tsx#L8-L10

Added lines #L8 - L10 were not covered by tests

function AdministrationLayout() {
useEffect(() => {
$layout.set({ admin: true });
return () => $layout.set({ admin: false });
}, []);

const matches = useMatches();
const title = matches[matches.length - 2]?.context.title;
const actions = matches[matches.length - 1]?.context.actions;

Check warning on line 20 in apps/frontend/src/routes/_auth/administration.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration.tsx#L18-L20

Added lines #L18 - L20 were not covered by tests

return (
<>
<ContentShell title={title || messages.get().title} breadcrumbs={<BreadcrumbsNavigation />} actions={actions}>

Check warning on line 23 in apps/frontend/src/routes/_auth/administration.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration.tsx#L23

Added line #L23 was not covered by tests
<Outlet />
</>
</ContentShell>

Check warning on line 25 in apps/frontend/src/routes/_auth/administration.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration.tsx#L25

Added line #L25 was not covered by tests
);
}

export const Route = createFileRoute("/_auth/administration")({
beforeLoad: () => ({ title: messages.get().title }),

Check warning on line 30 in apps/frontend/src/routes/_auth/administration.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration.tsx#L30

Added line #L30 was not covered by tests
component: AdministrationLayout,
});
18 changes: 6 additions & 12 deletions apps/frontend/src/routes/_auth/administration/carers.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { Paper, Title } from "@quassel/ui";
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { i18n } from "../../../stores/i18n";

function AdministrationCarers() {
return (
<>
<Title>Carers</Title>
<Paper my="lg">
<Outlet />
</Paper>
</>
);
}
const messages = i18n("AdministrationCarersRoute", {
title: "Carers",
});

Check warning on line 6 in apps/frontend/src/routes/_auth/administration/carers.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers.tsx#L4-L6

Added lines #L4 - L6 were not covered by tests

export const Route = createFileRoute("/_auth/administration/carers")({
component: AdministrationCarers,
beforeLoad: () => ({ title: messages.get().title }),
component: Outlet,

Check warning on line 10 in apps/frontend/src/routes/_auth/administration/carers.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers.tsx#L9-L10

Added lines #L9 - L10 were not covered by tests
});
80 changes: 41 additions & 39 deletions apps/frontend/src/routes/_auth/administration/carers/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,52 @@
});

return (
<>
<Button variant="default" renderRoot={(props) => <Link to="/administration/carers/new" {...props} />}>
New carer
</Button>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Id</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Color</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{carers.data?.map((c) => (
<Table.Tr key={c.id}>
<Table.Td>{c.id}</Table.Td>
<Table.Td>{c.name}</Table.Td>
<Table.Td>{c.color && <ColorSwatch color={c.color} />}</Table.Td>
<Table.Td>
<Button variant="default" renderRoot={(props) => <Link to={`/administration/carers/edit/${c.id}`} {...props} />}>
Edit
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Id</Table.Th>
<Table.Th>Name</Table.Th>
<Table.Th>Color</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{carers.data?.map((c) => (
<Table.Tr key={c.id}>
<Table.Td>{c.id}</Table.Td>
<Table.Td>{c.name}</Table.Td>
<Table.Td>{c.color && <ColorSwatch color={c.color} />}</Table.Td>
<Table.Td>
<Button variant="default" renderRoot={(props) => <Link to={`/administration/carers/edit/${c.id}`} {...props} />}>

Check warning on line 30 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L15-L30

Added lines #L15 - L30 were not covered by tests
Edit
</Button>
{sessionStore.role === "ADMIN" && (
<Button
variant="default"
onClick={() =>
deleteCarerMutation.mutate({
params: { path: { id: c.id.toString() } },
})

Check warning on line 39 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L32-L39

Added lines #L32 - L39 were not covered by tests
}
>

Check warning on line 41 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L41

Added line #L41 was not covered by tests
Delete
</Button>
{sessionStore.role === "ADMIN" && (
<Button
variant="default"
onClick={() =>
deleteCarerMutation.mutate({
params: { path: { id: c.id.toString() } },
})
}
>
Delete
</Button>
)}
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</>
)}
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>

Check warning on line 49 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L45-L49

Added lines #L45 - L49 were not covered by tests
);
}

export const Route = createFileRoute("/_auth/administration/carers/")({
beforeLoad: () => ({
actions: [
<Button key="new-carer" variant="default" renderRoot={(props) => <Link to="/administration/carers/new" {...props} />}>

Check warning on line 56 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L54-L56

Added lines #L54 - L56 were not covered by tests
New carer
</Button>,
],
}),

Check warning on line 60 in apps/frontend/src/routes/_auth/administration/carers/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/carers/index.tsx#L58-L60

Added lines #L58 - L60 were not covered by tests
loader: ({ context: { queryClient } }) => queryClient.ensureQueryData($api.queryOptions("get", "/carers")),
component: () => <AdministrationCarersIndex />,
});
18 changes: 6 additions & 12 deletions apps/frontend/src/routes/_auth/administration/export.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { Paper, Title } from "@quassel/ui";
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { i18n } from "../../../stores/i18n";

function AdministrationExport() {
return (
<>
<Title>Export</Title>
<Paper my="lg">
<Outlet />
</Paper>
</>
);
}
const messages = i18n("AdministrationExportRoute", {
title: "Export",
});

Check warning on line 6 in apps/frontend/src/routes/_auth/administration/export.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export.tsx#L4-L6

Added lines #L4 - L6 were not covered by tests

export const Route = createFileRoute("/_auth/administration/export")({
component: AdministrationExport,
beforeLoad: () => ({ title: messages.get().title }),
component: Outlet,

Check warning on line 10 in apps/frontend/src/routes/_auth/administration/export.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export.tsx#L9-L10

Added lines #L9 - L10 were not covered by tests
});
55 changes: 49 additions & 6 deletions apps/frontend/src/routes/_auth/administration/export/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,61 @@
import { Button } from "@quassel/ui";
import { Button, Group, Radio, Select, Stack, useForm } from "@quassel/ui";
import { createFileRoute } from "@tanstack/react-router";
import { $api } from "../../../../stores/api";
import { i18n } from "../../../../stores/i18n";
import { useStore } from "@nanostores/react";
import { useSuspenseQuery } from "@tanstack/react-query";

const messages = i18n("AdministrationExportIndexRoute", {
title: "Carers",
studyLabel: "Study",
studyPlaceholder: "Select a study",
formatLabel: "File format",
csvLabel: "Comma-separated values (CSV)",
sqlLabel: "Database export (SQL)",
formAction: "Download",
});

Check warning on line 16 in apps/frontend/src/routes/_auth/administration/export/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export/index.tsx#L8-L16

Added lines #L8 - L16 were not covered by tests

type FormValues = {
fileType: "csv" | "sql";
studyId?: string;
};

function AdministrationExportIndex() {
const t = useStore(messages);
const f = useForm<FormValues>({
mode: "uncontrolled",
initialValues: {
fileType: "csv",
},
});

Check warning on line 30 in apps/frontend/src/routes/_auth/administration/export/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export/index.tsx#L24-L30

Added lines #L24 - L30 were not covered by tests

const studies = useSuspenseQuery($api.queryOptions("get", "/studies"));

Check warning on line 32 in apps/frontend/src/routes/_auth/administration/export/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export/index.tsx#L32

Added line #L32 was not covered by tests
const { isDownloading, downloadFile } = $api.useDownload("/export", "dump.sql");
return (
<div>
<Button loading={isDownloading} onClick={() => downloadFile()}>
Download
</Button>
</div>
<form onSubmit={f.onSubmit(() => downloadFile())}>
<Stack>
<Select
label={t.studyLabel}
placeholder={t.studyPlaceholder}
data={studies.data.map((s) => ({ label: s.title, value: s.id.toString() }))}
/>
<Radio.Group label={t.formatLabel} withAsterisk {...f.getInputProps("fileType")}>
<Group>
<Radio checked value="csv" label={t.csvLabel} />
<Radio value="sql" label={t.sqlLabel} />
</Group>
</Radio.Group>
<Button type="submit" loading={isDownloading}>
{t.formAction}
</Button>
</Stack>
</form>

Check warning on line 52 in apps/frontend/src/routes/_auth/administration/export/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export/index.tsx#L35-L52

Added lines #L35 - L52 were not covered by tests
);
}

export const Route = createFileRoute("/_auth/administration/export/")({
loader: ({ context: { queryClient } }) => {
queryClient.ensureQueryData($api.queryOptions("get", "/studies"));
},

Check warning on line 59 in apps/frontend/src/routes/_auth/administration/export/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/export/index.tsx#L57-L59

Added lines #L57 - L59 were not covered by tests
component: AdministrationExportIndex,
});
13 changes: 9 additions & 4 deletions apps/frontend/src/routes/_auth/administration/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Title } from "@quassel/ui";
import { createFileRoute } from "@tanstack/react-router";
import { i18n } from "../../../stores/i18n";

export const Route = createFileRoute("/_auth/administration/")({
component: Index,
const messages = i18n("AdministrationDashboardRoute", {
title: "Dashboard",

Check warning on line 5 in apps/frontend/src/routes/_auth/administration/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/index.tsx#L4-L5

Added lines #L4 - L5 were not covered by tests
});

function Index() {
return <Title>Welcome to the administration interface!</Title>;
return null;

Check warning on line 9 in apps/frontend/src/routes/_auth/administration/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/index.tsx#L9

Added line #L9 was not covered by tests
}

export const Route = createFileRoute("/_auth/administration/")({
beforeLoad: () => ({ title: messages.get().title }),
component: Index,
});

Check warning on line 15 in apps/frontend/src/routes/_auth/administration/index.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/index.tsx#L12-L15

Added lines #L12 - L15 were not covered by tests
18 changes: 6 additions & 12 deletions apps/frontend/src/routes/_auth/administration/languages.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { Title, Paper } from "@quassel/ui";
import { createFileRoute, Outlet } from "@tanstack/react-router";
import { i18n } from "../../../stores/i18n";

function AdministrationLanguages() {
return (
<>
<Title>Languages</Title>
<Paper my="lg">
<Outlet />
</Paper>
</>
);
}
const messages = i18n("AdministrationLanguagesRoute", {
title: "Languages",
});

Check warning on line 6 in apps/frontend/src/routes/_auth/administration/languages.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/languages.tsx#L4-L6

Added lines #L4 - L6 were not covered by tests

export const Route = createFileRoute("/_auth/administration/languages")({
component: AdministrationLanguages,
beforeLoad: () => ({ title: messages.get().title }),
component: Outlet,

Check warning on line 10 in apps/frontend/src/routes/_auth/administration/languages.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/languages.tsx#L9-L10

Added lines #L9 - L10 were not covered by tests
});
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,14 @@
}, [isSuccess, data]);

return (
<>
<form autoComplete="off" onSubmit={f.onSubmit(handleSubmit)}>
<TextInput label="Name" type="name" {...f.getInputProps("name")} />
<TextInput label="IETF BCP 47" type="text" {...f.getInputProps("ietfBcp47")} />
<form autoComplete="off" onSubmit={f.onSubmit(handleSubmit)}>
<TextInput label="Name" type="name" {...f.getInputProps("name")} />
<TextInput label="IETF BCP 47" type="text" {...f.getInputProps("ietfBcp47")} />

Check warning on line 49 in apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx#L47-L49

Added lines #L47 - L49 were not covered by tests

<Button type="submit" fullWidth mt="xl" loading={editCarerMutation.isPending}>
Change
</Button>
</form>
</>
<Button type="submit" fullWidth mt="xl" loading={editCarerMutation.isPending}>

Check warning on line 51 in apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx#L51

Added line #L51 was not covered by tests
Change
</Button>
</form>

Check warning on line 54 in apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/routes/_auth/administration/languages/edit.$id.tsx#L53-L54

Added lines #L53 - L54 were not covered by tests
);
}

Expand Down
Loading
Loading