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> | |||
<I18nProvider namespaces={["departments"]}> | |||
<CreateDepartment /> | |||
<CreateDepartment isEdit={false} /> | |||
</I18nProvider> | |||
</> | |||
); | |||
@@ -1,6 +1,6 @@ | |||
"use server" | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { cache } from "react"; | |||
@@ -14,8 +14,9 @@ export interface combo { | |||
records: comboProp[]; | |||
} | |||
export interface CreateDepartmentInputs { | |||
departmentCode: string; | |||
departmentName: string; | |||
id: number; | |||
code: string; | |||
name: string; | |||
description: string; | |||
} | |||
@@ -25,7 +26,19 @@ export const saveDepartment = async (data: CreateDepartmentInputs) => { | |||
body: JSON.stringify(data), | |||
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 () => { | |||
@@ -2,6 +2,7 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { cache } from "react"; | |||
import "server-only"; | |||
import { CreateDepartmentInputs } from "./actions"; | |||
export interface DepartmentResult { | |||
id: number; | |||
@@ -18,4 +19,13 @@ export const fetchDepartments = cache(async () => { | |||
return serverFetchJson<DepartmentResult[]>(`${BASE_API_URL}/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" | |||
import { serverFetchJson } from "@/app/utils/fetchUtil"; | |||
import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; | |||
import { BASE_API_URL } from "@/config/api"; | |||
import { cache } from "react"; | |||
import { PositionResult } from "."; | |||
@@ -15,15 +15,15 @@ export interface combo { | |||
} | |||
export interface CreatePositionInputs { | |||
positionCode: string; | |||
positionName: string; | |||
code: string; | |||
name: string; | |||
description: string; | |||
} | |||
export interface EditPositionInputs { | |||
id: number; | |||
positionCode: string; | |||
positionName: string; | |||
code: string; | |||
name: 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 () => { | |||
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 { Typography } from "@mui/material"; | |||
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 { t } = useTranslation(); | |||
const router = useRouter(); | |||
console.log(department) | |||
const handleCancel = () => { | |||
router.back(); | |||
}; | |||
@@ -62,9 +67,10 @@ const CreateDepartment: React.FC = ({ | |||
const formProps = useForm<CreateDepartmentInputs>({ | |||
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 CreateDepartment from "./CreateDepartment"; | |||
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 ( | |||
<CreateDepartment | |||
<CreateDepartment isEdit department={departmentInfo} | |||
/> | |||
); | |||
}; | |||
@@ -39,20 +39,20 @@ const DepartmentDetails: React.FC = ({ | |||
<TextField | |||
label={t("Department Code")} | |||
fullWidth | |||
{...register("departmentCode", { | |||
{...register("code", { | |||
required: "Department code required!", | |||
})} | |||
error={Boolean(errors.departmentCode)} | |||
error={Boolean(errors.code)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Department Name")} | |||
fullWidth | |||
{...register("departmentName", { | |||
{...register("name", { | |||
required: "Department name required!", | |||
})} | |||
error={Boolean(errors.departmentName)} | |||
error={Boolean(errors.name)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
@@ -39,20 +39,20 @@ const PositionDetails: React.FC = ({ | |||
<TextField | |||
label={t("Position Code")} | |||
fullWidth | |||
{...register("positionCode", { | |||
{...register("code", { | |||
required: "Position code required!", | |||
})} | |||
error={Boolean(errors.positionCode)} | |||
error={Boolean(errors.code)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Position Name")} | |||
fullWidth | |||
{...register("positionName", { | |||
{...register("name", { | |||
required: "Position name required!", | |||
})} | |||
error={Boolean(errors.positionName)} | |||
error={Boolean(errors.name)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
@@ -5,8 +5,11 @@ import SearchBox, { Criterion } from "../SearchBox"; | |||
import { useTranslation } from "react-i18next"; | |||
import SearchResults, { Column } from "../SearchResults"; | |||
import EditNote from "@mui/icons-material/EditNote"; | |||
import uniq from "lodash/uniq"; | |||
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 { | |||
departments: DepartmentResult[]; | |||
@@ -17,6 +20,7 @@ type SearchParamNames = keyof SearchQuery; | |||
const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
const { t } = useTranslation("departments"); | |||
const router = useRouter(); | |||
const [filteredDepartments, setFilteredDepartments] = useState(departments); | |||
@@ -33,8 +37,20 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
setFilteredDepartments(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>[]>( | |||
@@ -48,6 +64,13 @@ const DepartmentSearch: React.FC<Props> = ({ departments }) => { | |||
{ name: "code", label: t("Department Code") }, | |||
{ name: "name", label: t("Department Name") }, | |||
{ name: "description", label: t("Department Description") }, | |||
{ | |||
name: "id", | |||
label: t("Delete"), | |||
onClick: onDeleteClick, | |||
buttonIcon: <DeleteIcon />, | |||
color: "error" | |||
}, | |||
], | |||
[t, onProjectClick], | |||
); | |||
@@ -45,16 +45,8 @@ const EditPosition: React.FC = ({ | |||
try{ | |||
if (positionId !== null && parseInt(positionId) > 0) { | |||
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){ | |||
console.log(error) | |||
@@ -46,20 +46,20 @@ const PositionDetails: React.FC<Props> = ({ | |||
<TextField | |||
label={t("Position Code")} | |||
fullWidth | |||
{...register("positionCode", { | |||
{...register("code", { | |||
required: "Position code required!", | |||
})} | |||
error={Boolean(errors.positionCode)} | |||
error={Boolean(errors.code)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
<TextField | |||
label={t("Position Name")} | |||
fullWidth | |||
{...register("positionName", { | |||
{...register("name", { | |||
required: "Position name required!", | |||
})} | |||
error={Boolean(errors.positionName)} | |||
error={Boolean(errors.name)} | |||
/> | |||
</Grid> | |||
<Grid item xs={6}> | |||
@@ -7,6 +7,9 @@ import SearchResults, { Column } from "../SearchResults"; | |||
import EditNote from "@mui/icons-material/EditNote"; | |||
import { PositionResult } from "@/app/api/positions"; | |||
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 { | |||
positions: PositionResult[]; | |||
@@ -34,12 +37,23 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||
setFilteredPositions(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}`); | |||
}, []); | |||
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>[]>( | |||
() => [ | |||
{ | |||
@@ -51,6 +65,13 @@ const PositionSearch: React.FC<Props> = ({ positions }) => { | |||
{ name: "code", label: t("Position Code") }, | |||
{ name: "name", label: t("Position Name") }, | |||
{ name: "description", label: t("Position Description") }, | |||
{ | |||
name: "id", | |||
label: t("Delete"), | |||
onClick: onDeleteClick, | |||
buttonIcon: <DeleteIcon />, | |||
color: "error" | |||
}, | |||
], | |||
[t, onPositionClick], | |||
); | |||