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

Feature/protocols view #26

Merged
merged 15 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from 14 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
55 changes: 55 additions & 0 deletions app/(dashboard)/dashboard/_components/ActiveProtocolSwitch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use client';

import { useRouter } from 'next/navigation';
import { trpc } from '~/app/_trpc/client';
import { Switch } from '~/components/ui/switch';

const ActiveProtocolSwitch = ({
initialData,
hash,
}: {
initialData: boolean;
hash: string;
}) => {
const utils = trpc.useContext();
const router = useRouter();

const { data: isActive } = trpc.protocol.getActive.useQuery(hash, {
initialData,
});

const { mutateAsync: setActive } = trpc.protocol.setActive.useMutation({
async onMutate(variables) {
const { input: newState, hash } = variables;
await utils.protocol.getActive.cancel();

const previousState = utils.protocol.getActive.getData();

if (hash) {
utils.protocol.getActive.setData(hash, newState);
}

return previousState;
},
onError: (err, _newState, previousState) => {
utils.protocol.getActive.setData(hash, previousState);
// eslint-disable-next-line no-console
console.error(err);
},
onSuccess: () => {
router.refresh();
},
});

const handleCheckedChange = async () => {
await setActive({ input: !isActive, hash });
};

return (
<Switch
checked={isActive}
onCheckedChange={() => void handleCheckedChange()}
/>
);
};
export default ActiveProtocolSwitch;
2 changes: 1 addition & 1 deletion app/(dashboard)/dashboard/_components/NavigationBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function NavigationBar() {
Home
</NavButton>
<NavButton
href="/dashboard"
href="/dashboard/protocols"
isActive={pathname === '/dashboard/protocols'}
>
Protocols
Expand Down
108 changes: 35 additions & 73 deletions app/(dashboard)/dashboard/_components/ProtocolUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ import { useState, useCallback } from 'react';

import { importProtocol } from '../_actions/importProtocol';
import { Button } from '~/components/ui/Button';
import { Switch } from '~/components/ui/switch';
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
} from '~/components/ui/form';

const ActivateProtocolFormSchema = z.object({
mark_protocol_active: z.boolean().default(false),
});

const { useUploadThing } = generateReactHelpers();

Expand All @@ -31,11 +18,8 @@ import {
DialogTitle,
} from '~/components/ui/dialog';
import type { UploadFileResponse } from 'uploadthing/client';
import React from 'react';
import { Collapsible, CollapsibleContent } from '~/components/ui/collapsible';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import ActiveProtocolSwitch from '~/app/(dashboard)/dashboard/_components/ActiveProtocolSwitch';
import { trpc } from '~/app/_trpc/client';

export default function ProtocolUploader({
Expand All @@ -51,11 +35,7 @@ export default function ProtocolUploader({
progress: true,
error: 'dsfsdf',
});
const { mutate: setActive } = trpc.protocol.setActive.useMutation({
onSuccess: () => {
setOpen(false);
},
});
const utils = trpc.useContext();

const handleUploadComplete = async (
res: UploadFileResponse[] | undefined,
Expand All @@ -74,25 +54,17 @@ export default function ProtocolUploader({
});
const { error, success } = await importProtocol(firstFile);

if (error) {
if (error || !success) {
setDialogContent({
title: 'Protocol import',
description: 'Error importing protocol',
progress: false,
error: error,
error: error ?? 'Unkown error occured',
});
return;
buckhalt marked this conversation as resolved.
Show resolved Hide resolved
}

if (!success) {
setDialogContent({
title: 'Protocol import',
description: 'Error importing protocol',
progress: false,
error: 'Unkown error occured',
});
return;
}
await utils.protocol.get.lastUploaded.refetch();

setDialogContent({
title: 'Protocol import',
Expand Down Expand Up @@ -154,17 +126,16 @@ export default function ProtocolUploader({
accept: { 'application/octect-stream': ['.netcanvas'] },
});

const form = useForm<z.infer<typeof ActivateProtocolFormSchema>>({
resolver: zodResolver(ActivateProtocolFormSchema),
});

function onSubmit(data: z.infer<typeof ActivateProtocolFormSchema>) {
setActive({ setActive: data.mark_protocol_active });
function handleFinishImport() {
if (typeof onUploaded === 'function') {
onUploaded();
}
setOpen(false);
}

const { data: lastUploadedProtocol } =
trpc.protocol.get.lastUploaded.useQuery();

return (
<>
<div
Expand Down Expand Up @@ -214,44 +185,35 @@ export default function ProtocolUploader({
</CollapsibleContent>
</Collapsible>
)}
{!dialogContent.progress && !dialogContent.error && (
<Form {...form}>
<form
onSubmit={() => void form.handleSubmit(onSubmit)}
className="w-full space-y-6"
>
{!dialogContent.progress &&
!dialogContent.error &&
lastUploadedProtocol && (
<div className="w-full space-y-6">
<div>
<div className="space-y-4">
<FormField
control={form.control}
name="mark_protocol_active"
render={({ field }) => (
<FormItem className="flex flex-row items-center rounded-lg border p-4">
<div className="space-y-0.5">
<FormLabel className="text-base">
Mark protocol as active?
</FormLabel>
<FormDescription>
Only one protocol may be active at a time. If you
already have an active protocol, activating this
one will make it inactive.
</FormDescription>
</div>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
</FormItem>
)}
/>
<div className="flex flex-row items-center rounded-lg border p-4">
<div className="space-y-0.5">
<label>Mark protocol as active?</label>
<p className="text-xs">
Only one protocol may be active at a time. If you
already have an active protocol, activating this one
will make it inactive.
</p>
</div>
<div>
<ActiveProtocolSwitch
initialData={lastUploadedProtocol.active}
hash={lastUploadedProtocol.hash}
/>
</div>
</div>
</div>
</div>
<Button type="submit">Finish Import</Button>
</form>
</Form>
)}
<Button type="submit" onClick={handleFinishImport}>
Finish Import
</Button>
</div>
)}
</DialogContent>
</Dialog>
</>
Expand Down
84 changes: 68 additions & 16 deletions app/(dashboard)/dashboard/_components/ProtocolsTable/Columns.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { type ColumnDef, flexRender } from '@tanstack/react-table';
import type { Protocol } from '@prisma/client';
import { ActionsDropdown } from '~/components/DataTable/ActionsDropdown';
import { Checkbox } from '~/components/ui/checkbox';
import { Settings } from 'lucide-react';
Expand All @@ -13,8 +12,14 @@ import {
TooltipProvider,
TooltipTrigger,
} from '~/components/ui/tooltip';
import { DropdownMenuItem } from '~/components/ui/dropdown-menu';
import ActiveProtocolSwitch from '~/app/(dashboard)/dashboard/_components/ActiveProtocolSwitch';

export const ProtocolColumns: ColumnDef<Protocol>[] = [
import type { ProtocolWithInterviews } from '~/shared/types';

export const ProtocolColumns = (
handleDelete: (data: ProtocolWithInterviews[]) => void,
): ColumnDef<ProtocolWithInterviews>[] => [
{
id: 'select',
header: ({ table }) => (
Expand All @@ -39,13 +44,27 @@ export const ProtocolColumns: ColumnDef<Protocol>[] = [
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Name" />;
},
cell: ({ row }) => {
return (
<div className={row.original.active ? '' : 'text-muted-foreground'}>
{flexRender(row.original.name, row)}
</div>
);
},
},
{
accessorKey: 'description',
header: 'Description',
cell: ({ row }) => {
return (
<div key={row.original.description} className="min-w-[200px]">
<div
className={
row.original.active
? 'min-w-[200px]'
: 'min-w-[200px] text-muted-foreground'
}
key={row.original.description}
>
{flexRender(row.original.description, row)}
</div>
);
Expand All @@ -56,26 +75,43 @@ export const ProtocolColumns: ColumnDef<Protocol>[] = [
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Imported" />;
},
cell: ({ row }) => {
const date = new Date(row.original.importedAt);
const isoString = date.toISOString().replace('T', ' ').replace('Z', '');
return isoString + ' UTC';
},
cell: ({ row }) => (
<div className={row.original.active ? '' : 'text-muted-foreground'}>
{new Date(row.original.importedAt).toLocaleString()}
</div>
),
},
{
accessorKey: 'lastModified',
header: ({ column }) => {
return <DataTableColumnHeader column={column} title="Modified" />;
},
cell: ({ row }) => {
const date = new Date(row.original.lastModified);
const isoString = date.toISOString().replace('T', ' ').replace('Z', '');
return isoString + ' UTC';
},
cell: ({ row }) => (
<div className={row.original.active ? '' : 'text-muted-foreground'}>
{new Date(row.original.lastModified).toLocaleString()}
</div>
),
},
{
accessorKey: 'schemaVersion',
header: 'Schema Version',
cell: ({ row }) => (
<div className={row.original.active ? '' : 'text-muted-foreground'}>
{row.original.schemaVersion}
</div>
),
},
{
accessorKey: 'active',
header: 'Active',
cell: ({ row }) => {
return (
<ActiveProtocolSwitch
initialData={row.original.active}
hash={row.original.hash}
/>
);
},
},
{
id: 'actions',
Expand All @@ -86,13 +122,29 @@ export const ProtocolColumns: ColumnDef<Protocol>[] = [
<Settings />
</TooltipTrigger>
<TooltipContent>
<p>Edit or delete an individual protocol.</p>
<p>Delete an individual protocol.</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
),
cell: () => {
return <ActionsDropdown />;
cell: ({ row }) => {
return (
<ActionsDropdown
menuItems={[
{
label: 'Delete',
row,
component: (
<DropdownMenuItem
onClick={() => void handleDelete([row.original])}
>
Delete
</DropdownMenuItem>
),
},
]}
/>
);
},
},
];
Loading