FPSMS-frontend
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

226 linhas
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((a) =>
  127. (
  128. a.authority.toLowerCase().includes(query.toLowerCase()) ||
  129. a.name?.toLowerCase().includes(query.toLowerCase())
  130. )
  131. )
  132. );
  133. }, [auths, query]);
  134. const resetAuth = React.useCallback(() => {
  135. clearQueryInput();
  136. clearAuth();
  137. }, [clearQueryInput, clearAuth]);
  138. const formProps = useForm({});
  139. // Tab related
  140. const [tabIndex, setTabIndex] = React.useState(0);
  141. const handleTabChange = React.useCallback<NonNullable<TabsProps["onChange"]>>(
  142. (_e, newValue) => {
  143. setTabIndex(newValue);
  144. },
  145. []
  146. );
  147. return (
  148. <>
  149. <FormProvider {...formProps}>
  150. <Card sx={{ display: "block" }}>
  151. <CardContent
  152. sx={{ display: "flex", flexDirection: "column", gap: 1 }}
  153. >
  154. <Stack gap={2}>
  155. <Typography variant="overline" display="block">
  156. {t("Authority")}
  157. </Typography>
  158. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  159. <Grid item xs={6} display="flex" alignItems="center">
  160. <Search sx={{ marginInlineEnd: 1 }} />
  161. <TextField
  162. variant="standard"
  163. fullWidth
  164. onChange={onQueryInputChange}
  165. value={query}
  166. placeholder={t(
  167. "Search by Authority or description or position."
  168. )}
  169. InputProps={{
  170. endAdornment: query && (
  171. <InputAdornment position="end">
  172. <IconButton onClick={clearQueryInput}>
  173. <Clear />
  174. </IconButton>
  175. </InputAdornment>
  176. ),
  177. }}
  178. />
  179. </Grid>
  180. </Grid>
  181. <Tabs value={tabIndex} onChange={handleTabChange}>
  182. <Tab label={t("Authority Pool")} />
  183. <Tab
  184. label={`${t("Allocated Authority")} (${
  185. selectedAuths.length
  186. })`}
  187. />
  188. </Tabs>
  189. <Box sx={{ marginInline: -3 }}>
  190. {tabIndex === 0 && (
  191. <SearchResults
  192. noWrapper
  193. items={differenceBy(filteredAuths, selectedAuths, "id")}
  194. columns={AuthPoolColumns}
  195. />
  196. )}
  197. {tabIndex === 1 && (
  198. <SearchResults
  199. noWrapper
  200. items={selectedAuths}
  201. columns={allocatedAuthColumns}
  202. />
  203. )}
  204. </Box>
  205. </Stack>
  206. </CardContent>
  207. </Card>
  208. </FormProvider>
  209. </>
  210. );
  211. };
  212. export default AuthAllocation;