Ediit Postion + Deletetags/Baseline_30082024_FRONTEND_UAT
@@ -0,0 +1,31 @@ | |||||
import CreateDepartment from "@/components/CreateDepartment"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
import Typography from "@mui/material/Typography"; | |||||
import { Metadata } from "next"; | |||||
export const metadata: Metadata = { | |||||
title: "Create Department", | |||||
}; | |||||
interface Props { | |||||
searchParams: { [key: string]: string | undefined }; | |||||
} | |||||
const Department: React.FC<Props> = async ({searchParams}) => { | |||||
const { t } = await getServerI18n("departments"); | |||||
// Preload necessary dependencies | |||||
// Assume projectId is string here | |||||
const departmentId = searchParams["id"]; | |||||
return ( | |||||
<> | |||||
<Typography variant="h4">{t("Create Department")}</Typography> | |||||
<I18nProvider namespaces={["departments"]}> | |||||
<CreateDepartment isEdit={true} departmentId={departmentId}/> | |||||
</I18nProvider> | |||||
</> | |||||
); | |||||
}; | |||||
export default Department; |
@@ -16,7 +16,7 @@ const Department: React.FC = async () => { | |||||
<> | <> | ||||
<Typography variant="h4">{t("Create Department")}</Typography> | <Typography variant="h4">{t("Create Department")}</Typography> | ||||
<I18nProvider namespaces={["departments"]}> | <I18nProvider namespaces={["departments"]}> | ||||
<CreateDepartment /> | |||||
<CreateDepartment isEdit={false} /> | |||||
</I18nProvider> | </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 { cache } from "react"; | import { cache } from "react"; | ||||
@@ -14,8 +14,9 @@ export interface combo { | |||||
records: comboProp[]; | records: comboProp[]; | ||||
} | } | ||||
export interface CreateDepartmentInputs { | export interface CreateDepartmentInputs { | ||||
departmentCode: string; | |||||
departmentName: string; | |||||
id: number; | |||||
code: string; | |||||
name: string; | |||||
description: string; | description: string; | ||||
} | } | ||||
@@ -25,7 +26,19 @@ export const saveDepartment = async (data: CreateDepartmentInputs) => { | |||||
body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
}); | }); | ||||
}; | |||||
}; | |||||
export const deleteDepartment = async (id: number) => { | |||||
const department = await serverFetchWithNoContent( | |||||
`${BASE_API_URL}/departments/${id}`, | |||||
{ | |||||
method: "DELETE", | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
return department | |||||
}; | |||||
export const fetchDepartmentCombo = cache(async () => { | export const fetchDepartmentCombo = cache(async () => { | ||||
@@ -2,6 +2,7 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||||
import { BASE_API_URL } from "@/config/api"; | import { BASE_API_URL } from "@/config/api"; | ||||
import { cache } from "react"; | import { cache } from "react"; | ||||
import "server-only"; | import "server-only"; | ||||
import { CreateDepartmentInputs } from "./actions"; | |||||
export interface DepartmentResult { | export interface DepartmentResult { | ||||
id: number; | id: number; | ||||
@@ -18,4 +19,13 @@ export const fetchDepartments = cache(async () => { | |||||
return serverFetchJson<DepartmentResult[]>(`${BASE_API_URL}/departments`, { | return serverFetchJson<DepartmentResult[]>(`${BASE_API_URL}/departments`, { | ||||
next: { tags: ["departments"] }, | next: { tags: ["departments"] }, | ||||
}); | }); | ||||
}); | |||||
export const fetchDepartmentDetails = cache(async (departmentId: string) => { | |||||
return serverFetchJson<CreateDepartmentInputs>( | |||||
`${BASE_API_URL}/departments/departmentDetails/${departmentId}`, | |||||
{ | |||||
next: { tags: [`departmentDetail${departmentId}`] }, | |||||
}, | |||||
); | |||||
}); | }); |
@@ -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 { cache } from "react"; | import { cache } from "react"; | ||||
import { PositionResult } from "."; | import { PositionResult } from "."; | ||||
@@ -15,15 +15,15 @@ export interface combo { | |||||
} | } | ||||
export interface CreatePositionInputs { | export interface CreatePositionInputs { | ||||
positionCode: string; | |||||
positionName: string; | |||||
code: string; | |||||
name: string; | |||||
description: string; | description: string; | ||||
} | } | ||||
export interface EditPositionInputs { | export interface EditPositionInputs { | ||||
id: number; | id: number; | ||||
positionCode: string; | |||||
positionName: string; | |||||
code: string; | |||||
name: string; | |||||
description: string; | description: string; | ||||
} | } | ||||
@@ -35,13 +35,25 @@ export const savePosition = async (data: CreatePositionInputs) => { | |||||
}); | }); | ||||
}; | }; | ||||
export const editPosition = async (data: EditPositionInputs) => { | |||||
return serverFetchJson(`${BASE_API_URL}/positions/new`, { | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
}; | |||||
export const editPosition = async (data: EditPositionInputs) => { | |||||
return serverFetchJson(`${BASE_API_URL}/positions/new`, { | |||||
method: "POST", | |||||
body: JSON.stringify(data), | |||||
headers: { "Content-Type": "application/json" }, | |||||
}); | |||||
}; | |||||
export const deletePosition = async (id: number) => { | |||||
const position = await serverFetchWithNoContent( | |||||
`${BASE_API_URL}/positions/${id}`, | |||||
{ | |||||
method: "DELETE", | |||||
headers: { "Content-Type": "application/json" }, | |||||
}, | |||||
); | |||||
return position | |||||
}; | |||||
export const fetchPositionCombo = cache(async () => { | export const fetchPositionCombo = cache(async () => { | ||||
return serverFetchJson<combo>(`${BASE_API_URL}/positions/combo`, { | return serverFetchJson<combo>(`${BASE_API_URL}/positions/combo`, { | ||||
@@ -22,18 +22,23 @@ import { Error } from "@mui/icons-material"; | |||||
import { ProjectCategory } from "@/app/api/projects"; | import { ProjectCategory } from "@/app/api/projects"; | ||||
import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
import DepartmentDetails from "./DepartmentDetails"; | import DepartmentDetails from "./DepartmentDetails"; | ||||
import { DepartmentResult } from "@/app/api/departments"; | |||||
interface Props { | |||||
isEdit: Boolean; | |||||
department?: CreateDepartmentInputs; | |||||
} | |||||
const CreateDepartment: React.FC = ({ | |||||
// allTasks, | |||||
// projectCategories, | |||||
// taskTemplates, | |||||
// teamLeads, | |||||
const CreateDepartment: React.FC<Props> = ({ | |||||
isEdit, | |||||
department, | |||||
}) => { | }) => { | ||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
console.log(department) | |||||
const handleCancel = () => { | const handleCancel = () => { | ||||
router.back(); | router.back(); | ||||
}; | }; | ||||
@@ -62,9 +67,10 @@ const CreateDepartment: React.FC = ({ | |||||
const formProps = useForm<CreateDepartmentInputs>({ | const formProps = useForm<CreateDepartmentInputs>({ | ||||
defaultValues: { | defaultValues: { | ||||
departmentCode: "", | |||||
departmentName: "", | |||||
description: "", | |||||
id: department?.id, | |||||
code: department?.code, | |||||
name: department?.name, | |||||
description: department?.description, | |||||
}, | }, | ||||
}); | }); | ||||
@@ -1,18 +1,24 @@ | |||||
import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | import { fetchAllTasks, fetchTaskTemplates } from "@/app/api/tasks"; | ||||
import CreateDepartment from "./CreateDepartment"; | import CreateDepartment from "./CreateDepartment"; | ||||
import { fetchTeamLeads } from "@/app/api/staff"; | import { fetchTeamLeads } from "@/app/api/staff"; | ||||
import { DepartmentResult, fetchDepartmentDetails } from "@/app/api/departments"; | |||||
const CreateDepartmentWrapper: React.FC = async () => { | |||||
// const [tasks, taskTemplates, DepartmentCategories, teamLeads] = | |||||
// await Promise.all([ | |||||
// fetchAllTasks(), | |||||
// fetchTaskTemplates(), | |||||
// fetchDepartmentCategories(), | |||||
// fetchTeamLeads(), | |||||
// ]); | |||||
type CreateDepartmentProps = { isEdit: false }; | |||||
interface EditDepartmentProps { | |||||
isEdit: true; | |||||
departmentId?: string; | |||||
} | |||||
type Props = CreateDepartmentProps | EditDepartmentProps; | |||||
const CreateDepartmentWrapper: React.FC<Props> = async (props) => { | |||||
const departmentInfo = props.isEdit | |||||
? await fetchDepartmentDetails(props.departmentId!) | |||||
: undefined; | |||||
return ( | return ( | ||||
<CreateDepartment | |||||
<CreateDepartment isEdit department={departmentInfo} | |||||
/> | /> | ||||
); | ); | ||||
}; | }; | ||||
@@ -39,20 +39,20 @@ const DepartmentDetails: React.FC = ({ | |||||
<TextField | <TextField | ||||
label={t("Department Code")} | label={t("Department Code")} | ||||
fullWidth | fullWidth | ||||
{...register("departmentCode", { | |||||
{...register("code", { | |||||
required: "Department code required!", | required: "Department code required!", | ||||
})} | })} | ||||
error={Boolean(errors.departmentCode)} | |||||
error={Boolean(errors.code)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("Department Name")} | label={t("Department Name")} | ||||
fullWidth | fullWidth | ||||
{...register("departmentName", { | |||||
{...register("name", { | |||||
required: "Department name required!", | required: "Department name required!", | ||||
})} | })} | ||||
error={Boolean(errors.departmentName)} | |||||
error={Boolean(errors.name)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
@@ -39,20 +39,20 @@ const PositionDetails: React.FC = ({ | |||||
<TextField | <TextField | ||||
label={t("Position Code")} | label={t("Position Code")} | ||||
fullWidth | fullWidth | ||||
{...register("positionCode", { | |||||
{...register("code", { | |||||
required: "Position code required!", | required: "Position code required!", | ||||
})} | })} | ||||
error={Boolean(errors.positionCode)} | |||||
error={Boolean(errors.code)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("Position Name")} | label={t("Position Name")} | ||||
fullWidth | fullWidth | ||||
{...register("positionName", { | |||||
{...register("name", { | |||||
required: "Position name required!", | required: "Position name required!", | ||||
})} | })} | ||||
error={Boolean(errors.positionName)} | |||||
error={Boolean(errors.name)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
@@ -5,8 +5,11 @@ 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 uniq from "lodash/uniq"; | |||||
import { DepartmentResult } from "@/app/api/departments"; | import { DepartmentResult } from "@/app/api/departments"; | ||||
import { useRouter } from "next/navigation"; | |||||
import DeleteIcon from '@mui/icons-material/Delete'; | |||||
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
import { deleteDepartment } from "@/app/api/departments/actions"; | |||||
interface Props { | interface Props { | ||||
departments: DepartmentResult[]; | departments: DepartmentResult[]; | ||||
@@ -17,6 +20,7 @@ type SearchParamNames = keyof SearchQuery; | |||||
const DepartmentSearch: React.FC<Props> = ({ departments }) => { | const DepartmentSearch: React.FC<Props> = ({ departments }) => { | ||||
const { t } = useTranslation("departments"); | const { t } = useTranslation("departments"); | ||||
const router = useRouter(); | |||||
const [filteredDepartments, setFilteredDepartments] = useState(departments); | const [filteredDepartments, setFilteredDepartments] = useState(departments); | ||||
@@ -33,8 +37,20 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||||
setFilteredDepartments(departments); | setFilteredDepartments(departments); | ||||
}, [departments]); | }, [departments]); | ||||
const onProjectClick = useCallback((project: DepartmentResult) => { | |||||
console.log(project); | |||||
const onProjectClick = useCallback((department: DepartmentResult) => { | |||||
console.log(department.id) | |||||
router.push(`/settings/department/edit?id=${department.id}`); | |||||
}, [router]); | |||||
const onDeleteClick = useCallback((department: DepartmentResult) => { | |||||
deleteDialog(async() => { | |||||
await deleteDepartment(department.id) | |||||
successDialog("Delete Success", t) | |||||
setFilteredDepartments((prev) => prev.filter((obj) => obj.id !== department.id)) | |||||
}, t) | |||||
}, []); | }, []); | ||||
const columns = useMemo<Column<DepartmentResult>[]>( | const columns = useMemo<Column<DepartmentResult>[]>( | ||||
@@ -48,6 +64,13 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||||
{ name: "code", label: t("Department Code") }, | { name: "code", label: t("Department Code") }, | ||||
{ name: "name", label: t("Department Name") }, | { name: "name", label: t("Department Name") }, | ||||
{ name: "description", label: t("Department Description") }, | { name: "description", label: t("Department Description") }, | ||||
{ | |||||
name: "id", | |||||
label: t("Delete"), | |||||
onClick: onDeleteClick, | |||||
buttonIcon: <DeleteIcon />, | |||||
color: "error" | |||||
}, | |||||
], | ], | ||||
[t, onProjectClick], | [t, onProjectClick], | ||||
); | ); | ||||
@@ -45,16 +45,8 @@ const EditPosition: React.FC = ({ | |||||
try{ | try{ | ||||
if (positionId !== null && parseInt(positionId) > 0) { | if (positionId !== null && parseInt(positionId) > 0) { | ||||
const postionDetails = await fetchPositionDetails(parseInt(positionId)) | const postionDetails = await fetchPositionDetails(parseInt(positionId)) | ||||
const updatedArray: EditPositionInputs[] = postionDetails.map((obj) => { | |||||
return { | |||||
id: obj.id, | |||||
positionCode: obj.code, | |||||
positionName: obj.name, | |||||
description: obj.description | |||||
}; | |||||
}); | |||||
setPositionDetails(updatedArray[0]) | |||||
setPositionDetails(postionDetails[0]) | |||||
} | } | ||||
} catch (error){ | } catch (error){ | ||||
console.log(error) | console.log(error) | ||||
@@ -46,20 +46,20 @@ const PositionDetails: React.FC<Props> = ({ | |||||
<TextField | <TextField | ||||
label={t("Position Code")} | label={t("Position Code")} | ||||
fullWidth | fullWidth | ||||
{...register("positionCode", { | |||||
{...register("code", { | |||||
required: "Position code required!", | required: "Position code required!", | ||||
})} | })} | ||||
error={Boolean(errors.positionCode)} | |||||
error={Boolean(errors.code)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
label={t("Position Name")} | label={t("Position Name")} | ||||
fullWidth | fullWidth | ||||
{...register("positionName", { | |||||
{...register("name", { | |||||
required: "Position name required!", | required: "Position name required!", | ||||
})} | })} | ||||
error={Boolean(errors.positionName)} | |||||
error={Boolean(errors.name)} | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
<Grid item xs={6}> | <Grid item xs={6}> | ||||
@@ -7,6 +7,9 @@ import SearchResults, { Column } from "../SearchResults"; | |||||
import EditNote from "@mui/icons-material/EditNote"; | import EditNote from "@mui/icons-material/EditNote"; | ||||
import { PositionResult } from "@/app/api/positions"; | import { PositionResult } from "@/app/api/positions"; | ||||
import { useRouter } from "next/navigation"; | import { useRouter } from "next/navigation"; | ||||
import DeleteIcon from '@mui/icons-material/Delete'; | |||||
import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; | |||||
import { deletePosition } from "@/app/api/positions/actions"; | |||||
interface Props { | interface Props { | ||||
positions: PositionResult[]; | positions: PositionResult[]; | ||||
@@ -34,12 +37,23 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||||
setFilteredPositions(positions); | setFilteredPositions(positions); | ||||
}, [positions]); | }, [positions]); | ||||
const onPositionClick = useCallback((project: PositionResult) => { | |||||
console.log(project); | |||||
const id = project.id | |||||
const onPositionClick = useCallback((position: PositionResult) => { | |||||
console.log(position); | |||||
const id = position.id | |||||
router.push(`/settings/position/edit?id=${id}`); | router.push(`/settings/position/edit?id=${id}`); | ||||
}, []); | }, []); | ||||
const onDeleteClick = useCallback((position: PositionResult) => { | |||||
deleteDialog(async() => { | |||||
await deletePosition(position.id) | |||||
successDialog("Delete Success", t) | |||||
setFilteredPositions((prev) => prev.filter((obj) => obj.id !== position.id)) | |||||
}, t) | |||||
}, []); | |||||
const columns = useMemo<Column<PositionResult>[]>( | const columns = useMemo<Column<PositionResult>[]>( | ||||
() => [ | () => [ | ||||
{ | { | ||||
@@ -51,6 +65,13 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||||
{ name: "code", label: t("Position Code") }, | { name: "code", label: t("Position Code") }, | ||||
{ name: "name", label: t("Position Name") }, | { name: "name", label: t("Position Name") }, | ||||
{ name: "description", label: t("Position Description") }, | { name: "description", label: t("Position Description") }, | ||||
{ | |||||
name: "id", | |||||
label: t("Delete"), | |||||
onClick: onDeleteClick, | |||||
buttonIcon: <DeleteIcon />, | |||||
color: "error" | |||||
}, | |||||
], | ], | ||||
[t, onPositionClick], | [t, onPositionClick], | ||||
); | ); | ||||