Skip to content
New issue

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

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

Already on GitHub? # to your account

feat: edit project design 🧑‍🎨 #1346

Merged
merged 2 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion web/src/components/data-table/data-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export function DataTable<TData, TValue>({
loading,
button,
onRowClick,
defaultRowsPerPage = 10
defaultRowsPerPage = 10,
}: DataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
Expand Down
85 changes: 85 additions & 0 deletions web/src/components/dialogs/edit-project-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import {Button} from '../ui/button';
import {List, ListDescription, ListItem} from '../ui/list';
import {Route} from '@/routes/projects/$projectId';
import {useAuth} from '@/context/auth-provider';
import {useGetProjects} from '@/hooks/get-hooks';
import {useState} from 'react';
import {NOTEBOOK_NAME, NOTEBOOK_NAME_CAPITALIZED} from '@/constants';
import {UpdateProjectForm} from '../forms/update-project-form';

/**
* EditProjectDialog component renders a dialog for editing a project.
* It provides a button to open the dialog and a form to update the project.
*
* @returns {JSX.Element} The rendered EditProjectDialog component.
*/
export const EditProjectDialog = () => {
const {user} = useAuth();
const {projectId} = Route.useParams();

const {data} = useGetProjects(user, projectId);

const [open, setOpen] = useState(false);

return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild className="w-fit">
<Button>Edit {NOTEBOOK_NAME_CAPITALIZED}</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Edit {NOTEBOOK_NAME_CAPITALIZED}</DialogTitle>
<DialogDescription>
Follow the following steps to edit the current {NOTEBOOK_NAME}.
</DialogDescription>
</DialogHeader>
<List>
<ListItem className="space-y-2">
<ListDescription>
1. Download the {NOTEBOOK_NAME} file.
</ListDescription>
<Button variant="outline">
<a
href={`data:text/json;charset=utf-8,${encodeURIComponent(
JSON.stringify(data)
)}`}
download={`${projectId}.json`}
>
Download
</a>
</Button>
</ListItem>
<ListItem>
<ListDescription>
2. Edit the {NOTEBOOK_NAME} either using a text editor or
uploading the template file to{' '}
<a
className="underline text-primary"
href={import.meta.env.VITE_DESIGNER_URL}
target="_blank"
rel="noreferrer"
>
Fieldmark Designer
</a>
.
</ListDescription>
</ListItem>
<ListItem className="space-y-2">
<ListDescription>
3. Upload the edited {NOTEBOOK_NAME} file.
</ListDescription>
<UpdateProjectForm setDialogOpen={setOpen} />
</ListItem>
</List>
</DialogContent>
</Dialog>
);
};
65 changes: 65 additions & 0 deletions web/src/components/forms/update-project-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {useAuth} from '@/context/auth-provider';
import {Form} from '@/components/form';
import {readFileAsText} from '@/lib/utils';
import {z} from 'zod';
import {NOTEBOOK_NAME} from '@/constants';
import {Route} from '@/routes/projects/$projectId';

const fields = [
{
name: 'file',
type: 'file',
schema: z.instanceof(File).refine(file => file.type === 'application/json'),
},
];

/**
* UpdateProjectForm component renders a form for updating a project.
* It provides a button to submit the form and a file input for selecting a JSON file.
*
* @param {React.Dispatch<React.SetStateAction<boolean>>} setDialogOpen - A function to set the dialog open state.
* @returns {JSX.Element} The rendered UpdateProjectForm component.
*/
export function UpdateProjectForm({
setDialogOpen,
}: {
setDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const {user} = useAuth();
const {projectId} = Route.useParams();

const onSubmit = async ({file}: {file: File}) => {
if (!user) return {type: 'submit', message: 'User not authenticated'};

const jsonString = await readFileAsText(file);

if (!jsonString) return {type: 'submit', message: 'Error reading file'};

const response = await fetch(
`${import.meta.env.VITE_API_URL}/api/notebooks/${projectId}`,
{
method: 'PUT',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${user.token}`,
},
body: jsonString,
}
);

if (!response.ok)
return {type: 'submit', message: 'Error updating template'};

setDialogOpen(false);
};

return (
<Form
fields={fields}
onSubmit={onSubmit}
submitButtonText={`Update ${NOTEBOOK_NAME}`}
submitButtonVariant="destructive"
warningMessage={`Editing the ${NOTEBOOK_NAME} may result in inconsistencies between responses.`}
/>
);
}
42 changes: 5 additions & 37 deletions web/src/components/tabs/project/actions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import {Alert, AlertTitle, AlertDescription} from '@/components/ui/alert';
import {Button} from '@/components/ui/button';
import {EditProjectDialog} from '@/components/dialogs/edit-project-dialog';
import {Card} from '@/components/ui/card';
import {List, ListDescription, ListItem, ListLabel} from '@/components/ui/list';
import {NOTEBOOK_NAME, NOTEBOOK_NAME_CAPITALIZED} from '@/constants';
Expand All @@ -16,44 +15,13 @@ import {NOTEBOOK_NAME, NOTEBOOK_NAME_CAPITALIZED} from '@/constants';
const ProjectActions = (): JSX.Element => {
return (
<div className="flex flex-col gap-2 justify-between">
<Card className="flex flex-col gap-4 flex-1">
<List>
<Card className="flex-1">
<List className="flex flex-col gap-4">
<ListItem>
<ListLabel>Edit {NOTEBOOK_NAME_CAPITALIZED}</ListLabel>
<ListDescription>Current Responses: 203</ListDescription>
<ListDescription>Edit the current {NOTEBOOK_NAME}.</ListDescription>
</ListItem>
<ListItem>
<Alert variant="destructive">
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
Updating the design for a {NOTEBOOK_NAME} with existing
responses could result in data inconsistencies.
</AlertDescription>
</Alert>
</ListItem>
<ListItem>
<Button variant="destructive">Edit {NOTEBOOK_NAME} Design</Button>
</ListItem>
</List>
</Card>
<Card className="flex flex-col gap-4 flex-1">
<List className="flex flex-col justify-between h-full">
<ListItem>
<ListLabel>Close {NOTEBOOK_NAME_CAPITALIZED}</ListLabel>
<ListDescription>Current Status: Active</ListDescription>
</ListItem>
<ListItem>
<Alert variant="destructive">
<AlertTitle>Warning</AlertTitle>
<AlertDescription>
Closing a {NOTEBOOK_NAME} prevents new responses from being
added to it.
</AlertDescription>
</Alert>
</ListItem>
<Button variant="destructive">
Close {NOTEBOOK_NAME_CAPITALIZED}
</Button>
<EditProjectDialog />
</List>
</Card>
</div>
Expand Down
Loading