| @@ -1,4 +1,4 @@ | |||||
| import CustomerDetail from "@/components/CustomerDetail"; | |||||
| import CustomerSave from "@/components/CustomerSave"; | |||||
| // import { preloadAllTasks } from "@/app/api/tasks"; | // import { preloadAllTasks } from "@/app/api/tasks"; | ||||
| import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | ||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| @@ -16,7 +16,7 @@ const CreateCustomer: React.FC = async () => { | |||||
| <> | <> | ||||
| <Typography variant="h4">{t("Create Customer")}</Typography> | <Typography variant="h4">{t("Create Customer")}</Typography> | ||||
| <I18nProvider namespaces={["customer", "common"]}> | <I18nProvider namespaces={["customer", "common"]}> | ||||
| <CustomerDetail /> | |||||
| <CustomerSave /> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -1,5 +1,5 @@ | |||||
| import { fetchAllSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; | import { fetchAllSubsidiaries, preloadAllCustomers } from "@/app/api/customer"; | ||||
| import CustomerDetail from "@/components/CustomerDetail"; | |||||
| import CustomerSave from "@/components/CustomerSave"; | |||||
| // import { preloadAllTasks } from "@/app/api/tasks"; | // import { preloadAllTasks } from "@/app/api/tasks"; | ||||
| import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | import CreateTaskTemplate from "@/components/CreateTaskTemplate"; | ||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| @@ -18,7 +18,7 @@ const EditCustomer: React.FC = async () => { | |||||
| <> | <> | ||||
| <Typography variant="h4">{t("Edit Customer")}</Typography> | <Typography variant="h4">{t("Edit Customer")}</Typography> | ||||
| <I18nProvider namespaces={["customer", "common"]}> | <I18nProvider namespaces={["customer", "common"]}> | ||||
| <CustomerDetail /> | |||||
| <CustomerSave /> | |||||
| </I18nProvider> | </I18nProvider> | ||||
| </> | </> | ||||
| ); | ); | ||||
| @@ -3,6 +3,7 @@ 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"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Create Task Template", | title: "Create Task Template", | ||||
| @@ -15,7 +16,9 @@ const Projects: React.FC = async () => { | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Typography variant="h4">{t("Create Task Template")}</Typography> | <Typography variant="h4">{t("Create Task Template")}</Typography> | ||||
| <CreateTaskTemplate /> | |||||
| <I18nProvider namespaces={["tasks", "common"]}> | |||||
| <CreateTaskTemplate /> | |||||
| </I18nProvider> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -0,0 +1,26 @@ | |||||
| import { 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"; | |||||
| export const metadata: Metadata = { | |||||
| title: "Edit Task Template", | |||||
| }; | |||||
| const TaskTemplates: React.FC = async () => { | |||||
| const { t } = await getServerI18n("tasks"); | |||||
| preloadAllTasks(); | |||||
| return ( | |||||
| <> | |||||
| <Typography variant="h4">{t("Edit Task Template")}</Typography> | |||||
| <I18nProvider namespaces={["tasks", "common"]}> | |||||
| <CreateTaskTemplate /> | |||||
| </I18nProvider> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default TaskTemplates; | |||||
| @@ -8,13 +8,14 @@ import Typography from "@mui/material/Typography"; | |||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import Link from "next/link"; | import Link from "next/link"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import { I18nProvider } from "@/i18n"; | |||||
| export const metadata: Metadata = { | export const metadata: Metadata = { | ||||
| title: "Tasks", | title: "Tasks", | ||||
| }; | }; | ||||
| const TaskTemplates: React.FC = async () => { | const TaskTemplates: React.FC = async () => { | ||||
| const { t } = await getServerI18n("projects"); | |||||
| const { t } = await getServerI18n("tasks"); | |||||
| preloadTaskTemplates(); | preloadTaskTemplates(); | ||||
| return ( | return ( | ||||
| @@ -34,12 +35,14 @@ const TaskTemplates: React.FC = async () => { | |||||
| LinkComponent={Link} | LinkComponent={Link} | ||||
| href="/tasks/create" | href="/tasks/create" | ||||
| > | > | ||||
| {t("Create Template")} | |||||
| {t("Create Task Template")} | |||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| <Suspense fallback={<TaskTemplateSearch.Loading />}> | |||||
| <TaskTemplateSearch /> | |||||
| </Suspense> | |||||
| <I18nProvider namespaces={["tasks", "common"]}> | |||||
| <Suspense fallback={<TaskTemplateSearch.Loading />}> | |||||
| <TaskTemplateSearch /> | |||||
| </Suspense> | |||||
| </I18nProvider> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1,6 +1,6 @@ | |||||
| "use server"; | "use server"; | ||||
| import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
| 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"; | ||||
| @@ -9,11 +9,13 @@ export interface NewTaskTemplateFormInputs { | |||||
| code: string; | code: string; | ||||
| name: string; | name: string; | ||||
| taskIds: number[]; | taskIds: number[]; | ||||
| id: number | null; | |||||
| } | } | ||||
| export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | ||||
| const newTaskTemplate = await serverFetchJson<TaskTemplate>( | const newTaskTemplate = await serverFetchJson<TaskTemplate>( | ||||
| `${BASE_API_URL}/tasks/templates/new`, | |||||
| `${BASE_API_URL}/tasks/templates/save`, | |||||
| { | { | ||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| @@ -25,3 +27,27 @@ export const saveTaskTemplate = async (data: NewTaskTemplateFormInputs) => { | |||||
| return newTaskTemplate; | return newTaskTemplate; | ||||
| }; | }; | ||||
| export const fetchTaskTemplate = async (id: number) => { | |||||
| const taskTemplate = await serverFetchJson<TaskTemplate>( | |||||
| `${BASE_API_URL}/tasks/templates/${id}`, | |||||
| { | |||||
| method: "GET", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| return taskTemplate; | |||||
| }; | |||||
| export const deleteTaskTemplate = async (id: number) => { | |||||
| const taskTemplate = await serverFetchWithNoContent( | |||||
| `${BASE_API_URL}/tasks/templates/${id}`, | |||||
| { | |||||
| method: "DELETE", | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }, | |||||
| ); | |||||
| return taskTemplate | |||||
| }; | |||||
| @@ -10,15 +10,17 @@ import TransferList from "../TransferList"; | |||||
| import Button from "@mui/material/Button"; | import Button from "@mui/material/Button"; | ||||
| import Check from "@mui/icons-material/Check"; | import Check from "@mui/icons-material/Check"; | ||||
| import Close from "@mui/icons-material/Close"; | import Close from "@mui/icons-material/Close"; | ||||
| import { useRouter } 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 } from "@/app/api/tasks"; | ||||
| import { | import { | ||||
| NewTaskTemplateFormInputs, | NewTaskTemplateFormInputs, | ||||
| fetchTaskTemplate, | |||||
| saveTaskTemplate, | saveTaskTemplate, | ||||
| } from "@/app/api/tasks/actions"; | } from "@/app/api/tasks/actions"; | ||||
| import { SubmitHandler, useForm } from "react-hook-form"; | |||||
| import { SubmitHandler, useFieldArray, useForm } from "react-hook-form"; | |||||
| import { errorDialog, submitDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
| interface Props { | interface Props { | ||||
| tasks: Task[]; | tasks: Task[]; | ||||
| @@ -27,6 +29,7 @@ interface Props { | |||||
| const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const searchParams = useSearchParams() | |||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const handleCancel = () => { | const handleCancel = () => { | ||||
| router.back(); | router.back(); | ||||
| @@ -49,6 +52,7 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| handleSubmit, | handleSubmit, | ||||
| setValue, | setValue, | ||||
| watch, | watch, | ||||
| resetField, | |||||
| formState: { errors, isSubmitting }, | formState: { errors, isSubmitting }, | ||||
| } = useForm<NewTaskTemplateFormInputs>({ defaultValues: { taskIds: [] } }); | } = useForm<NewTaskTemplateFormInputs>({ defaultValues: { taskIds: [] } }); | ||||
| @@ -57,12 +61,56 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| 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 onSubmit: SubmitHandler<NewTaskTemplateFormInputs> = React.useCallback( | const onSubmit: SubmitHandler<NewTaskTemplateFormInputs> = React.useCallback( | ||||
| async (data) => { | async (data) => { | ||||
| try { | try { | ||||
| setServerError(""); | setServerError(""); | ||||
| await saveTaskTemplate(data); | |||||
| router.replace("/tasks"); | |||||
| submitDialog(async () => { | |||||
| const response = await saveTaskTemplate(data); | |||||
| if (response?.id !== null && response?.id !== undefined && response?.id > 0) { | |||||
| successDialog(t("Submit Success"), t).then(() => { | |||||
| router.replace("/tasks"); | |||||
| }) | |||||
| } else { | |||||
| errorDialog(t("Submit Fail"), t).then(() => { | |||||
| return false | |||||
| }) | |||||
| } | |||||
| }, t) | |||||
| } catch (e) { | } catch (e) { | ||||
| setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
| } | } | ||||
| @@ -71,72 +119,77 @@ const CreateTaskTemplate: React.FC<Props> = ({ tasks }) => { | |||||
| ); | ); | ||||
| return ( | return ( | ||||
| <Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||||
| <Card> | |||||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||||
| <Typography variant="overline">{t("Task List Setup")}</Typography> | |||||
| <Grid | |||||
| container | |||||
| spacing={2} | |||||
| columns={{ xs: 6, sm: 12 }} | |||||
| marginBlockEnd={1} | |||||
| > | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Task Template Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: t("Task template code is required"), | |||||
| })} | |||||
| error={Boolean(errors.code?.message)} | |||||
| helperText={errors.code?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Task Template Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: t("Task template name is required"), | |||||
| })} | |||||
| error={Boolean(errors.name?.message)} | |||||
| helperText={errors.name?.message} | |||||
| <> | |||||
| { | |||||
| (id === null || refTaskTemplate !== undefined) && <Stack component="form" onSubmit={handleSubmit(onSubmit)} gap={2}> | |||||
| <Card> | |||||
| <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}> | |||||
| <Typography variant="overline">{t("Task List Setup")}</Typography> | |||||
| <Grid | |||||
| container | |||||
| spacing={2} | |||||
| columns={{ xs: 6, sm: 12 }} | |||||
| marginBlockEnd={1} | |||||
| > | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Task Template Code")} | |||||
| fullWidth | |||||
| {...register("code", { | |||||
| required: t("Task template code is required"), | |||||
| })} | |||||
| error={Boolean(errors.code?.message)} | |||||
| helperText={errors.code?.message} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <TextField | |||||
| label={t("Task Template Name")} | |||||
| fullWidth | |||||
| {...register("name", { | |||||
| required: t("Task template name is required"), | |||||
| })} | |||||
| error={Boolean(errors.name?.message)} | |||||
| helperText={errors.name?.message} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| <TransferList | |||||
| allItems={items} | |||||
| selectedItems={selectedItems} | |||||
| onChange={(selectedTasks) => { | |||||
| setValue( | |||||
| "taskIds", | |||||
| selectedTasks.map((item) => item.id), | |||||
| ); | |||||
| }} | |||||
| allItemsLabel={t("Task Pool")} | |||||
| selectedItemsLabel={t("Task List Template")} | |||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid> | |||||
| <TransferList | |||||
| allItems={items} | |||||
| selectedItems={selectedItems} | |||||
| onChange={(selectedTasks) => { | |||||
| setValue( | |||||
| "taskIds", | |||||
| selectedTasks.map((item) => item.id), | |||||
| ); | |||||
| }} | |||||
| allItemsLabel={t("Task Pool")} | |||||
| selectedItemsLabel={t("Task List Template")} | |||||
| /> | |||||
| </CardContent> | |||||
| </Card> | |||||
| {serverError && ( | |||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||||
| {serverError} | |||||
| </Typography> | |||||
| )} | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button variant="outlined" startIcon={<Close />} onClick={handleCancel}> | |||||
| {t("Cancel")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| type="submit" | |||||
| disabled={isSubmitting} | |||||
| > | |||||
| {t("Confirm")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Stack> | |||||
| </CardContent> | |||||
| </Card> | |||||
| { | |||||
| serverError && ( | |||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||||
| {serverError} | |||||
| </Typography> | |||||
| ) | |||||
| } | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Button variant="outlined" startIcon={<Close />} onClick={handleCancel}> | |||||
| {t("Cancel")} | |||||
| </Button> | |||||
| <Button | |||||
| variant="contained" | |||||
| startIcon={<Check />} | |||||
| type="submit" | |||||
| disabled={isSubmitting} | |||||
| > | |||||
| {t("Confirm")} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Stack >} | |||||
| </> | |||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -1 +0,0 @@ | |||||
| export { default } from "./CustomerDetailWrapper"; | |||||
| @@ -42,7 +42,7 @@ const hasErrorsInTab = ( | |||||
| } | } | ||||
| }; | }; | ||||
| const CustomerDetail: React.FC<Props> = ({ | |||||
| const CustomerSave: React.FC<Props> = ({ | |||||
| subsidiaries, | subsidiaries, | ||||
| customerTypes, | customerTypes, | ||||
| }) => { | }) => { | ||||
| @@ -277,4 +277,4 @@ const CustomerDetail: React.FC<Props> = ({ | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default CustomerDetail; | |||||
| export default CustomerSave; | |||||
| @@ -3,7 +3,7 @@ | |||||
| // import { fetchProjectCategories } from "@/app/api/projects"; | // import { fetchProjectCategories } from "@/app/api/projects"; | ||||
| // import { fetchTeamLeads } from "@/app/api/staff"; | // import { fetchTeamLeads } from "@/app/api/staff"; | ||||
| import { fetchCustomerTypes, fetchAllSubsidiaries } from "@/app/api/customer"; | import { fetchCustomerTypes, fetchAllSubsidiaries } from "@/app/api/customer"; | ||||
| import CustomerDetail from "./CustomerDetail"; | |||||
| import CustomerSave from "./CustomerSave"; | |||||
| // type Props = { | // type Props = { | ||||
| // params: { | // params: { | ||||
| @@ -11,7 +11,7 @@ import CustomerDetail from "./CustomerDetail"; | |||||
| // }; | // }; | ||||
| // }; | // }; | ||||
| const CustomerDetailWrapper: React.FC = async () => { | |||||
| const CustomerSaveWrapper: React.FC = async () => { | |||||
| // const { params } = props | // const { params } = props | ||||
| // console.log(params) | // console.log(params) | ||||
| const [subsidiaries, customerTypes] = | const [subsidiaries, customerTypes] = | ||||
| @@ -21,8 +21,8 @@ const CustomerDetailWrapper: React.FC = async () => { | |||||
| ]); | ]); | ||||
| return ( | return ( | ||||
| <CustomerDetail subsidiaries={subsidiaries} customerTypes={customerTypes} /> | |||||
| <CustomerSave subsidiaries={subsidiaries} customerTypes={customerTypes} /> | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default CustomerDetailWrapper; | |||||
| export default CustomerSaveWrapper; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default } from "./CustomerSaveWrapper"; | |||||
| @@ -68,7 +68,7 @@ const StaffSearch: React.FC<Props> = ({ staff }) => { | |||||
| const deleteClick = useCallback((staff: StaffResult) => { | const deleteClick = useCallback((staff: StaffResult) => { | ||||
| deleteDialog(async () => { | deleteDialog(async () => { | ||||
| await deleteStaff(staff.id); | await deleteStaff(staff.id); | ||||
| successDialog("Delete Success", t); | |||||
| successDialog(t("Delete Success"), t); | |||||
| setFilteredStaff((prev) => prev.filter((obj) => obj.id !== staff.id)); | setFilteredStaff((prev) => prev.filter((obj) => obj.id !== staff.id)); | ||||
| }, t); | }, t); | ||||
| }, []); | }, []); | ||||
| @@ -1,7 +1,7 @@ | |||||
| import { fetchAllCustomers, fetchSubsidiaryTypes } from "@/app/api/subsidiary"; | import { fetchAllCustomers, fetchSubsidiaryTypes } from "@/app/api/subsidiary"; | ||||
| import SubsidiaryDetail from "./SubsidiaryDetail"; | import SubsidiaryDetail from "./SubsidiaryDetail"; | ||||
| const CustomerDetailWrapper: React.FC = async () => { | |||||
| const CustomerSaveWrapper: React.FC = async () => { | |||||
| const [customers, subsidiaryTypes] = | const [customers, subsidiaryTypes] = | ||||
| await Promise.all([ | await Promise.all([ | ||||
| fetchAllCustomers(), | fetchAllCustomers(), | ||||
| @@ -13,4 +13,4 @@ const CustomerDetailWrapper: React.FC = async () => { | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default CustomerDetailWrapper; | |||||
| export default CustomerSaveWrapper; | |||||
| @@ -46,7 +46,7 @@ const SubsidiarySearch: React.FC<Props> = ({ subsidiaries }) => { | |||||
| deleteDialog(async() => { | deleteDialog(async() => { | ||||
| await deleteSubsidiary(subsidiary.id) | await deleteSubsidiary(subsidiary.id) | ||||
| successDialog("Delete Success", t) | |||||
| successDialog(t("Delete Success"), t) | |||||
| setFilteredSubsidiaries((prev) => prev.filter((obj) => obj.id !== subsidiary.id)) | setFilteredSubsidiaries((prev) => prev.filter((obj) => obj.id !== subsidiary.id)) | ||||
| }, t) | }, t) | ||||
| @@ -6,6 +6,10 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
| import EditNote from "@mui/icons-material/EditNote"; | import EditNote from "@mui/icons-material/EditNote"; | ||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import DeleteIcon from '@mui/icons-material/Delete'; | |||||
| import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
| import { deleteTaskTemplate } from "@/app/api/tasks/actions"; | |||||
| interface Props { | interface Props { | ||||
| taskTemplates: TaskTemplate[]; | taskTemplates: TaskTemplate[]; | ||||
| @@ -16,6 +20,8 @@ type SearchParamNames = keyof SearchQuery; | |||||
| const TaskTemplateSearch: React.FC<Props> = ({ taskTemplates }) => { | const TaskTemplateSearch: React.FC<Props> = ({ taskTemplates }) => { | ||||
| const { t } = useTranslation("tasks"); | const { t } = useTranslation("tasks"); | ||||
| const searchParams = useSearchParams() | |||||
| const router = useRouter() | |||||
| const [filteredTemplates, setFilteredTemplates] = useState(taskTemplates); | const [filteredTemplates, setFilteredTemplates] = useState(taskTemplates); | ||||
| const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | const searchCriteria: Criterion<SearchParamNames>[] = useMemo( | ||||
| @@ -30,7 +36,20 @@ const TaskTemplateSearch: React.FC<Props> = ({ taskTemplates }) => { | |||||
| }, [taskTemplates]); | }, [taskTemplates]); | ||||
| const onTaskClick = useCallback((taskTemplate: TaskTemplate) => { | const onTaskClick = useCallback((taskTemplate: TaskTemplate) => { | ||||
| console.log(taskTemplate); | |||||
| const params = new URLSearchParams(searchParams.toString()) | |||||
| params.set("id", taskTemplate.id.toString()) | |||||
| router.replace(`/tasks/edit?${params.toString()}`); | |||||
| }, []); | |||||
| const onDeleteClick = useCallback((taskTemplate: TaskTemplate) => { | |||||
| deleteDialog(async () => { | |||||
| await deleteTaskTemplate(taskTemplate.id) | |||||
| successDialog(t("Delete Success"), t) | |||||
| setFilteredTemplates((prev) => prev.filter((obj) => obj.id !== taskTemplate.id)) | |||||
| }, t) | |||||
| }, []); | }, []); | ||||
| const columns = useMemo<Column<TaskTemplate>[]>( | const columns = useMemo<Column<TaskTemplate>[]>( | ||||
| @@ -43,6 +62,13 @@ const TaskTemplateSearch: React.FC<Props> = ({ taskTemplates }) => { | |||||
| }, | }, | ||||
| { name: "code", label: t("Task Template Code") }, | { name: "code", label: t("Task Template Code") }, | ||||
| { name: "name", label: t("Task Template Name") }, | { name: "name", label: t("Task Template Name") }, | ||||
| { | |||||
| name: "id", | |||||
| label: t("Delete"), | |||||
| onClick: onDeleteClick, | |||||
| buttonIcon: <DeleteIcon />, | |||||
| color: "error" | |||||
| }, | |||||
| ], | ], | ||||
| [onTaskClick, t], | [onTaskClick, t], | ||||
| ); | ); | ||||
| @@ -56,7 +56,7 @@ const TeamSearch: React.FC<Props> = ({ team }) => { | |||||
| deleteDialog(async () => { | deleteDialog(async () => { | ||||
| await deleteTeam(team.id); | await deleteTeam(team.id); | ||||
| successDialog("Delete Success", t); | |||||
| successDialog(t("Delete Success"), t); | |||||
| setFilteredTeam((prev) => prev.filter((obj) => obj.id !== team.id)); | setFilteredTeam((prev) => prev.filter((obj) => obj.id !== team.id)); | ||||
| }, t); | }, t); | ||||
| @@ -109,7 +109,7 @@ const ItemList: React.FC<ItemListProps> = ({ | |||||
| </ListItemIcon> | </ListItemIcon> | ||||
| <Stack> | <Stack> | ||||
| <Typography variant="subtitle2">{label}</Typography> | <Typography variant="subtitle2">{label}</Typography> | ||||
| <Typography variant="caption">{`${checkedItems.length}/${items.length} selected`}</Typography> | |||||
| <Typography variant="caption">{`${checkedItems.length}/${items.length} ${t("selected")}`}</Typography> | |||||
| </Stack> | </Stack> | ||||
| </Stack> | </Stack> | ||||
| <Divider /> | <Divider /> | ||||
| @@ -45,7 +45,7 @@ const UserSearch: React.FC<Props> = ({ users }) => { | |||||
| deleteDialog(async () => { | deleteDialog(async () => { | ||||
| await deleteUser(users.id); | await deleteUser(users.id); | ||||
| successDialog("Delete Success", t); | |||||
| successDialog(t("Delete Success"), t); | |||||
| setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id)); | setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id)); | ||||
| }, t); | }, t); | ||||
| @@ -17,6 +17,8 @@ | |||||
| "Do you want to delete?": "Do you want to delete", | "Do you want to delete?": "Do you want to delete", | ||||
| "Delete Success": "Delete Success", | "Delete Success": "Delete Success", | ||||
| "Details": "Details", | |||||
| "Delete": "Delete", | |||||
| "Search": "Search", | "Search": "Search", | ||||
| "Search Criteria": "Search Criteria", | "Search Criteria": "Search Criteria", | ||||
| "Cancel": "Cancel", | "Cancel": "Cancel", | ||||
| @@ -0,0 +1,27 @@ | |||||
| { | |||||
| "Task Template": "Task Template", | |||||
| "Create Task Template": "Create Task Template", | |||||
| "Edit Task Template": "Edit Task Template", | |||||
| "Task Template Code": "Task Template Code", | |||||
| "Task Template Name": "Task Template Name", | |||||
| "Task List Setup": "Task List Setup", | |||||
| "Task Pool": "Task Pool", | |||||
| "Task List Template": "Task List Template", | |||||
| "Task template code is required": "Task template code is required", | |||||
| "Task template name is required": "Task template name is required", | |||||
| "Do you want to submit?": "Do you want to submit?", | |||||
| "Submit Success": "Submit Success", | |||||
| "Submit Fail": "Submit Fail", | |||||
| "Do you want to delete?": "Do you want to delete?", | |||||
| "Delete Success": "Delete Success", | |||||
| "selected": "selected", | |||||
| "Details": "Details", | |||||
| "Delete": "Delete", | |||||
| "Cancel": "Cancel", | |||||
| "Submit": "Submit", | |||||
| "Confirm": "Confirm" | |||||
| } | |||||
| @@ -15,6 +15,8 @@ | |||||
| "Do you want to delete?": "你是否確認要刪除?", | "Do you want to delete?": "你是否確認要刪除?", | ||||
| "Delete Success": "刪除成功", | "Delete Success": "刪除成功", | ||||
| "Details": "詳情", | |||||
| "Delete": "刪除", | |||||
| "Search": "搜尋", | "Search": "搜尋", | ||||
| "Search Criteria": "搜尋條件", | "Search Criteria": "搜尋條件", | ||||
| "Cancel": "取消", | "Cancel": "取消", | ||||
| @@ -0,0 +1,27 @@ | |||||
| { | |||||
| "Task Template": "工作範本", | |||||
| "Create Task Template": "建立工作範本", | |||||
| "Edit Task Template": "編輯工作範本", | |||||
| "Task Template Code": "工作範本編號", | |||||
| "Task Template Name": "工作範本名稱", | |||||
| "Task List Setup": "工作名單設置", | |||||
| "Task Pool": "所有工作", | |||||
| "Task List Template": "工作名單範本", | |||||
| "Task template code is required": "需要工作範本編號", | |||||
| "Task template name is required": "需要工作範本名稱", | |||||
| "Do you want to submit?": "你是否確認要提交?", | |||||
| "Submit Success": "提交成功", | |||||
| "Submit Fail": "提交失敗", | |||||
| "Do you want to delete?": "你是否確認要刪除?", | |||||
| "Delete Success": "刪除成功", | |||||
| "selected": "已選擇", | |||||
| "Details": "詳情", | |||||
| "Delete": "刪除", | |||||
| "Cancel": "取消", | |||||
| "Submit": "提交", | |||||
| "Confirm": "確認" | |||||
| } | |||||