diff --git a/src/components/Admin/StudentGroupPanel/index.tsx b/src/components/Admin/StudentGroupPanel/index.tsx index 12ed680bc..5879ca834 100644 --- a/src/components/Admin/StudentGroupPanel/index.tsx +++ b/src/components/Admin/StudentGroupPanel/index.tsx @@ -4,41 +4,126 @@ import styles from './styles.module.scss'; import { observer } from 'mobx-react-lite'; import { useStore } from '@tdev-hooks/useStore'; import Button from '@tdev-components/shared/Button'; -import { mdiPlusCircleOutline } from '@mdi/js'; +import { mdiMagnify, mdiPlusCircleOutline, mdiRestore } from '@mdi/js'; import StudentGroup from '@tdev-components/StudentGroup'; import _ from 'es-toolkit/compat'; -import scheduleMicrotask from '@tdev-components/util/scheduleMicrotask'; import { action } from 'mobx'; +import Icon from '@mdi/react'; const StudentGroupPanel = observer(() => { const userStore = useStore('userStore'); const groupStore = useStore('studentGroupStore'); const current = userStore.current; + const [searchFilter, setSearchFilter] = React.useState(''); + const [searchRegex, setSearchRegex] = React.useState(new RegExp(searchFilter, 'i')); + + React.useEffect(() => { + setSearchRegex(new RegExp(searchFilter, 'i')); + }, [searchFilter]); + if (!current?.hasElevatedAccess) { return null; } return (
-
+ +
- {_.orderBy( - groupStore.managedStudentGroups.filter((g) => !g.parentId), - ['_pristine.name', 'createdAt'], - ['asc', 'desc'] - ).map((group) => ( - - ))} + {(() => { + const matches = groupStore.managedStudentGroups + .filter((g) => !g.parentId) + .map((group) => { + let matchPriority = 0; + + if (searchRegex) { + // Group name, student name or student email starts with the search filter? + const startsWithMatch = + group.name.toLowerCase().startsWith(searchFilter.toLowerCase()) || + group.students?.some( + (s) => + s.name.toLowerCase().startsWith(searchFilter.toLowerCase()) || + s.email.toLowerCase().startsWith(searchFilter.toLowerCase()) + ); + + // Group name matches (RegExp)? + const groupNameMatch = searchRegex.test(group.name); + + // Student name or email matches (RegExp)? + const studentMatch = group.students?.some( + (s) => searchRegex.test(s.name) || searchRegex.test(s.email) + ); + + // Description matches (RegExp)? + const descriptionMatch = searchRegex.test(group.description ?? ''); + + if (startsWithMatch) { + matchPriority = 0; + } else if (studentMatch) { + matchPriority = 1; + } else if (groupNameMatch) { + matchPriority = 2; + } else if (descriptionMatch) { + matchPriority = 3; + } else { + // We have a search filter and this doesn't match it. + return null; + } + } + + return { + group: group, + matchPriority + }; + }) + .filter((group) => !!group); // Non-matched groups are null - filter them out. + + return _.orderBy( + matches, + ['matchPriority', '_pristine.name', 'createdAt'], + ['asc', 'asc', 'desc'] + ).map((match) => ( + + )); + })()}
); diff --git a/src/components/Admin/StudentGroupPanel/styles.module.scss b/src/components/Admin/StudentGroupPanel/styles.module.scss index 32dbc1a66..6faf16241 100644 --- a/src/components/Admin/StudentGroupPanel/styles.module.scss +++ b/src/components/Admin/StudentGroupPanel/styles.module.scss @@ -1,3 +1,43 @@ +.controls { + display: flex; + flex-direction: row; + gap: 1em; + width: 100%; + margin-bottom: 0.5em; + flex-wrap: wrap; + + .searchBox { + display: flex; + align-items: center; + grid-area: 0.5em; + flex-grow: 1; + border: 1px solid var(--ifm-color-secondary); + border-radius: var(--ifm-global-radius); + padding: 0 0.2em; + + .searchInput { + display: flex; + align-items: stretch; + width: 100%; + + input[type='text'] { + flex-grow: 1; + border: none; + outline: none; + padding: 0.5em; + border-radius: 4px; + background-color: transparent; + width: 100%; + + &::placeholder { + color: inherit; + opacity: 0.5; + } + } + } + } +} + .studentGroups { display: flex; flex-direction: row; diff --git a/src/components/StudentGroup/AddMembersPopup/AddUser.tsx b/src/components/StudentGroup/AddMembersPopup/AddUser.tsx index 45d7b6913..00a591477 100644 --- a/src/components/StudentGroup/AddMembersPopup/AddUser.tsx +++ b/src/components/StudentGroup/AddMembersPopup/AddUser.tsx @@ -1,5 +1,5 @@ import styles from './styles.module.scss'; -import { mdiAccountPlus } from '@mdi/js'; +import { mdiAccountPlus, mdiAccountPlusOutline } from '@mdi/js'; import { useStore } from '@tdev-hooks/useStore'; import clsx from 'clsx'; import { observer } from 'mobx-react-lite'; @@ -7,6 +7,48 @@ import React from 'react'; import { _AddMembersPopupPropsInternal } from './types'; import Button from '@tdev-components/shared/Button'; import LiveStatusIndicator from '@tdev-components/LiveStatusIndicator'; +import User from '@tdev-models/User'; +import StudentGroup from '@tdev-models/StudentGroup'; + +interface AddUserLineProps { + idx: number; + user: User; + group: StudentGroup; + showAsSecondary: boolean; +} + +const AddUserLine = ({ idx, user, group, showAsSecondary: showAsSecondary }: AddUserLineProps) => { + return ( +
+
+
+ {user.nameShort} +
+
+
+ {user.studentGroups.map((group) => ( + {group.name} + ))} +
+
+
+
+
+
+ ); +}; const AddUser = observer((props: _AddMembersPopupPropsInternal) => { const userStore = useStore('userStore'); @@ -17,14 +59,17 @@ const AddUser = observer((props: _AddMembersPopupPropsInternal) => { setSearchRegex(new RegExp(searchFilter, 'i')); }, [searchFilter]); - const group = props.studentGroup; + const group: StudentGroup = props.studentGroup; + const users = userStore.users.filter((user) => searchRegex.test(user.searchTerm)); + const usersInParentGroup = users.filter((user) => group.parent?.userIds.has(user.id)); + const otherUsers = users.filter((user) => !group.parent?.userIds.has(user.id)); return ( <>

Benutzer:in hinzufügen

-
+
{ />
- {userStore.users - .filter((user) => searchRegex.test(user.searchTerm)) - .map((user, idx) => ( -
-
- - {' '} - {user.nameShort} - -
-
-
-
- ))} + {usersInParentGroup.map((user, idx) => ( + + ))} + {otherUsers.map((user, idx) => ( + 0} + /> + ))}
diff --git a/src/components/StudentGroup/AddMembersPopup/index.tsx b/src/components/StudentGroup/AddMembersPopup/index.tsx index 3d0fe2c5e..4987cd6c6 100644 --- a/src/components/StudentGroup/AddMembersPopup/index.tsx +++ b/src/components/StudentGroup/AddMembersPopup/index.tsx @@ -41,7 +41,7 @@ const AddUserPopup = observer((props: AddMembersPopupProps) => { ref={popupRef} >
-
+

{props.studentGroup.name}

diff --git a/src/components/StudentGroup/AddMembersPopup/styles.module.scss b/src/components/StudentGroup/AddMembersPopup/styles.module.scss index 32d546f58..83da21979 100644 --- a/src/components/StudentGroup/AddMembersPopup/styles.module.scss +++ b/src/components/StudentGroup/AddMembersPopup/styles.module.scss @@ -1,6 +1,11 @@ .wrapper { overflow-y: auto; - max-height: 80vh; + height: 80vh; + width: 40em; + + @media screen and (max-width: 768px) { + width: 95vw; + } } .tabs { @@ -8,6 +13,14 @@ --ifm-tabs-padding-horizontal: 0.6em; } +.textInput { + width: 100%; + border: 1px solid var(--ifm-color-secondary); + border-radius: var(--ifm-global-radius); + padding: 0.5em 1em; + margin-bottom: 0.5em; +} + .list { > :nth-child(2n) { background-color: var(--ifm-color-secondary-lightest); @@ -31,7 +44,7 @@ } } -.addUserCardTitle { +.addMembersPopupTitle { display: flex; flex-direction: row; align-items: center; @@ -42,6 +55,45 @@ } } +.addUserCardBody { + padding-top: 0.5em; + + .addUserListItem { + display: flex; + flex-direction: row; + width: 100%; + text-wrap: nowrap; + + .userInfo { + display: flex; + flex-direction: row; + align-items: center; + flex-grow: 0; + flex-shrink: 0; + gap: 0.2em; + } + + .groupMembership { + display: flex; + width: 100%; + flex-grow: 1; + flex-shrink: 1; + justify-content: flex-end; + align-items: center; + overflow: hidden; + + .groupMembershipBadges { + margin: 0 0.5em; + gap: 0.2em; + display: flex; + flex-direction: row; + align-items: center; + overflow-x: auto; + } + } + } +} + .importFromList { padding: 1em; diff --git a/src/pages/user/index.tsx b/src/pages/user/index.tsx index 45277fbc3..d4a4c9fef 100644 --- a/src/pages/user/index.tsx +++ b/src/pages/user/index.tsx @@ -177,19 +177,6 @@ const UserPage = observer(() => { )} - {userStore.current?.hasElevatedAccess && ( -
-

User Tabelle

-
- -
-
- )}

Account

Code Theme