@@ -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 { getServerI18n } from "@/i18n"; | |||
import Typography from "@mui/material/Typography"; | |||
import { Metadata } from "next"; | |||
import { I18nProvider } from "@/i18n"; | |||
import { ServerFetchError } from "@/app/utils/fetchUtil"; | |||
import { isArray } from "lodash"; | |||
import { notFound } from "next/navigation"; | |||
export const metadata: Metadata = { | |||
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 taskTemplateId = searchParams["id"]; | |||
if (!taskTemplateId || isArray(taskTemplateId)) { | |||
notFound(); | |||
} | |||
preloadAllTasks(); | |||
try { | |||
await fetchTaskTemplate(taskTemplateId); | |||
} catch (e) { | |||
if (e instanceof ServerFetchError && e.response?.status === 404) { | |||
notFound(); | |||
} | |||
} | |||
return ( | |||
<> | |||
<Typography variant="h4">{t("Edit Task Template")}</Typography> | |||
<I18nProvider namespaces={["tasks", "common"]}> | |||
<CreateTaskTemplate /> | |||
<CreateTaskTemplate taskTemplateId={taskTemplateId}/> | |||
</I18nProvider> | |||
</> | |||
); | |||
@@ -4,13 +4,26 @@ import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { TaskTemplate } from "."; | |||
import { revalidateTag } from "next/cache"; | |||
import { ManhourAllocation } from "@/app/api/projects/actions"; | |||
import { Task, TaskGroup } from '@/app/api/tasks'; | |||
export interface NewTaskTemplateFormInputs { | |||
// task template | |||
code: string; | |||
name: string; | |||
taskIds: number[]; | |||
id: number | null; | |||
// resource allocation template | |||
manhourPercentageByGrade: ManhourAllocation; | |||
taskGroups: { | |||
[taskGroup: TaskGroup["id"]]: { | |||
taskIds: Task["id"][]; | |||
percentAllocation: number; | |||
}; | |||
}; | |||
} | |||
export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | |||
@@ -39,3 +39,15 @@ export const preloadAllTasks = () => { | |||
export const fetchAllTasks = cache(async () => { | |||
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 React from "react"; | |||
import Stack from "@mui/material/Stack"; | |||
import { Task } from "@/app/api/tasks"; | |||
import { Task, TaskTemplate } from "@/app/api/tasks"; | |||
import { | |||
NewTaskTemplateFormInputs, | |||
fetchTaskTemplate, | |||
@@ -24,9 +24,10 @@ import { errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||
interface Props { | |||
tasks: Task[]; | |||
defaultInputs?: TaskTemplate; | |||
} | |||
const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||
const CreateTaskTemplate: React.FC<Props> = ({ tasks, defaultInputs }) => { | |||
const { t } = useTranslation(); | |||
const searchParams = useSearchParams() | |||
@@ -54,45 +55,45 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||
watch, | |||
resetField, | |||
formState: { errors, isSubmitting }, | |||
} = useForm<NewTaskTemplateFormInputs>({ defaultValues: { taskIds: [] } }); | |||
} = useForm<NewTaskTemplateFormInputs>({ defaultValues: defaultInputs }); | |||
const currentTaskIds = watch("taskIds"); | |||
const selectedItems = React.useMemo(() => { | |||
return items.filter((item) => currentTaskIds.includes(item.id)); | |||
}, [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( | |||
async (data) => { | |||
@@ -120,9 +121,9 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||
return ( | |||
<> | |||
{ | |||
(id === null || refTaskTemplate !== undefined) && <Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||
<Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||
<Card> | |||
{/* Task List Setup */} | |||
<CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||
<Typography variant="overline">{t("Task List Setup")}</Typography> | |||
<Grid | |||
@@ -166,6 +167,9 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||
allItemsLabel={t("Task Pool")} | |||
selectedItemsLabel={t("Task List Template")} | |||
/> | |||
{/* Task List Setup */} | |||
{/* Task List Setup */} | |||
</CardContent> | |||
</Card> | |||
{ | |||
@@ -188,7 +192,7 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||
{t("Confirm")} | |||
</Button> | |||
</Stack> | |||
</Stack >} | |||
</Stack > | |||
</> | |||
); | |||
}; | |||
@@ -1,11 +1,18 @@ | |||
import React from "react"; | |||
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; |
@@ -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 result = await Swal.fire({ | |||
icon: "question", | |||
title: props.title ?? t("Do you want to submit?"), | |||
title: props?.title ?? t("Do you want to submit?"), | |||
cancelButtonText: t("Cancel"), | |||
confirmButtonText: props.confirmButtonText ?? t("Submit"), | |||
confirmButtonText: props?.confirmButtonText ?? t("Submit"), | |||
showCancelButton: true, | |||
showConfirmButton: true, | |||
customClass: { | |||