| @@ -0,0 +1,17 @@ | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import { Stack, Typography, Link } from "@mui/material"; | |||||
| import NextLink from "next/link"; | |||||
| export default async function NotFound() { | |||||
| const { t } = await getServerI18n("tasks", "common"); | |||||
| return ( | |||||
| <Stack spacing={2}> | |||||
| <Typography variant="h4">{t("Not Found")}</Typography> | |||||
| <Typography variant="body1">{t("The task template was not found!")}</Typography> | |||||
| <Link href="/projects" component={NextLink} variant="body2"> | |||||
| {t("Return to all task templates")} | |||||
| </Link> | |||||
| </Stack> | |||||
| ); | |||||
| } | |||||
| @@ -1,23 +1,44 @@ | |||||
| import { preloadAllTasks } from "@/app/api/tasks"; | |||||
| import { fetchTaskTemplate, preloadAllTasks } from "@/app/api/tasks"; | |||||
| import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | ||||
| import { getServerI18n } from "@/i18n"; | import { getServerI18n } from "@/i18n"; | ||||
| import Typography from "@mui/material/Typography"; | import Typography from "@mui/material/Typography"; | ||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| import { ServerFetchError } from "@/app/utils/fetchUtil"; | |||||
| import { isArray } from "lodash"; | |||||
| import { notFound } from "next/navigation"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Edit Task Template", | title: "Edit Task Template", | ||||
| }; | }; | ||||
| const TaskTemplates: React.FC = async () => { | |||||
| interface Props { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| const TaskTemplates: React.FC<Props> = async ({ searchParams }) => { | |||||
| const { t } = await getServerI18n("tasks"); | const { t } = await getServerI18n("tasks"); | ||||
| const taskTemplateId = searchParams["id"]; | |||||
| if (!taskTemplateId || isArray(taskTemplateId)) { | |||||
| notFound(); | |||||
| } | |||||
| preloadAllTasks(); | preloadAllTasks(); | ||||
| try { | |||||
| await fetchTaskTemplate(taskTemplateId); | |||||
| } catch (e) { | |||||
| if (e instanceof ServerFetchError && e.response?.status === 404) { | |||||
| notFound(); | |||||
| } | |||||
| } | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Typography variant="h4">{t("Edit Task Template")}</Typography> | <Typography variant="h4">{t("Edit Task Template")}</Typography> | ||||
| <I18nProvider namespaces={["tasks", "common"]}> | <I18nProvider namespaces={["tasks", "common"]}> | ||||
| <CreateTaskTemplate /> | |||||
| <CreateTaskTemplate taskTemplateId={taskTemplateId}/> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -4,13 +4,26 @@ import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil | |||||
| import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
| import { TaskTemplate } from "."; | import { TaskTemplate } from "."; | ||||
| import { revalidateTag } from "next/cache"; | import { revalidateTag } from "next/cache"; | ||||
| import { ManhourAllocation } from "@/app/api/projects/actions"; | |||||
| import { Task, TaskGroup } from '@/app/api/tasks'; | |||||
| export interface NewTaskTemplateFormInputs { | export interface NewTaskTemplateFormInputs { | ||||
| // task template | |||||
| code: string; | code: string; | ||||
| name: string; | name: string; | ||||
| taskIds: number[]; | taskIds: number[]; | ||||
| id: number | null; | id: number | null; | ||||
| // resource allocation template | |||||
| manhourPercentageByGrade: ManhourAllocation; | |||||
| taskGroups: { | |||||
| [taskGroup: TaskGroup["id"]]: { | |||||
| taskIds: Task["id"][]; | |||||
| percentAllocation: number; | |||||
| }; | |||||
| }; | |||||
| } | } | ||||
| export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | ||||
| @@ -39,3 +39,15 @@ export const preloadAllTasks = () => { | |||||
| export const fetchAllTasks = cache(async () => { | export const fetchAllTasks = cache(async () => { | ||||
| return serverFetchJson<Task[]>(`${BASE_API_URL}/tasks`); | return serverFetchJson<Task[]>(`${BASE_API_URL}/tasks`); | ||||
| }); | }); | ||||
| export const fetchTaskTemplate = cache(async (id: string) => { | |||||
| const taskTemplate = await serverFetchJson<TaskTemplate>( | |||||
| `${BASE_API_URL}/tasks/templatesDetails/${id}`, | |||||
| { | |||||
| method: "GET", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| return taskTemplate; | |||||
| }); | |||||
| @@ -13,7 +13,7 @@ import Close from "@mui/icons-material/Close"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
| import React from "react"; | import React from "react"; | ||||
| import Stack from "@mui/material/Stack"; | import Stack from "@mui/material/Stack"; | ||||
| import { Task } from "@/app/api/tasks"; | |||||
| import { Task, TaskTemplate } from "@/app/api/tasks"; | |||||
| import { | import { | ||||
| NewTaskTemplateFormInputs, | NewTaskTemplateFormInputs, | ||||
| fetchTaskTemplate, | fetchTaskTemplate, | ||||
| @@ -24,9 +24,10 @@ import { errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
| interface Props { | interface Props { | ||||
| tasks: Task[]; | tasks: Task[]; | ||||
| defaultInputs?: TaskTemplate; | |||||
| } | } | ||||
| const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| const CreateTaskTemplate: React.FC<Props> = ({ tasks, defaultInputs }) => { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const searchParams = useSearchParams() | const searchParams = useSearchParams() | ||||
| @@ -54,45 +55,45 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| watch, | watch, | ||||
| resetField, | resetField, | ||||
| formState: { errors, isSubmitting }, | formState: { errors, isSubmitting }, | ||||
| } = useForm<NewTaskTemplateFormInputs>({ defaultValues: { taskIds: [] } }); | |||||
| } = useForm<NewTaskTemplateFormInputs>({ defaultValues: defaultInputs }); | |||||
| const currentTaskIds = watch("taskIds"); | const currentTaskIds = watch("taskIds"); | ||||
| const selectedItems = React.useMemo(() => { | const selectedItems = React.useMemo(() => { | ||||
| return items.filter((item) => currentTaskIds.includes(item.id)); | return items.filter((item) => currentTaskIds.includes(item.id)); | ||||
| }, [currentTaskIds, items]); | }, [currentTaskIds, items]); | ||||
| const [refTaskTemplate, setRefTaskTemplate] = React.useState<NewTaskTemplateFormInputs>() | |||||
| const id = searchParams.get('id') | |||||
| const fetchCurrentTaskTemplate = async () => { | |||||
| try { | |||||
| const taskTemplate = await fetchTaskTemplate(parseInt(id!!)) | |||||
| const defaultValues = { | |||||
| id: parseInt(id!!), | |||||
| code: taskTemplate.code ?? null, | |||||
| name: taskTemplate.name ?? null, | |||||
| taskIds: taskTemplate.tasks.map(task => task.id) ?? [], | |||||
| } | |||||
| setRefTaskTemplate(defaultValues) | |||||
| } catch (e) { | |||||
| console.log(e) | |||||
| } | |||||
| } | |||||
| React.useLayoutEffect(() => { | |||||
| if (id !== null && parseInt(id) > 0) fetchCurrentTaskTemplate() | |||||
| }, [id]) | |||||
| React.useEffect(() => { | |||||
| if (refTaskTemplate) { | |||||
| setValue("taskIds", refTaskTemplate.taskIds) | |||||
| resetField("code", { defaultValue: refTaskTemplate.code }) | |||||
| resetField("name", { defaultValue: refTaskTemplate.name }) | |||||
| setValue("id", refTaskTemplate.id) | |||||
| } | |||||
| }, [refTaskTemplate]) | |||||
| // const [refTaskTemplate, setRefTaskTemplate] = React.useState<NewTaskTemplateFormInputs>() | |||||
| // const id = searchParams.get('id') | |||||
| // const fetchCurrentTaskTemplate = async () => { | |||||
| // try { | |||||
| // const taskTemplate = await fetchTaskTemplate(parseInt(id!!)) | |||||
| // const defaultValues = { | |||||
| // id: parseInt(id!!), | |||||
| // code: taskTemplate.code ?? null, | |||||
| // name: taskTemplate.name ?? null, | |||||
| // taskIds: taskTemplate.tasks.map(task => task.id) ?? [], | |||||
| // } | |||||
| // setRefTaskTemplate(defaultValues) | |||||
| // } catch (e) { | |||||
| // console.log(e) | |||||
| // } | |||||
| // } | |||||
| // React.useLayoutEffect(() => { | |||||
| // if (id !== null && parseInt(id) > 0) fetchCurrentTaskTemplate() | |||||
| // }, [id]) | |||||
| // React.useEffect(() => { | |||||
| // if (refTaskTemplate) { | |||||
| // setValue("taskIds", refTaskTemplate.taskIds) | |||||
| // resetField("code", { defaultValue: refTaskTemplate.code }) | |||||
| // resetField("name", { defaultValue: refTaskTemplate.name }) | |||||
| // setValue("id", refTaskTemplate.id) | |||||
| // } | |||||
| // }, [refTaskTemplate]) | |||||
| const onSubmit: SubmitHandler<NewTaskTemplateFormInputs> = React.useCallback( | const onSubmit: SubmitHandler<NewTaskTemplateFormInputs> = React.useCallback( | ||||
| async (data) => { | async (data) => { | ||||
| @@ -120,9 +121,9 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| { | |||||
| (id === null || refTaskTemplate !== undefined) && <Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||||
| <Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||||
| <Card> | <Card> | ||||
| {/* Task List Setup */} | |||||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | ||||
| <Typography variant="overline">{t("Task List Setup")}</Typography> | <Typography variant="overline">{t("Task List Setup")}</Typography> | ||||
| <Grid | <Grid | ||||
| @@ -166,6 +167,9 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| allItemsLabel={t("Task Pool")} | allItemsLabel={t("Task Pool")} | ||||
| selectedItemsLabel={t("Task List Template")} | selectedItemsLabel={t("Task List Template")} | ||||
| /> | /> | ||||
| {/* Task List Setup */} | |||||
| {/* Task List Setup */} | |||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> | ||||
| { | { | ||||
| @@ -188,7 +192,7 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| {t("Confirm")} | {t("Confirm")} | ||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| </Stack >} | |||||
| </Stack > | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,11 +1,18 @@ | |||||
| import React from "react"; | import React from "react"; | ||||
| import CreateTaskTemplate from "./CreateTaskTemplate"; | import CreateTaskTemplate from "./CreateTaskTemplate"; | ||||
| import { fetchAllTasks } from "@/app/api/tasks"; | |||||
| import { fetchAllTasks, fetchTaskTemplate } from "@/app/api/tasks"; | |||||
| const CreateTaskTemplateWrapper: React.FC = async () => { | |||||
| const tasks = await fetchAllTasks(); | |||||
| interface Props { | |||||
| taskTemplateId?: string; | |||||
| } | |||||
| return <CreateTaskTemplate tasks={tasks} />; | |||||
| const CreateTaskTemplateWrapper: React.FC<Props> = async (props) => { | |||||
| const [tasks] = await Promise.all([ | |||||
| fetchAllTasks(), | |||||
| ]); | |||||
| const taskTemplateInfo = props.taskTemplateId ? await fetchTaskTemplate(props.taskTemplateId) : undefined | |||||
| return <CreateTaskTemplate tasks={tasks} defaultInputs={taskTemplateInfo}/>; | |||||
| }; | }; | ||||
| export default CreateTaskTemplateWrapper; | export default CreateTaskTemplateWrapper; | ||||
| @@ -51,13 +51,17 @@ export const warningDialog = (text, t) => { | |||||
| }) | }) | ||||
| } | } | ||||
| export const submitDialog = async (confirmAction, t, {...props}) => { | |||||
| export const submitDialog = async (confirmAction, t, {...props} = { | |||||
| title: null, | |||||
| confirmButtonText: null | |||||
| }) => { | |||||
| // console.log(props) | |||||
| // const { t } = useTranslation("common") | // const { t } = useTranslation("common") | ||||
| const result = await Swal.fire({ | const result = await Swal.fire({ | ||||
| icon: "question", | icon: "question", | ||||
| title: props.title ?? t("Do you want to submit?"), | |||||
| title: props?.title ?? t("Do you want to submit?"), | |||||
| cancelButtonText: t("Cancel"), | cancelButtonText: t("Cancel"), | ||||
| confirmButtonText: props.confirmButtonText ?? t("Submit"), | |||||
| confirmButtonText: props?.confirmButtonText ?? t("Submit"), | |||||
| showCancelButton: true, | showCancelButton: true, | ||||
| showConfirmButton: true, | showConfirmButton: true, | ||||
| customClass: { | customClass: { | ||||