Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/app/protected/settings/locations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { revalidatePath } from "next/cache"
import { headers } from "next/headers"
import { LocationTable } from "@/components/settings/locations/LocationTable"
import type { Counter } from "@/generated/prisma/client"
import { getAllCounters } from "@/lib/prisma/counter/getAllCounters"
import { doesLocationCodeExist } from "@/lib/prisma/location/doesLocationCodeExist"
import { getAllLocations } from "@/lib/prisma/location/getAllLocations"
import { insertLocation } from "@/lib/prisma/location/insertLocation"
Expand All @@ -22,7 +22,7 @@ export default async function Page() {
const currentUser = await getStaffUserBySub(user?.sub ?? "")
const locations = await getAllLocations()
const services = await getAllServices()
const counters = [] as Counter[]
const counters = await getAllCounters()
const staffUsers = await getAllStaffUsers()

const revalidateTable = async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/app/protected/settings/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { revalidatePath } from "next/cache"
import { headers } from "next/headers"
import { StaffUserTable } from "@/components/settings/users/StaffUserTable"
import { getAllCounters } from "@/lib/prisma/counter/getAllCounters"
import { getAllLocations } from "@/lib/prisma/location/getAllLocations"
import { getAllStaffUsers } from "@/lib/prisma/staff_user/getAllStaffUsers"
import { getStaffUserBySub } from "@/lib/prisma/staff_user/getStaffUserBySub"
Expand All @@ -23,6 +24,7 @@ export default async function Page() {
const currentUser = await getStaffUserBySub(user?.sub ?? "")
const users = await getAllStaffUsers()
const locations = await getAllLocations()
const counters = await getAllCounters()

return (
<div className="space-y-sm">
Expand All @@ -31,6 +33,7 @@ export default async function Page() {
currentUser={currentUser}
users={users}
locations={locations}
counters={counters}
updateStaffUser={updateStaffUser}
revalidateTable={revalidateTable}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export const LocationCounterSwitch = ({
const handleLocationChange = (locationCode: string) => {
const loc = locations?.find((l) => l?.code === locationCode) || null
setSelectedLocation(loc)
const defaultCounter = loc?.counters?.find((c) => c?.name === "Counter") || null
setSelectedCounter(defaultCounter)
}

const handleCounterChange = (counterId: string) => {
Expand Down
8 changes: 5 additions & 3 deletions src/components/common/select/MultiSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type MultiSelectProps = {
onChange: (selected: string[]) => void
placeholder?: string
disabled?: boolean
locked?: string[]
}

export const MultiSelect = ({
Expand All @@ -26,6 +27,7 @@ export const MultiSelect = ({
onChange,
placeholder = "Search...",
disabled = false,
locked = [],
}: MultiSelectProps) => {
const [query, setQuery] = useState("")
const [focusedWithin, setFocusedWithin] = useState(false)
Expand All @@ -47,14 +49,14 @@ export const MultiSelect = ({
const isSelected = (key: string) => selected.includes(key)

const toggle = (key: string) => {
if (disabled) return
if (disabled || locked.includes(key)) return
if (isSelected(key)) onChange(selected.filter((s) => s !== key))
else onChange([...selected, key])
}

const remove = (e: React.MouseEvent, key: string) => {
e.stopPropagation()
if (disabled) return
if (disabled || locked.includes(key)) return
onChange(selected.filter((s) => s !== key))
}

Expand Down Expand Up @@ -251,7 +253,7 @@ export const MultiSelect = ({
>
{o.label}
</span>
{!disabled && (
{!disabled && !locked.includes(o.key) && (
<button
type="button"
aria-label={`Remove ${o.label}`}
Expand Down
2 changes: 2 additions & 0 deletions src/components/settings/locations/LocationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const LocationForm = ({
selectedStaffUserIds,
serviceOptions,
counterOptions,
lockedCounterIds,
staffUserOptions,
handleCodeChange,
handleNameChange,
Expand Down Expand Up @@ -171,6 +172,7 @@ export const LocationForm = ({
onChange={handleCountersChange}
placeholder="Select counters"
disabled={isReadonly}
locked={lockedCounterIds}
/>
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,30 @@ import {
DialogTitle,
Modal,
} from "@/components/common/dialog"
import type { Location, Role, StaffUser } from "@/generated/prisma/client"
import type { Role } from "@/generated/prisma/client"
import { useEditStaffUserModal } from "@/hooks/settings/users/useEditStaffUserModal"
import type { CounterWithRelations } from "@/lib/prisma/counter/types"
import type { LocationWithRelations } from "@/lib/prisma/location/types"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"
import { PermissionsSection } from "./sections/PermissionsSection"
import { RoleAndAssignmentSection } from "./sections/RoleAndAssignmentSection"
import { UserInformationSection } from "./sections/UserInformationSection"

type EditStaffUserModalProps = {
open: boolean
onClose: () => void
user: StaffUser | null
user: StaffUserWithRelations | null
canEdit: boolean
canArchive: boolean
canEditLocation: boolean
availableRoles: Role[]
locations: Location[]
locations: LocationWithRelations[]
counters: CounterWithRelations[]
updateStaffUser: (
user: Partial<StaffUser>,
prevUser: Partial<StaffUser>,
user: Partial<StaffUserWithRelations>,
prevUser: Partial<StaffUserWithRelations>,
availableRoles: Role[]
) => Promise<StaffUser | null>
) => Promise<StaffUserWithRelations | null>
revalidateTable: () => Promise<void>
openConfirmArchiveUserModal: () => void
}
Expand All @@ -40,6 +44,7 @@ export const EditStaffUserModal = ({
canArchive,
canEditLocation,
locations,
counters,
availableRoles,
updateStaffUser,
revalidateTable,
Expand Down Expand Up @@ -105,6 +110,7 @@ export const EditStaffUserModal = ({
<RoleAndAssignmentSection
user={formData}
locations={locations}
counters={counters}
setFormData={setFormData}
availableRoles={availableRoles}
canEditLocation={canEditLocation}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Dispatch, SetStateAction } from "react"
import { CheckboxInput } from "@/components/common/checkbox"
import type { StaffUser } from "@/generated/prisma/client"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"
import { Section } from "./Section"

type PermissionsSectionProps = {
user: StaffUser
setFormData: Dispatch<SetStateAction<StaffUser | null>>
user: StaffUserWithRelations
setFormData: Dispatch<SetStateAction<StaffUserWithRelations | null>>
disabled?: boolean
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type { Dispatch, SetStateAction } from "react"
import { SelectInput } from "@/components/common/select"
import type { Location, Role, StaffUser } from "@/generated/prisma/client"
import type { Role } from "@/generated/prisma/client"
import type { CounterWithRelations } from "@/lib/prisma/counter/types"
import type { LocationWithRelations } from "@/lib/prisma/location/types"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"
import { Section } from "./Section"

type RoleAndAssignmentSectionProps = {
user: StaffUser
locations: Location[]
setFormData: Dispatch<SetStateAction<StaffUser | null>>
user: StaffUserWithRelations
locations: LocationWithRelations[]
counters: CounterWithRelations[]
setFormData: Dispatch<SetStateAction<StaffUserWithRelations | null>>
availableRoles: Role[]
canEditLocation: boolean
disabled?: boolean
Expand All @@ -15,13 +19,26 @@ type RoleAndAssignmentSectionProps = {
export const RoleAndAssignmentSection = ({
user,
locations,
counters,
setFormData,
availableRoles,
canEditLocation,
disabled = false,
}: RoleAndAssignmentSectionProps) => {
const locationCounters = counters.filter((c) =>
c.locations.some((l) => l.code === user.locationCode)
)

const defaultCounter = counters.find((c) => c.name === "Counter") ?? null

const handleLocationChange = (value: string) => {
setFormData(
(prev) => prev && { ...prev, locationCode: value, counterId: defaultCounter?.id ?? null }
)
}

return (
<Section title="Role and Assignment" disabled={disabled ?? false}>
<Section title="Assignment" disabled={disabled ?? false}>
<SelectInput
id="role"
label="Role"
Expand All @@ -35,7 +52,7 @@ export const RoleAndAssignmentSection = ({
id="locationCode"
label="Location"
value={user.locationCode === null ? undefined : user.locationCode}
onChange={(value) => setFormData((prev) => prev && { ...prev, locationCode: value })}
onChange={handleLocationChange}
disabled={!canEditLocation || disabled}
options={locations
.filter((location) => location.deletedAt === null)
Expand All @@ -44,6 +61,15 @@ export const RoleAndAssignmentSection = ({
label: `${location.name} (${location.code})`,
}))}
/>

<SelectInput
id="counterId"
label="Counter"
value={user.counterId !== null ? user.counterId : undefined}
onChange={(value) => setFormData((prev) => prev && { ...prev, counterId: value })}
disabled={disabled || !user.locationCode || locationCounters.length === 0}
options={locationCounters.map((counter) => ({ value: counter.id, label: counter.name }))}
/>
</Section>
)
}
17 changes: 11 additions & 6 deletions src/components/settings/users/StaffUserTable/StaffUserTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,33 @@

import { DataTable } from "@/components/common/datatable"
import { Switch } from "@/components/common/switch"
import type { Location, Role, StaffUser } from "@/generated/prisma/client"
import type { Role } from "@/generated/prisma/client"
import { useStaffUserTable } from "@/hooks/settings/users/useStaffUserTable"
import type { CounterWithRelations } from "@/lib/prisma/counter/types"
import type { LocationWithRelations } from "@/lib/prisma/location/types"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"
import { ConfirmArchiveUserModal } from "../ConfirmArchiveUserModal"
import { EditStaffUserModal } from "../EditStaffUserModal"
import { columns } from "./columns"

export type UserTableProps = {
currentUser: StaffUserWithRelations | null
users: StaffUser[]
locations: Location[]
users: StaffUserWithRelations[]
locations: LocationWithRelations[]
counters: CounterWithRelations[]
updateStaffUser: (
user: Partial<StaffUser>,
prevUser: Partial<StaffUser>,
user: Partial<StaffUserWithRelations>,
prevUser: Partial<StaffUserWithRelations>,
availableRoles?: Role[]
) => Promise<StaffUser | null>
) => Promise<StaffUserWithRelations | null>
revalidateTable: () => Promise<void>
}

export const StaffUserTable = ({
currentUser,
users,
locations,
counters,
updateStaffUser,
revalidateTable,
}: UserTableProps) => {
Expand Down Expand Up @@ -75,6 +79,7 @@ export const StaffUserTable = ({
canEditLocation={canEditLocationSelectedUser}
availableRoles={availableRolesForSelectedUser}
locations={locations}
counters={counters}
updateStaffUser={updateStaffUser}
revalidateTable={revalidateTable}
openConfirmArchiveUserModal={openConfirmArchiveUserModal}
Expand Down
4 changes: 2 additions & 2 deletions src/components/settings/users/StaffUserTable/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ColumnConfig } from "@/components/common/datatable"
import type { StaffUser } from "@/generated/prisma/client"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"

export const columns: ColumnConfig<StaffUser>[] = [
export const columns: ColumnConfig<StaffUserWithRelations>[] = [
{
key: "displayName",
label: "Name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ export const useLocationForm = ({
[staffUsers]
)

// IDs of counters named "Counter" that are currently assigned to this location
const lockedCounterIds = useMemo(
() => (location.counters ?? []).filter((c) => c.name === "Counter").map((c) => c.id),
[location.counters]
)

// biome-ignore lint/correctness/useExhaustiveDependencies: <>
useEffect(() => {
// when the location changes (new location loaded) reset initial code and state
Expand Down Expand Up @@ -153,11 +159,13 @@ export const useLocationForm = ({
}

const handleCountersChange = (selected: string[]) => {
// Ensure locked counters (named "Counter") are never removed
const withLocked = Array.from(new Set([...lockedCounterIds, ...selected]))
setFormData((s) =>
s
? {
...s,
counters: selected.map((id) => counters.find((c) => c.id === id) as Counter),
counters: withLocked.map((id) => counters.find((c) => c.id === id) as Counter),
}
: s
)
Expand All @@ -181,6 +189,7 @@ export const useLocationForm = ({
selectedStaffUserIds,
serviceOptions,
counterOptions,
lockedCounterIds,
staffUserOptions,
handleCodeChange,
handleNameChange,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useRouter } from "next/navigation"
import { useEffect, useState } from "react"
import type { Role, StaffUser } from "@/generated/prisma/client"
import type { Role } from "@/generated/prisma/client"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"

type UseEditStaffUserModalProps = {
open: boolean
onClose: () => void
user: StaffUser | null
user: StaffUserWithRelations | null
canEdit: boolean
canArchive: boolean
updateStaffUser: (
user: Partial<StaffUser>,
prevUser: Partial<StaffUser>,
user: Partial<StaffUserWithRelations>,
prevUser: Partial<StaffUserWithRelations>,
availableRoles: Role[]
) => Promise<StaffUser | null>
) => Promise<StaffUserWithRelations | null>
availableRoles: Role[]
revalidateTable: () => Promise<void>
openConfirmArchiveUserModal: () => void
Expand Down Expand Up @@ -46,8 +47,8 @@ export const useEditStaffUserModal = ({
const router = useRouter()
const [isSaving, setIsSaving] = useState(false)
const [error, setError] = useState<string | null>(null)
const [formData, setFormData] = useState<StaffUser | null>(null)
const [previousUser, setPreviousUser] = useState<StaffUser | null>(null)
const [formData, setFormData] = useState<StaffUserWithRelations | null>(null)
const [previousUser, setPreviousUser] = useState<StaffUserWithRelations | null>(null)

useEffect(() => {
if (open && user) {
Expand Down
Loading
Loading