Skip to content
Snippets Groups Projects
Commit 5232d016 authored by Sivert Kronen Hatteberg's avatar Sivert Kronen Hatteberg
Browse files

Merge branch 'GREG-335-filter-guests' into 'master'

Add filter on unit for the guests table

See merge request !389
parents 59bed020 034c73d5
No related branches found
No related tags found
1 merge request!389Add filter on unit for the guests table
Pipeline #182125 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