您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 

269 行
7.9 KiB

  1. "use client";
  2. import React, { useCallback, useEffect, useMemo, useState } from "react";
  3. import CustomInputForm from "../CustomInputForm";
  4. import { useRouter, useSearchParams } from "next/navigation";
  5. import { useTranslation } from "react-i18next";
  6. import {
  7. FieldErrors,
  8. FormProvider,
  9. SubmitErrorHandler,
  10. SubmitHandler,
  11. useForm,
  12. useFormContext,
  13. } from "react-hook-form";
  14. import { CreateTeamInputs } from "@/app/api/team/actions";
  15. import { Staff4TransferList, fetchStaffCombo } from "@/app/api/staff/actions";
  16. import { StaffResult, StaffTeamTable } from "@/app/api/staff";
  17. import SearchResults, { Column } from "../SearchResults";
  18. import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material";
  19. import {
  20. Card,
  21. Box,
  22. CardContent,
  23. Grid,
  24. IconButton,
  25. InputAdornment,
  26. Stack,
  27. Tab,
  28. Tabs,
  29. TabsProps,
  30. TextField,
  31. Typography,
  32. } from "@mui/material";
  33. import { differenceBy } from "lodash";
  34. import StarsIcon from "@mui/icons-material/Stars";
  35. export interface Props {
  36. allStaffs: StaffResult[];
  37. teamLead: number;
  38. }
  39. const Allocation: React.FC<Props> = ({ allStaffs: staff, teamLead }) => {
  40. const { t } = useTranslation();
  41. const searchParams = useSearchParams();
  42. const idString = searchParams.get("id");
  43. const {
  44. setValue,
  45. getValues,
  46. formState: { defaultValues },
  47. reset,
  48. resetField,
  49. } = useFormContext<CreateTeamInputs>();
  50. const initialStaffs = staff.map((s) => ({ ...s }));
  51. const [filteredStaff, setFilteredStaff] = useState(initialStaffs);
  52. const [selectedStaff, setSelectedStaff] = useState<typeof filteredStaff>(() => {
  53. const rearrangedStaff = filteredStaff.sort((a, b) => {
  54. if (a.id === teamLead) return -1;
  55. if (b.id === teamLead) return 1;
  56. return 0;
  57. });
  58. return rearrangedStaff.filter((s) => getValues("addStaffIds")?.includes(s.id))
  59. }
  60. );
  61. const [seletedTeamLead, setSeletedTeamLead] = useState<number>();
  62. const [deletedStaffIds, setDeletedStaffIds] = useState<number[]>([]);
  63. // Adding / Removing staff
  64. const addStaff = useCallback((staff: StaffResult) => {
  65. setSelectedStaff((s) => [...s, staff]);
  66. // setDeletedStaffIds((s) => s.filter((s) => s === selectedStaff.id))
  67. }, []);
  68. const removeStaff = useCallback((staff: StaffResult) => {
  69. setSelectedStaff((s) => s.filter((s) => s.id !== staff.id));
  70. // setDeletedStaffIds((s) => s)
  71. setDeletedStaffIds((prevIds) => [...prevIds, staff.id]);
  72. }, []);
  73. const setTeamLead = useCallback(
  74. (staff: StaffResult) => {
  75. setSeletedTeamLead(staff.id);
  76. const rearrangedList = getValues("addStaffIds").reduce<number[]>(
  77. (acc, num, index) => {
  78. if (num === staff.id && index !== 0) {
  79. acc.splice(index, 1);
  80. acc.unshift(num);
  81. }
  82. return acc;
  83. },
  84. getValues("addStaffIds")
  85. );
  86. // console.log(rearrangedList);
  87. // console.log(selectedStaff);
  88. const rearrangedStaff = rearrangedList.map((id) => {
  89. return selectedStaff.find((staff) => staff.id === id);
  90. });
  91. console.log(rearrangedStaff);
  92. setSelectedStaff(rearrangedStaff as StaffResult[]);
  93. setValue("addStaffIds", rearrangedList);
  94. },
  95. [addStaff, selectedStaff]
  96. );
  97. const clearSubsidiary = useCallback(() => {
  98. if (defaultValues !== undefined) {
  99. resetField("addStaffIds");
  100. setSelectedStaff(
  101. initialStaffs.filter((s) => defaultValues.addStaffIds?.includes(s.id))
  102. );
  103. }
  104. }, [defaultValues]);
  105. // Sync with form
  106. useEffect(() => {
  107. setValue(
  108. "addStaffIds",
  109. selectedStaff.map((s) => s.id)
  110. );
  111. }, [selectedStaff, setValue]);
  112. useEffect(() => {
  113. setValue("deleteStaffIds", deletedStaffIds)
  114. console.log(deletedStaffIds)
  115. }, [deletedStaffIds]);
  116. const StaffPoolColumns = useMemo<Column<StaffResult>[]>(
  117. () => [
  118. {
  119. label: t("Add"),
  120. name: "id",
  121. onClick: addStaff,
  122. buttonIcon: <PersonAdd />,
  123. },
  124. { label: t("Staff Id"), name: "staffId" },
  125. { label: t("Staff Name"), name: "name" },
  126. { label: t("Current Position"), name: "currentPosition" },
  127. ],
  128. [addStaff, t]
  129. );
  130. const allocatedStaffColumns = useMemo<Column<StaffResult>[]>(
  131. () => [
  132. {
  133. label: t("Remove"),
  134. name: "action",
  135. onClick: removeStaff,
  136. buttonIcon: <PersonRemove />,
  137. },
  138. { label: t("Staff Id"), name: "staffId" },
  139. { label: t("Staff Name"), name: "name" },
  140. { label: t("Current Position"), name: "currentPosition" },
  141. {
  142. label: t("Team Lead"),
  143. name: "action",
  144. onClick: setTeamLead,
  145. buttonIcon: <StarsIcon />,
  146. },
  147. ],
  148. [removeStaff, selectedStaff, t]
  149. );
  150. const [query, setQuery] = React.useState("");
  151. const onQueryInputChange = React.useCallback<
  152. React.ChangeEventHandler<HTMLInputElement>
  153. >((e) => {
  154. setQuery(e.target.value);
  155. }, []);
  156. const clearQueryInput = React.useCallback(() => {
  157. setQuery("");
  158. }, []);
  159. React.useEffect(() => {
  160. // setFilteredStaff(
  161. // initialStaffs.filter((s) => {
  162. // const q = query.toLowerCase();
  163. // // s.staffId.toLowerCase().includes(q)
  164. // // const q = query.toLowerCase();
  165. // // return s.name.toLowerCase().includes(q);
  166. // // s.code.toString().includes(q) ||
  167. // // (s.brNo != null && s.brNo.toLowerCase().includes(q))
  168. // })
  169. // );
  170. }, [staff, query]);
  171. useEffect(() => {
  172. // console.log(getValues("addStaffIds"))
  173. }, [initialStaffs]);
  174. const resetStaff = React.useCallback(() => {
  175. clearQueryInput();
  176. clearSubsidiary();
  177. }, [clearQueryInput, clearSubsidiary]);
  178. const formProps = useForm({});
  179. // Tab related
  180. const [tabIndex, setTabIndex] = React.useState(0);
  181. const handleTabChange = React.useCallback<NonNullable<TabsProps["onChange"]>>(
  182. (_e, newValue) => {
  183. setTabIndex(newValue);
  184. },
  185. []
  186. );
  187. return (
  188. <>
  189. <FormProvider {...formProps}>
  190. <Card sx={{ display: "block" }}>
  191. <CardContent
  192. sx={{ display: "flex", flexDirection: "column", gap: 1 }}
  193. >
  194. <Stack gap={2}>
  195. <Typography variant="overline" display="block">
  196. {t("staff")}
  197. </Typography>
  198. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  199. <Grid item xs={6} display="flex" alignItems="center">
  200. <Search sx={{ marginInlineEnd: 1 }} />
  201. <TextField
  202. variant="standard"
  203. fullWidth
  204. onChange={onQueryInputChange}
  205. value={query}
  206. placeholder={t("Search by staff ID, name or position.")}
  207. InputProps={{
  208. endAdornment: query && (
  209. <InputAdornment position="end">
  210. <IconButton onClick={clearQueryInput}>
  211. <Clear />
  212. </IconButton>
  213. </InputAdornment>
  214. ),
  215. }}
  216. />
  217. </Grid>
  218. </Grid>
  219. <Tabs value={tabIndex} onChange={handleTabChange}>
  220. <Tab label={t("Staff Pool")} />
  221. <Tab
  222. label={`${t("Allocated Staff")} (${selectedStaff.length})`}
  223. />
  224. </Tabs>
  225. <Box sx={{ marginInline: -3 }}>
  226. {tabIndex === 0 && (
  227. <SearchResults
  228. noWrapper
  229. items={differenceBy(filteredStaff, selectedStaff, "id")}
  230. columns={StaffPoolColumns}
  231. />
  232. )}
  233. {tabIndex === 1 && (
  234. <SearchResults
  235. noWrapper
  236. items={selectedStaff}
  237. columns={allocatedStaffColumns}
  238. />
  239. )}
  240. </Box>
  241. </Stack>
  242. </CardContent>
  243. </Card>
  244. </FormProvider>
  245. </>
  246. );
  247. };
  248. export default Allocation;