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
9 changes: 9 additions & 0 deletions src/app/protected/settings/locations/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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 { doesLocationCodeExist } from "@/lib/prisma/location/doesLocationCodeExist"
Expand All @@ -7,11 +8,18 @@ import { insertLocation } from "@/lib/prisma/location/insertLocation"
import { updateLocation } from "@/lib/prisma/location/updateLocation"
import { getAllServices } from "@/lib/prisma/service/getAllServices"
import { getAllStaffUsers } from "@/lib/prisma/staff_user/getAllStaffUsers"
import { getStaffUserBySub } from "@/lib/prisma/staff_user/getStaffUserBySub"
import { getAuthContext } from "@/utils/auth/getAuthContext"

// This page should always be rendered dynamically to ensure fresh data
export const dynamic = "force-dynamic"

export default async function Page() {
const headersList = await headers()
const authContext = getAuthContext(headersList)
const user = authContext?.user

const currentUser = await getStaffUserBySub(user?.sub ?? "")
const locations = await getAllLocations()
const services = await getAllServices()
const counters = [] as Counter[]
Expand All @@ -26,6 +34,7 @@ export default async function Page() {
<div className="space-y-sm">
<h2>Locations</h2>
<LocationTable
currentUser={currentUser}
locations={locations}
services={services}
counters={counters}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand All @@ -113,6 +115,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down Expand Up @@ -141,6 +145,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down Expand Up @@ -172,6 +178,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down Expand Up @@ -199,6 +207,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand All @@ -224,6 +234,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down Expand Up @@ -256,6 +268,8 @@ describe("EditLocationModal", () => {
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={true}
canArchive={true}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type EditLocationModalProps = {
services: ServiceWithRelations[]
counters: Counter[]
staffUsers: StaffUser[]
canEdit: boolean
canArchive: boolean
updateLocation: (
location: Partial<LocationWithRelations>,
prevLocation: Partial<LocationWithRelations>
Expand All @@ -38,6 +40,8 @@ export const EditLocationModal = ({
services,
counters,
staffUsers,
canEdit,
canArchive,
updateLocation,
doesLocationCodeExist,
revalidateTable,
Expand Down Expand Up @@ -131,7 +135,7 @@ export const EditLocationModal = ({
if (!location || !formData || !previousLocation) return null

const isArchived = location.deletedAt !== null
const isReadonly = isArchived
const isReadonly = isArchived || !canEdit

const handleSave = async () => {
if (formData && !isReadonly) {
Expand Down Expand Up @@ -166,13 +170,19 @@ export const EditLocationModal = ({

<DialogBody>
<form className="space-y-5">
{isReadonly && (
{!canEdit && (
<div className="flex flex-col gap-1 rounded-md border-l-4 border-l-red-600 bg-red-50 p-4">
{isArchived && (
<p className="text-sm font-medium text-red-800">
This location is archived and cannot be edited.
</p>
)}
<p className="text-sm font-medium text-red-800">
You do not have permission to edit this location.
</p>
</div>
)}

{isArchived && (
<div className="flex flex-col gap-1 rounded-md border-l-4 border-l-red-600 bg-red-50 p-4">
<p className="text-sm font-medium text-red-800">
This location is archived and cannot be edited.
</p>
</div>
)}

Expand All @@ -198,9 +208,11 @@ export const EditLocationModal = ({
<button type="button" className="tertiary" onClick={onClose}>
Cancel
</button>
<button type="button" className="secondary danger" onClick={handleOpenArchive}>
{isArchived ? "Unarchive" : "Archive"}
</button>
{canArchive && (
<button type="button" className="secondary danger" onClick={handleOpenArchive}>
{isArchived ? "Unarchive" : "Archive"}
</button>
)}
<button
type="button"
className="primary"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
"use client"

import { useState } from "react"
import { useEffect, useMemo, useState } from "react"
import { DataTable } from "@/components/common/datatable"
import { Switch } from "@/components/common/switch"
import type { Counter, StaffUser } from "@/generated/prisma/client"
import { useAuth } from "@/hooks/useAuth"
import { useDialog } from "@/hooks/useDialog"
import type { LocationWithRelations } from "@/lib/prisma/location/types"
import type { ServiceWithRelations } from "@/lib/prisma/service/types"
import type { StaffUserWithRelations } from "@/lib/prisma/staff_user/types"
import { resolvePolicy } from "@/utils/policies/resolvePolicy"
import type { UserContext } from "@/utils/policies/types"
import { ConfirmArchiveLocationModal } from "../ConfirmArchiveLocationModal"
import { CreateLocationModal } from "../CreateLocationModal"
import { EditLocationModal } from "../EditLocationModal"
import { columns } from "./columns"

export type LocationTableProps = {
currentUser: StaffUserWithRelations | null
locations: LocationWithRelations[]
services: ServiceWithRelations[]
counters: Counter[]
Expand All @@ -29,6 +34,7 @@ export type LocationTableProps = {
}

export const LocationTable = ({
currentUser,
locations,
services,
counters,
Expand All @@ -38,6 +44,7 @@ export const LocationTable = ({
doesLocationCodeExist,
revalidateTable,
}: LocationTableProps) => {
const { role, idir_user_guid } = useAuth()
const {
open: editLocationModalOpen,
openDialog: openEditLocationModal,
Expand All @@ -54,24 +61,61 @@ export const LocationTable = ({
closeDialog: closeConfirmArchiveLocationModal,
} = useDialog()

const userContext = useMemo<UserContext>(
() => ({
staff_user_id: idir_user_guid ?? null,
role,
location_code: currentUser?.locationCode ?? null,
}),
[idir_user_guid, role, currentUser?.locationCode]
)

const actions = resolvePolicy("location", userContext)

const [showArchived, setShowArchived] = useState<boolean>(false)
const [selectedLocation, setSelectedLocation] = useState<LocationWithRelations | null>(null)
const [canEditSelectedLocation, setCanEditSelectedLocation] = useState<boolean>(false)
const [canArchiveSelectedLocation, setCanArchiveSelectedLocation] = useState<boolean>(false)

// Determine if the current user can edit/archive the selected location whenever either changes
useEffect(() => {
if (selectedLocation) {
const actions = resolvePolicy("location", userContext, selectedLocation)
setCanEditSelectedLocation(actions.includes("edit"))
setCanArchiveSelectedLocation(actions.includes("archive"))
} else {
setCanEditSelectedLocation(false)
setCanArchiveSelectedLocation(false)
}
}, [selectedLocation, userContext])

const handleRowClick = (location: LocationWithRelations) => {
setSelectedLocation(location)
openEditLocationModal()
}

const locationsToShow = showArchived
? locations
: locations.filter((location) => location.deletedAt === null)
const locationsToShow = locations.filter((location) => {
// Filter by archived status
if (!showArchived && location.deletedAt !== null) return false

// Filter by view permission
const actions = resolvePolicy("location", userContext, location)
return actions.includes("view")
})

const canCreate = actions.includes("create")

return (
<>
<div className="flex items-center justify-end mb-3 gap-4">
<h3 className="self-center text-sm font-medium text-gray-700">Show Archived</h3>
<Switch checked={showArchived} onChange={setShowArchived} />
<button type="button" className="primary" onClick={openCreateLocationModal}>
<button
type="button"
className="primary"
onClick={openCreateLocationModal}
disabled={!canCreate}
>
+ Create
</button>
</div>
Expand All @@ -97,6 +141,8 @@ export const LocationTable = ({
services={services}
counters={counters}
staffUsers={staffUsers}
canEdit={canEditSelectedLocation}
canArchive={canArchiveSelectedLocation}
updateLocation={updateLocation}
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type EditStaffUserModalProps = {
user: StaffUser | null
canEdit: boolean
canArchive: boolean
canEditLocation: boolean
availableRoles: Role[]
locations: Location[]
updateStaffUser: (
Expand All @@ -37,6 +38,7 @@ export const EditStaffUserModal = ({
user,
canEdit,
canArchive,
canEditLocation,
locations,
availableRoles,
updateStaffUser,
Expand Down Expand Up @@ -95,18 +97,19 @@ export const EditStaffUserModal = ({

<DialogBody>
<form className="space-y-5">
{isReadonly && (
{!canEdit && (
<div className="flex flex-col gap-1 rounded-md border-l-4 border-l-red-600 bg-red-50 p-4">
{isArchived && (
<p className="text-sm font-medium text-red-800">
This user is archived and cannot be edited.
</p>
)}
{!canEdit && (
<p className="text-sm font-medium text-red-800">
You do not have permission to edit this user.
</p>
)}
<p className="text-sm font-medium text-red-800">
You do not have permission to edit this user.
</p>
</div>
)}

{isArchived && (
<div className="flex flex-col gap-1 rounded-md border-l-4 border-l-red-600 bg-red-50 p-4">
<p className="text-sm font-medium text-red-800">
This user is archived and cannot be edited.
</p>
</div>
)}

Expand All @@ -124,6 +127,7 @@ export const EditStaffUserModal = ({
locations={locations}
setFormData={setFormData}
availableRoles={availableRoles}
canEditLocation={canEditLocation}
disabled={isReadonly}
/>
<PermissionsSection user={formData} setFormData={setFormData} disabled={isReadonly} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type RoleAndAssignmentSectionProps = {
locations: Location[]
setFormData: Dispatch<SetStateAction<StaffUser | null>>
availableRoles: Role[]
canEditLocation: boolean
disabled?: boolean
}

Expand All @@ -16,7 +17,8 @@ export const RoleAndAssignmentSection = ({
locations,
setFormData,
availableRoles,
disabled,
canEditLocation,
disabled = false,
}: RoleAndAssignmentSectionProps) => {
return (
<Section title="Role and Assignment" disabled={disabled ?? false}>
Expand All @@ -25,7 +27,7 @@ export const RoleAndAssignmentSection = ({
label="Role"
value={user.role}
onChange={(value) => setFormData((prev) => prev && { ...prev, role: value as Role })}
disabled={disabled ?? false}
disabled={disabled}
options={availableRoles.map((role) => ({ value: role, label: role }))}
/>

Expand All @@ -34,7 +36,7 @@ export const RoleAndAssignmentSection = ({
label="Location"
value={user.locationCode === null ? undefined : user.locationCode}
onChange={(value) => setFormData((prev) => prev && { ...prev, locationCode: value })}
disabled={disabled ?? false}
disabled={!canEditLocation || disabled}
options={locations
.filter((location) => location.deletedAt === null)
.map((location) => ({
Expand Down
Loading
Loading