diff --git a/src/components/settings/locations/CreateLocationModal/CreateLocationModal.tsx b/src/components/settings/locations/CreateLocationModal/CreateLocationModal.tsx
new file mode 100644
index 0000000..71a833d
--- /dev/null
+++ b/src/components/settings/locations/CreateLocationModal/CreateLocationModal.tsx
@@ -0,0 +1,184 @@
+"use client"
+
+import { useEffect, useState } from "react"
+import { z } from "zod"
+import {
+ CloseButton,
+ DialogActions,
+ DialogBody,
+ DialogHeader,
+ DialogTitle,
+ Modal,
+} from "@/components/common/dialog"
+import type { Counter, StaffUser } from "@/generated/prisma/client"
+import type { LocationWithRelations } from "@/lib/prisma/location/types"
+import type { ServiceWithRelations } from "@/lib/prisma/service/types"
+import { LocationForm } from "../LocationForm"
+
+type CreateLocationModalProps = {
+ open: boolean
+ onClose: () => void
+ services: ServiceWithRelations[]
+ counters: Counter[]
+ staffUsers: StaffUser[]
+ insertLocation: (
+ location: Partial
+ ) => Promise
+ doesLocationCodeExist: (code: string) => Promise
+ revalidateTable: () => Promise
+}
+
+export const CreateLocationModal = ({
+ open,
+ onClose,
+ services,
+ counters,
+ staffUsers,
+ insertLocation,
+ doesLocationCodeExist,
+ revalidateTable,
+}: CreateLocationModalProps) => {
+ const [isSaving, setIsSaving] = useState(false)
+ const [formData, setFormData] = useState | null>(null)
+ const [isFormValidState, setIsFormValidState] = useState(false)
+ const [isFormValidating, setIsFormValidating] = useState(false)
+
+ // initialize form data when the modal opens
+ useEffect(() => {
+ if (open) {
+ setFormData({
+ name: "",
+ code: "",
+ streetAddress: "",
+ mailAddress: null,
+ phoneNumber: null,
+ timezone: "America/Vancouver",
+ latitude: undefined,
+ longitude: undefined,
+ legacyOfficeNumber: null,
+ deletedAt: null,
+ services: [],
+ counters: [],
+ staffUsers: [],
+ })
+ } else {
+ setFormData(null)
+ setIsFormValidState(false)
+ setIsFormValidating(false)
+ }
+ }, [open])
+
+ const NewLocationWithRelationsSchema = z.object({
+ name: z.string().min(1, "Name is required"),
+ code: z
+ .string()
+ .min(1, "Code is required")
+ .refine(
+ async (code) => {
+ return !(await doesLocationCodeExist(code))
+ },
+ { message: "Code already exists" }
+ ),
+ streetAddress: z.string(),
+ mailAddress: z.string().nullable(),
+ timezone: z.string(),
+ phoneNumber: z
+ .string()
+ .nullable()
+ .refine(
+ (phone) => {
+ // Allow null or empty string (optional field)
+ if (!phone || phone.trim() === "") return true
+ // Must contain at least 10 digits for a valid phone number
+ const digits = phone.replace(/\D/g, "")
+ return digits.length >= 10
+ },
+ { message: "Phone number must contain at least 10 digits" }
+ ),
+ latitude: z.number(),
+ longitude: z.number(),
+ legacyOfficeNumber: z.number().nullable(),
+ services: z.array(z.any()),
+ counters: z.array(z.any()),
+ staffUsers: z.array(z.any()),
+ })
+
+ // Validate formData asynchronously and update local state instead of calling async validators during render
+ // biome-ignore lint/correctness/useExhaustiveDependencies: <>
+ useEffect(() => {
+ if (!formData) {
+ setIsFormValidState(false)
+ setIsFormValidating(false)
+ return
+ }
+
+ let active = true
+ setIsFormValidating(true)
+
+ NewLocationWithRelationsSchema.parseAsync(formData)
+ .then(() => {
+ if (active) setIsFormValidState(true)
+ })
+ .catch(() => {
+ if (active) setIsFormValidState(false)
+ })
+ .finally(() => {
+ if (active) setIsFormValidating(false)
+ })
+
+ return () => {
+ active = false
+ }
+ }, [formData, doesLocationCodeExist])
+
+ if (!formData) return null
+
+ const isArchived = formData.deletedAt !== null
+ const isReadonly = isArchived
+
+ const handleSave = async () => {
+ if (formData && !isReadonly) {
+ setIsSaving(true)
+ await insertLocation(formData)
+ await revalidateTable()
+ onClose()
+ setIsSaving(false)
+ }
+ }
+
+ return (
+
+ }>
+ Create Location
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/settings/locations/CreateLocationModal/index.ts b/src/components/settings/locations/CreateLocationModal/index.ts
new file mode 100644
index 0000000..29dd8aa
--- /dev/null
+++ b/src/components/settings/locations/CreateLocationModal/index.ts
@@ -0,0 +1 @@
+export * from "./CreateLocationModal"
diff --git a/src/components/settings/locations/EditLocationModal/EditLocationModal.tsx b/src/components/settings/locations/EditLocationModal/EditLocationModal.tsx
index b4cc43b..630197c 100644
--- a/src/components/settings/locations/EditLocationModal/EditLocationModal.tsx
+++ b/src/components/settings/locations/EditLocationModal/EditLocationModal.tsx
@@ -63,6 +63,24 @@ export const EditLocationModal = ({
},
{ message: "Code already exists" }
),
+ streetAddress: z.string(),
+ mailAddress: z.string().nullable(),
+ phoneNumber: z
+ .string()
+ .nullable()
+ .refine(
+ (phone) => {
+ // Allow null or empty string (optional field)
+ if (!phone || phone.trim() === "") return true
+ // Must contain at least 10 digits for a valid phone number
+ const digits = phone.replace(/\D/g, "")
+ return digits.length >= 10
+ },
+ { message: "Phone number must contain at least 10 digits" }
+ ),
+ timezone: z.string(),
+ latitude: z.number(),
+ longitude: z.number(),
legacyOfficeNumber: z.number().nullable(),
deletedAt: z.date().nullable(),
createdAt: z.date(),
diff --git a/src/components/settings/locations/LocationForm.tsx b/src/components/settings/locations/LocationForm.tsx
index 0cb0eb0..1c50d25 100644
--- a/src/components/settings/locations/LocationForm.tsx
+++ b/src/components/settings/locations/LocationForm.tsx
@@ -131,6 +131,7 @@ export const LocationForm = ({
{
// Update the form with the selected address information
@@ -156,7 +157,6 @@ export const LocationForm = ({
value={location.latitude?.toString() || ""}
onChange={() => {}}
disabled
- className="flex-1"
/>
{}}
disabled
- className="flex-1"
/>
,
prevLocation: Partial
) => Promise
+ insertLocation: (
+ location: Partial
+ ) => Promise
doesLocationCodeExist: (code: string) => Promise
revalidateTable: () => Promise
}
@@ -29,6 +33,7 @@ export const LocationTable = ({
counters,
staffUsers,
updateLocation,
+ insertLocation,
doesLocationCodeExist,
revalidateTable,
}: LocationTableProps) => {
@@ -37,6 +42,11 @@ export const LocationTable = ({
openDialog: openEditLocationModal,
closeDialog: closeEditLocationModal,
} = useDialog()
+ const {
+ open: createLocationModalOpen,
+ openDialog: openCreateLocationModal,
+ closeDialog: closeCreateLocationModal,
+ } = useDialog()
const [showArchived, setShowArchived] = useState(false)
const [selectedLocation, setSelectedLocation] = useState(null)
@@ -55,7 +65,7 @@ export const LocationTable = ({
Show Archived
-
@@ -85,6 +95,16 @@ export const LocationTable = ({
doesLocationCodeExist={doesLocationCodeExist}
revalidateTable={revalidateTable}
/>
+
>
)
}