Display the list of family users
This commit is contained in:
		
							
								
								
									
										31
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								geneit_app/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -16,6 +16,7 @@
 | 
				
			|||||||
        "@mdi/react": "^1.6.1",
 | 
					        "@mdi/react": "^1.6.1",
 | 
				
			||||||
        "@mui/icons-material": "^5.11.16",
 | 
					        "@mui/icons-material": "^5.11.16",
 | 
				
			||||||
        "@mui/material": "^5.13.4",
 | 
					        "@mui/material": "^5.13.4",
 | 
				
			||||||
 | 
					        "@mui/x-data-grid": "^6.9.2",
 | 
				
			||||||
        "@testing-library/jest-dom": "^5.16.5",
 | 
					        "@testing-library/jest-dom": "^5.16.5",
 | 
				
			||||||
        "@testing-library/react": "^13.4.0",
 | 
					        "@testing-library/react": "^13.4.0",
 | 
				
			||||||
        "@testing-library/user-event": "^13.5.0",
 | 
					        "@testing-library/user-event": "^13.5.0",
 | 
				
			||||||
@@ -3266,6 +3267,31 @@
 | 
				
			|||||||
        "react": "^17.0.0 || ^18.0.0"
 | 
					        "react": "^17.0.0 || ^18.0.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/@mui/x-data-grid": {
 | 
				
			||||||
 | 
					      "version": "6.9.2",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-6.9.2.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-hrjVq3FrbUpioi2GYSWJtU4NR3V4bPwLbXngw07+I21TGOWV1TKeTslkPI+FGVYU3gMjGSSJRFN8gehPlh5Evw==",
 | 
				
			||||||
 | 
					      "dependencies": {
 | 
				
			||||||
 | 
					        "@babel/runtime": "^7.22.5",
 | 
				
			||||||
 | 
					        "@mui/utils": "^5.13.6",
 | 
				
			||||||
 | 
					        "clsx": "^1.2.1",
 | 
				
			||||||
 | 
					        "prop-types": "^15.8.1",
 | 
				
			||||||
 | 
					        "reselect": "^4.1.8"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "engines": {
 | 
				
			||||||
 | 
					        "node": ">=14.0.0"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "funding": {
 | 
				
			||||||
 | 
					        "type": "opencollective",
 | 
				
			||||||
 | 
					        "url": "https://opencollective.com/mui"
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "peerDependencies": {
 | 
				
			||||||
 | 
					        "@mui/material": "^5.4.1",
 | 
				
			||||||
 | 
					        "@mui/system": "^5.4.1",
 | 
				
			||||||
 | 
					        "react": "^17.0.0 || ^18.0.0",
 | 
				
			||||||
 | 
					        "react-dom": "^17.0.0 || ^18.0.0"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
 | 
					    "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
 | 
				
			||||||
      "version": "5.1.1-v1",
 | 
					      "version": "5.1.1-v1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
 | 
				
			||||||
@@ -13217,6 +13243,11 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
 | 
				
			||||||
      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
 | 
					      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/reselect": {
 | 
				
			||||||
 | 
					      "version": "4.1.8",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/resolve": {
 | 
					    "node_modules/resolve": {
 | 
				
			||||||
      "version": "1.22.2",
 | 
					      "version": "1.22.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,7 @@
 | 
				
			|||||||
    "@mdi/react": "^1.6.1",
 | 
					    "@mdi/react": "^1.6.1",
 | 
				
			||||||
    "@mui/icons-material": "^5.11.16",
 | 
					    "@mui/icons-material": "^5.11.16",
 | 
				
			||||||
    "@mui/material": "^5.13.4",
 | 
					    "@mui/material": "^5.13.4",
 | 
				
			||||||
 | 
					    "@mui/x-data-grid": "^6.9.2",
 | 
				
			||||||
    "@testing-library/jest-dom": "^5.16.5",
 | 
					    "@testing-library/jest-dom": "^5.16.5",
 | 
				
			||||||
    "@testing-library/react": "^13.4.0",
 | 
					    "@testing-library/react": "^13.4.0",
 | 
				
			||||||
    "@testing-library/user-event": "^13.5.0",
 | 
					    "@testing-library/user-event": "^13.5.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,7 @@ import { FamilyHomeRoute } from "./routes/family/FamilyHomeRoute";
 | 
				
			|||||||
import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
					import { BaseAuthenticatedPage } from "./widgets/BaseAuthenticatedPage";
 | 
				
			||||||
import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
					import { BaseFamilyRoute } from "./widgets/BaseFamilyRoute";
 | 
				
			||||||
import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
					import { BaseLoginPage } from "./widgets/BaseLoginpage";
 | 
				
			||||||
 | 
					import { FamilyUsersListRoute } from "./routes/family/FamilyUsersListRoute";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface AuthContext {
 | 
					interface AuthContext {
 | 
				
			||||||
  signedIn: boolean;
 | 
					  signedIn: boolean;
 | 
				
			||||||
@@ -45,6 +46,7 @@ export function App(): React.ReactElement {
 | 
				
			|||||||
            <Route path="profile" element={<ProfileRoute />} />
 | 
					            <Route path="profile" element={<ProfileRoute />} />
 | 
				
			||||||
            <Route path="family/:familyId/*" element={<BaseFamilyRoute />}>
 | 
					            <Route path="family/:familyId/*" element={<BaseFamilyRoute />}>
 | 
				
			||||||
              <Route path="" element={<FamilyHomeRoute />} />
 | 
					              <Route path="" element={<FamilyHomeRoute />} />
 | 
				
			||||||
 | 
					              <Route path="users" element={<FamilyUsersListRoute />} />
 | 
				
			||||||
              <Route path="*" element={<NotFoundRoute />} />
 | 
					              <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
            </Route>
 | 
					            </Route>
 | 
				
			||||||
            <Route path="*" element={<NotFoundRoute />} />
 | 
					            <Route path="*" element={<NotFoundRoute />} />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,7 +26,7 @@ export class APIClient {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  static async exec(args: {
 | 
					  static async exec(args: {
 | 
				
			||||||
    uri: string;
 | 
					    uri: string;
 | 
				
			||||||
    method: "GET" | "POST" | "DELETE";
 | 
					    method: "GET" | "POST" | "DELETE" | "PATCH";
 | 
				
			||||||
    allowFail?: boolean;
 | 
					    allowFail?: boolean;
 | 
				
			||||||
    jsonData?: any;
 | 
					    jsonData?: any;
 | 
				
			||||||
  }): Promise<APIResponse> {
 | 
					  }): Promise<APIResponse> {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,6 +62,15 @@ export enum JoinFamilyResult {
 | 
				
			|||||||
  Success,
 | 
					  Success,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface FamilyUser {
 | 
				
			||||||
 | 
					  user_id: number;
 | 
				
			||||||
 | 
					  family_id: number;
 | 
				
			||||||
 | 
					  time_create: number;
 | 
				
			||||||
 | 
					  is_admin: boolean;
 | 
				
			||||||
 | 
					  user_name: string;
 | 
				
			||||||
 | 
					  user_mail: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class FamilyApi {
 | 
					export class FamilyApi {
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Create a new family
 | 
					   * Create a new family
 | 
				
			||||||
@@ -142,4 +151,29 @@ export class FamilyApi {
 | 
				
			|||||||
      uri: `/family/${id}/renew_invitation_code`,
 | 
					      uri: `/family/${id}/renew_invitation_code`,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Get the users of a family
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async GetUsersList(id: number): Promise<FamilyUser[]> {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      await APIClient.exec({
 | 
				
			||||||
 | 
					        method: "GET",
 | 
				
			||||||
 | 
					        uri: `/family/${id}/users`,
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ).data;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Update a user of the family
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  static async UpdateUser(user: FamilyUser): Promise<void> {
 | 
				
			||||||
 | 
					    await APIClient.exec({
 | 
				
			||||||
 | 
					      method: "PATCH",
 | 
				
			||||||
 | 
					      uri: `/family/${user.family_id}/user/${user.user_id}`,
 | 
				
			||||||
 | 
					      jsonData: {
 | 
				
			||||||
 | 
					        is_admin: user.is_admin,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,27 @@
 | 
				
			|||||||
import { ThemeProvider, createTheme } from "@mui/material/styles";
 | 
					import { ThemeProvider, createTheme } from "@mui/material/styles";
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import { PropsWithChildren } from "react";
 | 
					import { PropsWithChildren } from "react";
 | 
				
			||||||
 | 
					import { frFR as dataGridFr } from "@mui/x-data-grid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const localStorageKey = "dark-theme";
 | 
					const localStorageKey = "dark-theme";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const darkTheme = createTheme({
 | 
					const darkTheme = createTheme(
 | 
				
			||||||
  palette: {
 | 
					  {
 | 
				
			||||||
    mode: "dark",
 | 
					    palette: {
 | 
				
			||||||
 | 
					      mode: "dark",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					  dataGridFr
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const lightTheme = createTheme({
 | 
					const lightTheme = createTheme(
 | 
				
			||||||
  palette: {
 | 
					  {
 | 
				
			||||||
    mode: "light",
 | 
					    palette: {
 | 
				
			||||||
 | 
					      mode: "light",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
});
 | 
					  dataGridFr
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface DarkThemeContext {
 | 
					interface DarkThemeContext {
 | 
				
			||||||
  enabled: boolean;
 | 
					  enabled: boolean;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										74
									
								
								geneit_app/src/routes/family/FamilyUsersListRoute.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								geneit_app/src/routes/family/FamilyUsersListRoute.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					import { DataGrid, GridColDef } from "@mui/x-data-grid";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import { FamilyApi, FamilyUser } from "../../api/FamilyApi";
 | 
				
			||||||
 | 
					import { useAlert } from "../../context_providers/AlertDialogProvider";
 | 
				
			||||||
 | 
					import { AsyncWidget } from "../../widgets/AsyncWidget";
 | 
				
			||||||
 | 
					import { useUser } from "../../widgets/BaseAuthenticatedPage";
 | 
				
			||||||
 | 
					import { useFamily } from "../../widgets/BaseFamilyRoute";
 | 
				
			||||||
 | 
					import { FamilyPageTitle } from "../../widgets/FamilyPageTitle";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function FamilyUsersListRoute(): React.ReactElement {
 | 
				
			||||||
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const [users, setUsers] = React.useState<FamilyUser[] | null>(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const key = React.useRef(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const load = async () => {
 | 
				
			||||||
 | 
					    setUsers(await FamilyApi.GetUsersList(family.family.family_id));
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <AsyncWidget
 | 
				
			||||||
 | 
					      loadKey={`${family.family.family_id}-${key.current}`}
 | 
				
			||||||
 | 
					      errMsg="Echec du chargement de la liste des utilisateurs de la famille !"
 | 
				
			||||||
 | 
					      load={load}
 | 
				
			||||||
 | 
					      ready={users !== null}
 | 
				
			||||||
 | 
					      build={() => (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					          <FamilyPageTitle title="Utilisateurs de la famille" />
 | 
				
			||||||
 | 
					          <UsersTable users={users!} />
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function UsersTable(p: { users: FamilyUser[] }): React.ReactElement {
 | 
				
			||||||
 | 
					  const alert = useAlert();
 | 
				
			||||||
 | 
					  const user = useUser();
 | 
				
			||||||
 | 
					  const family = useFamily();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const columns: GridColDef[] = [
 | 
				
			||||||
 | 
					    { field: "user_id", headerName: "#", flex: 1 },
 | 
				
			||||||
 | 
					    { field: "user_mail", headerName: "Adresse mail", flex: 5 },
 | 
				
			||||||
 | 
					    { field: "user_name", headerName: "Nom d'utilisateur", flex: 5 },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      field: "is_admin",
 | 
				
			||||||
 | 
					      headerName: "Admin",
 | 
				
			||||||
 | 
					      flex: 2,
 | 
				
			||||||
 | 
					      type: "boolean",
 | 
				
			||||||
 | 
					      editable: family.family.is_admin,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <DataGrid
 | 
				
			||||||
 | 
					      style={{ flex: "1" }}
 | 
				
			||||||
 | 
					      rows={p.users}
 | 
				
			||||||
 | 
					      columns={columns}
 | 
				
			||||||
 | 
					      autoPageSize
 | 
				
			||||||
 | 
					      editMode="cell"
 | 
				
			||||||
 | 
					      getRowId={(c) => c.user_id}
 | 
				
			||||||
 | 
					      isCellEditable={(params) => params.row.user_id !== user.user.id}
 | 
				
			||||||
 | 
					      processRowUpdate={async (n: FamilyUser) => {
 | 
				
			||||||
 | 
					        await FamilyApi.UpdateUser(n);
 | 
				
			||||||
 | 
					        return n;
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      onProcessRowUpdateError={(e) => {
 | 
				
			||||||
 | 
					        console.error(e);
 | 
				
			||||||
 | 
					        alert.alert("Échec de la mise à jour des informations utilisateurs !");
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -184,6 +184,8 @@ export function BaseFamilyRoute(): React.ReactElement {
 | 
				
			|||||||
                flexGrow: 1,
 | 
					                flexGrow: 1,
 | 
				
			||||||
                overflow: "auto",
 | 
					                overflow: "auto",
 | 
				
			||||||
                padding: "20px",
 | 
					                padding: "20px",
 | 
				
			||||||
 | 
					                display: "flex",
 | 
				
			||||||
 | 
					                flexDirection: "column",
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              <Outlet />
 | 
					              <Outlet />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								geneit_app/src/widgets/FamilyPageTitle.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								geneit_app/src/widgets/FamilyPageTitle.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					import { Typography } from "@mui/material";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function FamilyPageTitle(p: { title: string }): React.ReactElement {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Typography variant="h4" style={{ margin: "20px" }}>
 | 
				
			||||||
 | 
					      {p.title}
 | 
				
			||||||
 | 
					    </Typography>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -18,7 +18,7 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
            .wrap(
 | 
					            .wrap(
 | 
				
			||||||
                Cors::default()
 | 
					                Cors::default()
 | 
				
			||||||
                    .allowed_origin(&AppConfig::get().website_origin)
 | 
					                    .allowed_origin(&AppConfig::get().website_origin)
 | 
				
			||||||
                    .allowed_methods(vec!["GET", "POST"])
 | 
					                    .allowed_methods(vec!["GET", "POST", "PATCH", "DELETE"])
 | 
				
			||||||
                    .allowed_header("X-Auth-Token")
 | 
					                    .allowed_header("X-Auth-Token")
 | 
				
			||||||
                    .allow_any_header()
 | 
					                    .allow_any_header()
 | 
				
			||||||
                    .supports_credentials()
 | 
					                    .supports_credentials()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,8 +44,4 @@ diesel::table! {
 | 
				
			|||||||
diesel::joinable!(memberships -> families (family_id));
 | 
					diesel::joinable!(memberships -> families (family_id));
 | 
				
			||||||
diesel::joinable!(memberships -> users (user_id));
 | 
					diesel::joinable!(memberships -> users (user_id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
diesel::allow_tables_to_appear_in_same_query!(
 | 
					diesel::allow_tables_to_appear_in_same_query!(families, memberships, users,);
 | 
				
			||||||
    families,
 | 
					 | 
				
			||||||
    memberships,
 | 
					 | 
				
			||||||
    users,
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user