@@ -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: { | ||||