Skip to content
Snippets Groups Projects
Verified Commit 034c73d5 authored by Sivert Kronen Hatteberg's avatar Sivert Kronen Hatteberg
Browse files

Add filter on unit for the guests table

parent 59bed020
No related branches found
No related tags found
1 merge request!389Add filter on unit for the guests table
Pipeline #182121 passed
......@@ -107,6 +107,7 @@
"foundNoGuests": "Found no guests",
"sentInvitations": "Sent invitations",
"placeholder": "Search for guest",
"chooseUnits": "Choose unit(s)",
"sentInvitationsDescription": "Invitations awaiting response from guest.",
"noInvitations": "No invitations",
"status": "Status",
......
......@@ -107,6 +107,7 @@
"foundNoGuests": "Fant ingen gjester",
"sentInvitations": "Sendte invitasjoner",
"placeholder": "Søk etter gjest",
"chooseUnits": "Velg avdeling(er)",
"sentInvitationsDescription": "Invitasjoner som venter på at gjesten skal ferdigstille registreringen.",
"noInvitations": "Ingen invitasjoner",
"status": "Status",
......
......@@ -107,6 +107,7 @@
"foundNoGuests": "Fann ingen gjester",
"sentInvitations": "Sendte invitasjonar",
"placeholder": "Søk etter gjest",
"chooseUnits": "Vel avdeling(ar)",
"sentInvitationsDescription": "Invitasjonar som venter på at gjesten skal ferdigstille registreringa.",
"noInvitations": "Ingen invitasjonar",
"status": "Status",
......
import { useEffect, useState } from 'react'
import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'
import NotificationsActiveIcon from '@mui/icons-material/NotificationsActive'
import {
......@@ -5,8 +7,14 @@ import {
AccordionDetails,
AccordionSummary,
Button,
Chip,
FormControl,
InputAdornment,
InputLabel,
MenuItem,
Paper,
Select,
SelectChangeEvent,
Table,
TableBody,
TableCell,
......@@ -18,7 +26,6 @@ import {
Typography,
} from '@mui/material'
import { Box, styled } from '@mui/system'
import { useState } from 'react'
import SearchIcon from '@mui/icons-material/Search'
import Loading from 'components/loading'
......@@ -48,6 +55,7 @@ interface GuestTableProps {
guests: Guest[]
emptyText: string
marginWidth: number
unitFilters: string[]
}
interface FrontPageProps {
......@@ -98,7 +106,7 @@ const StyledTableHead = styled(TableHead)(({ theme }) => ({
borderRadius: '0',
}))
const calculateStatus = (person: Guest, role: Role): [string, number] => {
function calculateStatus(person: Guest, role: Role): [string, number] {
const today = new Date()
today.setHours(0, 0, 0, 0)
let status = ''
......@@ -125,7 +133,7 @@ const calculateStatus = (person: Guest, role: Role): [string, number] => {
return [status, days]
}
const Status = ({ person, role }: StatusProps) => {
function Status({ person, role }: StatusProps) {
const { t } = useTranslation('common')
const [status, days] = calculateStatus(person, role)
......@@ -265,7 +273,10 @@ function sortByStatus(guests: Guest[], direction: SortDirection): GuestRole[] {
.concat(expiringRoleAndPerson)
}
function sortGuestsByRoleName(guests: Guest[], direction: SortDirection) {
function sortGuestsByRoleName(
guests: Guest[],
direction: SortDirection
): GuestRole[] {
return createGuestsRoles(guests).sort((a, b) => {
const aRoleName = getRoleName(a.role).toLowerCase()
const bRoleName = getRoleName(b.role).toLowerCase()
......@@ -280,7 +291,10 @@ function sortGuestsByRoleName(guests: Guest[], direction: SortDirection) {
})
}
function sortGuestsByDepartmentName(guests: Guest[], direction: SortDirection) {
function sortGuestsByDepartmentName(
guests: Guest[],
direction: SortDirection
): GuestRole[] {
return createGuestsRoles(guests).sort((a, b) => {
const aRoleOUName = getRoleOuName(a.role).toLowerCase()
const bRoleOUName = getRoleOuName(b.role).toLowerCase()
......@@ -298,26 +312,43 @@ function sortGuestsByDepartmentName(guests: Guest[], direction: SortDirection) {
function sortGuestsAndRoles(
guests: Guest[],
sortField: SortField,
direction: SortDirection
): { guest: Guest; role: Role }[] {
direction: SortDirection,
unitFilters?: string[]
): GuestRole[] {
let guestRoles: GuestRole[]
switch (sortField) {
case 'department':
return sortGuestsByDepartmentName(guests, direction)
guestRoles = sortGuestsByDepartmentName(guests, direction)
break
case 'endDate':
return sortGuestRolesByEndDate(createGuestsRoles(guests), direction)
guestRoles = sortGuestRolesByEndDate(createGuestsRoles(guests), direction)
break
case 'name':
return sortGuestsByName(guests, direction)
guestRoles = sortGuestsByName(guests, direction)
break
case 'role':
return sortGuestsByRoleName(guests, direction)
guestRoles = sortGuestsByRoleName(guests, direction)
break
case 'status':
return sortByStatus(guests, direction)
guestRoles = sortByStatus(guests, direction)
break
default:
// Fallback to original sort
return createGuestsRoles(guests)
guestRoles = createGuestsRoles(guests)
break
}
if (unitFilters && unitFilters.length > 0) {
return guestRoles.filter(({ role }) =>
unitFilters.includes(role.ou_id.toString())
)
}
return guestRoles
}
const PersonLine = ({ person, role }: PersonLineProps) => {
function PersonLine({ person, role }: PersonLineProps) {
const [t] = useTranslation(['common'])
return (
......@@ -343,7 +374,52 @@ const PersonLine = ({ person, role }: PersonLineProps) => {
)
}
const GuestTable = ({ guests, emptyText, marginWidth }: GuestTableProps) => {
function PersonTableBody({
guests,
orderBy,
direction,
unitFilters,
}: {
guests: Guest[]
orderBy: SortField
direction: SortDirection
unitFilters: string[] | undefined
}) {
const { t } = useTranslation('common')
const filteredSortedGuests = sortGuestsAndRoles(
guests,
orderBy,
direction,
unitFilters
)
if (filteredSortedGuests.length === 0) {
return (
<StyledTableRow>
<TableCell>{t('foundNoGuests')}</TableCell>
</StyledTableRow>
)
}
return (
<>
{filteredSortedGuests.map((personRole) => (
<PersonLine
key={`${personRole.guest.first} ${personRole.guest.last} ${personRole.role.id}`}
role={personRole.role}
person={personRole.guest}
/>
))}
</>
)
}
function GuestTable({
guests,
emptyText,
marginWidth,
unitFilters,
}: GuestTableProps) {
const { t } = useTranslation('common')
const [direction, setDirection] = useState<SortDirection>('asc')
......@@ -425,13 +501,12 @@ const GuestTable = ({ guests, emptyText, marginWidth }: GuestTableProps) => {
</StyledTableHead>
<TableBody>
{guests.length > 0 ? (
sortGuestsAndRoles(guests, orderBy, direction).map((personRole) => (
<PersonLine
key={`${personRole.guest.first} ${personRole.guest.last} ${personRole.role.id}`}
role={personRole.role}
person={personRole.guest}
/>
))
<PersonTableBody
guests={guests}
orderBy={orderBy}
direction={direction}
unitFilters={unitFilters}
/>
) : (
<StyledTableRow>
<TableCell>{emptyText}</TableCell>
......@@ -443,7 +518,7 @@ const GuestTable = ({ guests, emptyText, marginWidth }: GuestTableProps) => {
)
}
const InvitedGuests = ({ persons }: GuestProps) => {
function InvitedGuests({ persons }: GuestProps) {
const [activeExpanded, setActiveExpanded] = useState(false)
// Show guests that have not responded to the invite yet
......@@ -477,6 +552,7 @@ const InvitedGuests = ({ persons }: GuestProps) => {
<GuestTable
guests={guests}
emptyText={t('common:noInvitations')}
unitFilters={[]}
marginWidth={650}
/>
</AccordionDetails>
......@@ -484,32 +560,138 @@ const InvitedGuests = ({ persons }: GuestProps) => {
)
}
const ActiveGuests = ({ persons }: GuestProps) => {
const [activeExpanded, setActiveExpanded] = useState(false)
const [searchHasInput, setSearchHasInput] = useState(false)
const ITEM_HEIGHT = 48
const ITEM_PADDING_TOP = 8
const MenuProps = {
PaperProps: {
style: {
maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP,
width: 400,
},
},
}
interface UnitFilterSelectProps {
guests: Guest[]
onChange: (units: string[]) => void
}
function UnitFilterSelect({ guests, onChange }: UnitFilterSelectProps) {
//
const [selectedUnits, setSelectedUnits] = useState<string[]>([])
const { t } = useTranslation(['common'])
useEffect(() => {
onChange(selectedUnits)
}, [selectedUnits, onChange])
const handleChange = (event: SelectChangeEvent<typeof selectedUnits>) => {
const {
target: { value },
} = event
setSelectedUnits(
// On autofill we get a stringified value.
typeof value === 'string' ? value.split(',') : value
)
}
const units: { [key: string]: string } = {}
guests.forEach((guest) =>
guest.roles.forEach((role) => {
if (!(role.ou_id in units)) {
units[role.ou_id.toString()] = getRoleOuName(role)
}
})
)
if (units) {
return (
<div>
<FormControl sx={{ m: 1, width: 450 }}>
<InputLabel id="ou-select-label">{t('chooseUnits')}</InputLabel>
<Select
labelId="ou-select-label"
id="ou-select"
value={selectedUnits}
onChange={handleChange}
multiple
label={t('chooseUnits')}
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{Object.entries(selected).map(([, id]) => (
<Chip
key={id}
label={units[id]}
onDelete={() =>
setSelectedUnits(selectedUnits.filter((u) => id !== u))
}
onMouseDown={(event) => {
event.stopPropagation()
}}
/>
))}
</Box>
)}
MenuProps={MenuProps}
>
{Object.entries(units).map(([id, name]) => (
<MenuItem key={id} value={id}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
</div>
)
}
return null
}
function ActiveGuests({ persons }: GuestProps) {
const [activeExpanded, setActiveExpanded] = useState<boolean>(false)
const [searchGuests, setSearchGuests] = useState<Guest[]>([])
const [selectedUnits, setSelectedUnits] = useState<string[]>([])
const [searchInput, setSearchInput] = useState<string>('')
const [searching, setSearching] = useState<boolean>(false)
const [t] = useTranslation(['common'])
// Show all verified guests
let guests = persons.length > 0 ? persons : []
if (guests.length > 0) {
guests = guests.filter((person) => person.verified)
}
const [t] = useTranslation(['common'])
const getSponsorGuests = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value) {
setSearchHasInput(true)
const guestSearch: Guest[] = guests.filter((guest) =>
`${guest.first.toLowerCase()} ${guest.last.toLowerCase()}`.includes(
event.target.value.toLowerCase()
// Wait 1s after last change to start the search.
useEffect(() => {
if (searchInput === '') {
if (searchGuests) {
setSearchGuests([])
}
return () => {}
}
setSearching(true)
const delaySearch = setTimeout(() => {
setSearchGuests(
guests.filter((guest) =>
`${guest.first.toLowerCase()} ${guest.last.toLowerCase()}`.includes(
searchInput
)
)
)
setSearchGuests(guestSearch)
setSearching(false)
}, 1000)
return () => clearTimeout(delaySearch)
}, [searchInput])
const getSponsorGuests = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value) {
setSearchInput(event.target.value.toLowerCase())
} else {
setSearchHasInput(false)
setSearchGuests([])
setSearchInput('')
}
}
return (
<StyledAccordion
expanded={activeExpanded}
......@@ -536,30 +718,44 @@ const ActiveGuests = ({ persons }: GuestProps) => {
marginBottom: '1rem',
}}
>
<TextField
variant="standard"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'flex-start',
}}
placeholder={t('placeholder')}
onChange={getSponsorGuests}
/>
{!searchHasInput ? (
<GuestTable
>
<FormControl sx={{ m: 1, width: 300 }}>
<TextField
InputProps={{
endAdornment: (
<InputAdornment position="end">
<SearchIcon />
</InputAdornment>
),
}}
placeholder={t('placeholder')}
onChange={getSponsorGuests}
/>
</FormControl>
<UnitFilterSelect
guests={guests}
emptyText={t('common:noActiveGuests')}
marginWidth={1000}
onChange={(units: string[]) => setSelectedUnits(units)}
/>
) : (
</Box>
{!searching ? (
<GuestTable
guests={searchGuests}
emptyText={t('common:foundNoGuests')}
guests={searchInput ? searchGuests : guests}
emptyText={
searchInput
? t('common:foundNoGuests')
: t('common:noActiveGuests')
}
marginWidth={1000}
unitFilters={selectedUnits}
/>
) : (
<Loading />
)}
</Box>
</AccordionDetails>
......@@ -567,7 +763,7 @@ const ActiveGuests = ({ persons }: GuestProps) => {
)
}
const WaitingGuests = ({ persons }: GuestProps) => {
function WaitingGuests({ persons }: GuestProps) {
const [waitingExpanded, setWaitingExpanded] = useState(false)
// Show guests that have completed the registration but are not verified yet
......@@ -610,6 +806,7 @@ const WaitingGuests = ({ persons }: GuestProps) => {
guests={guests}
emptyText={t('common:noWaitingGuests')}
marginWidth={650}
unitFilters={[]}
/>
</AccordionDetails>
</StyledAccordion>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment