FPSMS-frontend
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 

225 Zeilen
6.1 KiB

  1. "use client";
  2. import React, { useCallback, useEffect, useMemo, useState } from "react";
  3. import { useRouter, useSearchParams } from "next/navigation";
  4. import {
  5. Add,
  6. Clear,
  7. PersonAdd,
  8. PersonRemove,
  9. Remove,
  10. Search,
  11. } from "@mui/icons-material";
  12. import { useTranslation } from "react-i18next";
  13. import {
  14. FieldErrors,
  15. FormProvider,
  16. SubmitErrorHandler,
  17. SubmitHandler,
  18. useForm,
  19. useFormContext,
  20. } from "react-hook-form";
  21. import {
  22. Box,
  23. Card,
  24. CardContent,
  25. Grid,
  26. IconButton,
  27. InputAdornment,
  28. Stack,
  29. Tab,
  30. Tabs,
  31. TabsProps,
  32. TextField,
  33. Typography,
  34. } from "@mui/material";
  35. import { differenceBy } from "lodash";
  36. import { UserInputs } from "@/app/api/user/actions";
  37. import { auth } from "@/app/api/group/actions";
  38. import SearchResults, { Column } from "../SearchResults";
  39. export interface Props {
  40. auths: auth[];
  41. }
  42. const AuthAllocation: React.FC<Props> = ({ auths }) => {
  43. const { t } = useTranslation();
  44. const searchParams = useSearchParams();
  45. const id = parseInt(searchParams.get("id") || "0");
  46. const {
  47. setValue,
  48. getValues,
  49. formState: { defaultValues },
  50. reset,
  51. resetField,
  52. } = useFormContext<UserInputs>();
  53. const initialAuths = auths.map((u) => ({ ...u })).sort((a, b) => a.id - b.id);
  54. const [filteredAuths, setFilteredAuths] = useState(initialAuths);
  55. const [selectedAuths, setSelectedAuths] = useState<typeof filteredAuths>(
  56. () => {
  57. return filteredAuths.filter(
  58. (s) => getValues("addAuthIds")?.includes(s.id),
  59. );
  60. },
  61. );
  62. const [removeAuthIds, setRemoveAuthIds] = useState<number[]>([]);
  63. // Adding / Removing Auth
  64. const addAuth = useCallback((auth: auth) => {
  65. setSelectedAuths((a) => [...a, auth]);
  66. }, []);
  67. const removeAuth = useCallback((auth: auth) => {
  68. setSelectedAuths((a) => a.filter((a) => a.id !== auth.id));
  69. setRemoveAuthIds((prevIds) => [...prevIds, auth.id]);
  70. }, []);
  71. const clearAuth = useCallback(() => {
  72. if (defaultValues !== undefined) {
  73. resetField("addAuthIds");
  74. setSelectedAuths(
  75. initialAuths.filter(
  76. (auth) => defaultValues.addAuthIds?.includes(auth.id),
  77. ),
  78. );
  79. }
  80. }, [defaultValues]);
  81. // Sync with form
  82. useEffect(() => {
  83. setValue(
  84. "addAuthIds",
  85. selectedAuths.map((a) => a.id),
  86. );
  87. setValue("removeAuthIds", removeAuthIds);
  88. }, [selectedAuths, removeAuthIds, setValue]);
  89. const AuthPoolColumns = useMemo<Column<auth>[]>(
  90. () => [
  91. {
  92. label: t("Add"),
  93. name: "id",
  94. onClick: addAuth,
  95. buttonIcon: <Add />,
  96. },
  97. { label: t("authority"), name: "authority" },
  98. { label: t("description"), name: "name" },
  99. ],
  100. [addAuth, t],
  101. );
  102. const allocatedAuthColumns = useMemo<Column<auth>[]>(
  103. () => [
  104. {
  105. label: t("Remove"),
  106. name: "id",
  107. onClick: removeAuth,
  108. buttonIcon: <Remove color="warning" />,
  109. },
  110. { label: t("authority"), name: "authority" },
  111. { label: t("description"), name: "name" },
  112. ],
  113. [removeAuth, selectedAuths, t],
  114. );
  115. const [query, setQuery] = React.useState("");
  116. const onQueryInputChange = React.useCallback<
  117. React.ChangeEventHandler<HTMLInputElement>
  118. >((e) => {
  119. setQuery(e.target.value);
  120. }, []);
  121. const clearQueryInput = React.useCallback(() => {
  122. setQuery("");
  123. }, []);
  124. React.useEffect(() => {
  125. setFilteredAuths(
  126. initialAuths.filter(
  127. (a) =>
  128. a.authority.toLowerCase().includes(query.toLowerCase()) ||
  129. a.name?.toLowerCase().includes(query.toLowerCase()),
  130. ),
  131. );
  132. }, [auths, query]);
  133. const resetAuth = React.useCallback(() => {
  134. clearQueryInput();
  135. clearAuth();
  136. }, [clearQueryInput, clearAuth]);
  137. const formProps = useForm({});
  138. // Tab related
  139. const [tabIndex, setTabIndex] = React.useState(0);
  140. const handleTabChange = React.useCallback<NonNullable<TabsProps["onChange"]>>(
  141. (_e, newValue) => {
  142. setTabIndex(newValue);
  143. },
  144. [],
  145. );
  146. return (
  147. <>
  148. <FormProvider {...formProps}>
  149. <Card sx={{ display: "block" }}>
  150. <CardContent
  151. sx={{ display: "flex", flexDirection: "column", gap: 1 }}
  152. >
  153. <Stack gap={2}>
  154. <Typography variant="overline" display="block">
  155. {t("Authority")}
  156. </Typography>
  157. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  158. <Grid item xs={6} display="flex" alignItems="center">
  159. <Search sx={{ marginInlineEnd: 1 }} />
  160. <TextField
  161. variant="standard"
  162. fullWidth
  163. onChange={onQueryInputChange}
  164. value={query}
  165. placeholder={t(
  166. "Search by Authority or description or position.",
  167. )}
  168. InputProps={{
  169. endAdornment: query && (
  170. <InputAdornment position="end">
  171. <IconButton onClick={clearQueryInput}>
  172. <Clear />
  173. </IconButton>
  174. </InputAdornment>
  175. ),
  176. }}
  177. />
  178. </Grid>
  179. </Grid>
  180. <Tabs value={tabIndex} onChange={handleTabChange}>
  181. <Tab label={t("Authority Pool")} />
  182. <Tab
  183. label={`${t("Allocated Authority")} (${
  184. selectedAuths.length
  185. })`}
  186. />
  187. </Tabs>
  188. <Box sx={{ marginInline: -3 }}>
  189. {tabIndex === 0 && (
  190. <SearchResults
  191. noWrapper
  192. items={differenceBy(filteredAuths, selectedAuths, "id")}
  193. columns={AuthPoolColumns}
  194. />
  195. )}
  196. {tabIndex === 1 && (
  197. <SearchResults
  198. noWrapper
  199. items={selectedAuths}
  200. columns={allocatedAuthColumns}
  201. />
  202. )}
  203. </Box>
  204. </Stack>
  205. </CardContent>
  206. </Card>
  207. </FormProvider>
  208. </>
  209. );
  210. };
  211. export default AuthAllocation;