Skip to content

Commit

Permalink
Add user assigment in group page
Browse files Browse the repository at this point in the history
  • Loading branch information
r00tat committed Sep 28, 2024
1 parent 796775d commit 500f91d
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 38 deletions.
61 changes: 53 additions & 8 deletions src/app/groups/GroupAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,28 +23,73 @@ async function getGroups(): Promise<Group[]> {
);
}

export async function getGroupsFromServer(): Promise<Group[]> {
export async function getGroupsAction(): Promise<Group[]> {
await actionUserRequired();

return await getGroups();
}

async function updateGroup(group: Group) {
export async function updateGroupAction(group: Group, assigendUsers: string[]) {
await actionAdminRequired();

console.info(
`${group.id ? 'Updating' : 'Adding'} group ${group.name} ${group.id || ''}`
);

let groupId: string;

if (group.id) {
const doc = firestore.collection(GROUP_COLLECTION_ID).doc(group.id);
await doc.set(group, { merge: true });
return doc.id;

groupId = group.id;
} else {
// new doc
const result = await firestore.collection(GROUP_COLLECTION_ID).add(group);
return result.id;
groupId = result.id;
}
}

export async function updateGroupFromServer(group: Group) {
await actionAdminRequired();
const userCollection = firestore.collection(USER_COLLECTION_ID);
const users = (await userCollection.get()).docs || [];
// update assigned users
const batch = firestore.batch();

// users to remove
const removeUsers = users.filter(
(user) =>
(user.data().groups || []).includes(groupId) &&
!assigendUsers.includes(user.id)
);
console.info(
`removing ${removeUsers.length} from group ${group.name}: ${removeUsers.map(
(u) => u.data().displayName || u.data().email
)}`
);
removeUsers.forEach((user) =>
batch.update(userCollection.doc(user.id), {
groups: (user.data().groups as string[]).filter((id) => id !== groupId),
})
);

// users to add
const addUsers = users.filter(
(user) =>
!(user.data().groups || []).includes(groupId) &&
assigendUsers.includes(user.id)
);
console.info(
`adding ${addUsers.length} to group ${group.name}: ${addUsers.map(
(u) => u.data().displayName || u.data().email
)}`
);
addUsers.forEach((user) =>
batch.update(userCollection.doc(user.id), {
groups: [...((user.data().groups as string[]) || []), groupId],
})
);

return await updateGroup(group);
await batch.commit();
return groupId;
}

async function getMyGroups(userId: string): Promise<Group[]> {
Expand Down
81 changes: 77 additions & 4 deletions src/app/groups/GroupDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,60 @@ import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import { SelectChangeEvent } from '@mui/material/Select';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import TextField from '@mui/material/TextField';
import React, { useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { Group } from './GroupAction';
import { UserRecordExtended } from '../../common/users';
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Checkbox from '@mui/material/Checkbox';
import ListItemText from '@mui/material/ListItemText';
import OutlinedInput from '@mui/material/OutlinedInput';

export interface GroupDialoggOptions {
onClose: (item?: Group) => void;
onClose: (item?: Group, assigendUsers?: string[]) => void;
group: Group;
users: UserRecordExtended[];
}

export const groupTextFields: { [key: string]: string } = {
name: 'Name',
description: 'Zusatzinfo',
};

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 10 + ITEM_PADDING_TOP,
width: 250,
},
},
};

export default function GroupDialogg({
onClose,
group: groupDefault,
users,
}: GroupDialoggOptions) {
const [open, setOpen] = useState(true);
const [group, setGroup] = useState<Group>(groupDefault);
const [assigendUsers, setAssigendUsers] = useState<string[]>([]);

const userMap = Object.fromEntries(users.map((user) => [user.uid, user]));

useEffect(() => {
setAssigendUsers(
users
.filter(
(user) => group.id && user.groups && user.groups.includes(group.id)
)
.map((user) => user.uid)
);
}, [group.id, users]);

const onChange =
(field: string) =>
Expand Down Expand Up @@ -59,6 +92,46 @@ export default function GroupDialogg({
value={((group as any)[key] as string) || ''}
/>
))}

<FormControl fullWidth variant="standard">
<InputLabel id={`firecall-item-users-label`}>
Benutzer der Gruppe
</InputLabel>
<Select
labelId={`firecall-item-users-label`}
id={`firecall-item-users`}
multiple={true}
value={assigendUsers}
label="Assigned Users"
input={<OutlinedInput label="Assigned Users" />}
MenuProps={MenuProps}
renderValue={(selected) =>
selected
.map((key) => userMap[key])
.filter((v) => v)
.map((user) => `${user.displayName || user.email}`)
.join(', ')
}
onChange={(event) => {
const {
target: { value },
} = event;
// On autofill we get a stringified value.
setAssigendUsers(
typeof value === 'string' ? value.split(',') : value
);
}}
>
{users.map((user) => (
<MenuItem key={`user-for-group-${user.uid}`} value={user.uid}>
<Checkbox checked={assigendUsers.indexOf(user.uid) > -1} />
<ListItemText
primary={`${user.displayName || ''} (${user.email})`}
/>
</MenuItem>
))}
</Select>
</FormControl>
</DialogContent>
<DialogActions>
<Button
Expand All @@ -72,7 +145,7 @@ export default function GroupDialogg({
<Button
onClick={() => {
setOpen(false);
onClose(group);
onClose(group, assigendUsers);
}}
>
Aktualisieren
Expand Down
36 changes: 25 additions & 11 deletions src/app/groups/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
import AddIcon from '@mui/icons-material/Add';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';
import RefreshIcon from '@mui/icons-material/Refresh';
import Box from '@mui/material/Box';
import Fab from '@mui/material/Fab';
import Grid from '@mui/material/Grid';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import React, { useCallback, useEffect, useState } from 'react';
import { UserRecordExtended } from '../../common/users';
import ConfirmDialog from '../../components/dialogs/ConfirmDialog';
import { getUsers } from '../users/action';
import {
deleteGroupAction,
getGroupsFromServer,
getGroupsAction,
Group,
updateGroupFromServer,
updateGroupAction,
} from './GroupAction';
import GroupDialog from './GroupDialog';

Expand Down Expand Up @@ -53,26 +56,33 @@ function GroupRowButtons({ row, editFn, deleteFn }: UserRowButtonParams) {
);
}

function useGroupList(): [Group[], () => Promise<Group[]>] {
function useGroupList(): [
Group[],
() => Promise<Group[]>,
UserRecordExtended[]
] {
const [groups, setGroups] = useState<Group[]>([]);
const [users, setUsers] = useState<UserRecordExtended[]>([]);
const getGroups = useCallback(async () => {
const newGroups = await getGroupsFromServer();
const newGroups = await getGroupsAction();
const users = await getUsers();
setGroups(newGroups);
setUsers(users);
return newGroups;
}, []);

useEffect(() => {
getGroups();
}, [getGroups]);

return [groups, getGroups];
return [groups, getGroups, users];
}

export default function Users() {
export default function Groups() {
const [showEditDialog, setShowEditDialog] = useState(false);
const [isConfirmOpen, setIsConfirmOpen] = useState(false);
const [editGroup, setEditGroup] = useState<Group>();
const [groups, getGroups] = useGroupList();
const [groups, getGroups, users] = useGroupList();

const editAction = useCallback(async (group: Group) => {
setEditGroup(group);
Expand Down Expand Up @@ -101,6 +111,9 @@ export default function Users() {
<Box sx={{ p: 2, height: '70vh' }}>
<Typography variant="h3" gutterBottom>
Groups
<IconButton onClick={() => getGroups()}>
<RefreshIcon />
</IconButton>
</Typography>
<Grid container>
<Grid item xs={2} md={2} lg={2}></Grid>
Expand Down Expand Up @@ -149,12 +162,13 @@ export default function Users() {
{showEditDialog && editGroup && (
<GroupDialog
group={editGroup}
onClose={async (group) => {
users={users}
onClose={async (group, assigendUsers) => {
setShowEditDialog(false);
if (group) {
await updateGroupFromServer(group);
await getGroups();
if (group && assigendUsers) {
await updateGroupAction(group, assigendUsers);
}
await getGroups();
}}
/>
)}
Expand Down
13 changes: 6 additions & 7 deletions src/app/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@
import BlockIcon from '@mui/icons-material/Block';
import CheckIcon from '@mui/icons-material/Check';
import EditIcon from '@mui/icons-material/Edit';
import RefreshIcon from '@mui/icons-material/Refresh';
import Box from '@mui/material/Box';
import Grid from '@mui/material/Grid';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import Typography from '@mui/material/Typography';
import { green, red } from '@mui/material/colors';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { GridColDef } from '@mui/x-data-grid';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { feuerwehren } from '../../common/feuerwehren';
import { UserRecordExtended } from '../../common/users';
import UserRecordExtendedDialog from '../../components/users/UserDialog';
import useFirebaseCollection from '../../hooks/useFirebaseCollection';
import useUpdateUser from '../../hooks/useUpdateUser';
import useUserList from '../../hooks/useUserList';
import useFirebaseCollection from '../../hooks/useFirebaseCollection';
import { Group } from '../groups/GroupAction';
import Grid from '@mui/material/Grid';
import React from 'react';
import { Refresh } from '@mui/icons-material';

interface UserRowButtonParams {
row: UserRecordExtended;
Expand Down Expand Up @@ -155,7 +154,7 @@ export default function Users() {
<Typography variant="h3" gutterBottom>
Users{' '}
<IconButton onClick={() => fetchUsers()}>
<Refresh />
<RefreshIcon />
</IconButton>
</Typography>
<Grid container>
Expand Down
13 changes: 5 additions & 8 deletions src/components/users/UserDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Button from '@mui/material/Button';
import Checkbox from '@mui/material/Checkbox';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
Expand All @@ -8,26 +9,22 @@ import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormGroup from '@mui/material/FormGroup';
import InputLabel from '@mui/material/InputLabel';
import ListItemText from '@mui/material/ListItemText';
import MenuItem from '@mui/material/MenuItem';
import OutlinedInput from '@mui/material/OutlinedInput';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField';
import React, { useMemo, useState } from 'react';
import React, { useState } from 'react';
import { feuerwehren } from '../../common/feuerwehren';
import { UserRecordExtended, userTextFields } from '../../common/users';
import OutlinedInput from '@mui/material/OutlinedInput';
import Checkbox from '@mui/material/Checkbox';
import ListItemText from '@mui/material/ListItemText';
import { useFirecallId } from '../../hooks/useFirecall';
import useFirebaseCollection from '../../hooks/useFirebaseCollection';
import { Group } from '../../app/groups/GroupAction';

const ITEM_HEIGHT = 48;
const ITEM_PADDING_TOP = 8;
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
maxHeight: ITEM_HEIGHT * 10 + ITEM_PADDING_TOP,
width: 250,
},
},
Expand Down

0 comments on commit 500f91d

Please sign in to comment.