diff --git a/apps/app/public/locales/en/common.json b/apps/app/public/locales/en/common.json
index 783ce73e04..c0c29dcbf9 100644
--- a/apps/app/public/locales/en/common.json
+++ b/apps/app/public/locales/en/common.json
@@ -34,8 +34,8 @@
"back-to-dynamic": "Back to {{page}}",
"back-to-search": "Back to search"
},
- "cancel": "Cancel",
"can-help-people-in": "Can help people located anywhere in {{location}}",
+ "cancel": "Cancel",
"claim-org-modal": {
"list": "🔗 Claim your organization’s profile page and build trust with your audience on InReach\n✍🏾 Update your organization's information on InReach\n📨 Invite other staff to join your organization on InReach\n🔑 Gain access to future features built specifically for affiliated service providers on InReach",
"title": "🏠\nThis organization has not yet been claimed by a service provider.\nWith a free InReach Service Provider account, you will soon be able to:"
@@ -211,6 +211,9 @@
"photo_one": "Photo",
"photo_other": "Photos",
"please-specify": "Please specify",
+ "portal-module": {
+ "service-area": "Service Area"
+ },
"powered-by-vercel": "Powered by Vercel",
"prefer-not-to-say": "Prefer not to say",
"privacy-policy": "Privacy policy",
@@ -265,6 +268,9 @@
"organization-placeholder-searchby": "Search by organization name...",
"suggest-resource": "Can't find it? Suggest an organization you think should be included."
},
+ "select": {
+ "base": "Select {{- item}}"
+ },
"send-email": "Send email",
"service": {
"additional-info": "Additional eligibility information",
diff --git a/packages/ui/modals/CoverageArea/hooks.ts b/packages/ui/modals/CoverageArea/hooks.ts
new file mode 100644
index 0000000000..8280909c3f
--- /dev/null
+++ b/packages/ui/modals/CoverageArea/hooks.ts
@@ -0,0 +1,14 @@
+import { useState } from 'react'
+
+export const useServiceAreaSelections = () => {
+ const [selected, setSelected] = useState({ country: null, govDist: null, subDist: null })
+ const setVal = {
+ country: (value: string) => setSelected({ country: value, govDist: null, subDist: null }),
+ govDist: (value: string) => setSelected((prev) => ({ ...prev, govDist: value, subDist: null })),
+ subDist: (value: string) => setSelected((prev) => ({ ...prev, subDist: value })),
+ blank: () => setSelected({ country: null, govDist: null, subDist: null }),
+ }
+
+ return [selected, setVal] as [typeof selected, typeof setVal]
+}
+type SelectionState = { country: string | null; govDist: string | null; subDist: string | null }
diff --git a/packages/ui/modals/CoverageArea/index.stories.tsx b/packages/ui/modals/CoverageArea/index.stories.tsx
index 82f4f31867..baa3794681 100644
--- a/packages/ui/modals/CoverageArea/index.stories.tsx
+++ b/packages/ui/modals/CoverageArea/index.stories.tsx
@@ -22,6 +22,7 @@ export default {
fieldOpt.countries,
fieldOpt.govDists,
serviceArea.getServiceArea,
+ serviceArea.update,
],
rqDevtools: true,
whyDidYouRender: { collapseGroups: true },
diff --git a/packages/ui/modals/CoverageArea/index.tsx b/packages/ui/modals/CoverageArea/index.tsx
index c146972c36..a36969aace 100644
--- a/packages/ui/modals/CoverageArea/index.tsx
+++ b/packages/ui/modals/CoverageArea/index.tsx
@@ -15,33 +15,37 @@ import {
Title,
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
+import { compareArrayVals } from 'crud-object-diff'
import compact from 'just-compact'
-import { useTranslation } from 'next-i18next'
-import { forwardRef, useState } from 'react'
+import { type TFunction, useTranslation } from 'next-i18next'
+import { forwardRef } from 'react'
import { useForm } from 'react-hook-form'
-import { Icon } from '~ui/icon'
import { trpc as api } from '~ui/lib/trpcClient'
+import { useServiceAreaSelections } from './hooks'
import { ServiceAreaForm, type ZServiceAreaForm } from './schema'
import { useStyles } from './styles'
import { ModalTitle } from '../ModalTitle'
+const reduceDistType = (data: { tsNs: string; tsKey: string }[] | undefined, t: TFunction) => {
+ if (!data) return ''
+ const valueSet = data.reduce((prev, curr) => {
+ const translated = t(curr.tsKey, { ns: curr.tsNs, count: 1 })
+ prev.add(translated)
+ return prev
+ }, new Set())
+ return [...valueSet].sort().join('/')
+}
+
const CoverageAreaModal = forwardRef(({ id, ...props }, ref) => {
const { classes } = useStyles()
const { t, i18n } = useTranslation(['common', 'gov-dist'])
const countryTranslation = new Intl.DisplayNames(i18n.language, { type: 'region' })
const [opened, { open, close }] = useDisclosure(true) //TODO: remove `true` when done with dev
- const [selected, setSelected] = useState({ country: null, govDist: null, subDist: null })
- const setVal = {
- country: (value: string) => setSelected({ country: value, govDist: null, subDist: null }),
- govDist: (value: string) => setSelected((prev) => ({ ...prev, govDist: value, subDist: null })),
- subDist: (value: string) => setSelected((prev) => ({ ...prev, subDist: value })),
- blank: () => setSelected({ country: null, govDist: null, subDist: null }),
- }
+ const [selected, setVal] = useServiceAreaSelections()
- const { data: dataServiceArea } = api.serviceArea.getServiceArea.useQuery(id)
const { data: dataCountry } = api.fieldOpt.countries.useQuery(
{ activeForOrgs: true },
{
@@ -60,6 +64,7 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
label: t(tsKey, { ns: tsNs }),
tsKey,
tsNs,
+ parent: null,
...rest,
})) ?? [],
placeholderData: [],
@@ -78,6 +83,9 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
placeholderData: [],
})
const apiUtils = api.useUtils()
+
+ const updateServiceArea = api.serviceArea.update.useMutation()
+
const form = useForm({
resolver: zodResolver(ServiceAreaForm),
defaultValues: async () => {
@@ -94,7 +102,13 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
const serviceAreaCountries = form.watch('countries')
const serviceAreaDistricts = form.watch('districts')
- console.log(serviceAreaCountries, serviceAreaDistricts)
+ const placeHolders = {
+ first: t('select.base', { item: 'Country' }),
+ second: t('select.base', {
+ item: reduceDistType(dataDistrict?.map(({ govDistType }) => govDistType), t),
+ }),
+ third: t('select.base', { item: reduceDistType(dataSubDist?.map(({ govDistType }) => govDistType), t) }),
+ }
const handleAdd = () => {
switch (true) {
@@ -107,7 +121,16 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
if (!valToAdd) return
form.setValue(
'districts',
- [...serviceAreaDistricts, { id: valToAdd.value, tsKey: valToAdd.tsKey, tsNs: valToAdd.tsNs }],
+ [
+ ...serviceAreaDistricts,
+ {
+ id: valToAdd.value,
+ tsKey: valToAdd.tsKey,
+ tsNs: valToAdd.tsNs,
+ parent: valToAdd.parent,
+ country: valToAdd.country,
+ },
+ ],
{
shouldValidate: true,
}
@@ -127,68 +150,70 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
}
}
- // const LocationSelect = ({ placeholder, data /*, inputPropsName*/ }: SelectFieldProps) => {
- // // Display close button when field is not empty
- // // const displayClose = form.getInputProps(inputPropsName).value.length > 0
- // const rightSection = (
- //
- // {
- // //displayClose && (
- // <>
- // console.log('clicked')}
- // variant='transparent'
- // style={{ pointerEvents: 'all' }}
- // >
- //
- //
- //
- // >
- // /*)*/
- // }
- //
- //
- // )
-
- // // Disable Select fields unless it's the state select field, or the state field has a value
- // // const disabled = inputPropsName.includes('state) || form.getInputProps('state').value.length === 0
-
- // return (
- //
- // )
- // }
-
const activeAreas = compact(
[
serviceAreaCountries?.map((country) => (
{countryTranslation.of(country.cca2)}
- console.log('Delete: ', location)} />
+
+ form.setValue('countries', serviceAreaCountries?.filter(({ id }) => id !== country.id))
+ }
+ />
)),
// Display -> Country / District / Sub-District
- serviceAreaDistricts?.map((govDist) => (
-
-
- {t(govDist.tsKey, { ns: govDist.tsNs })}
- console.log('Delete: ', location)} />
-
-
- )),
+ serviceAreaDistricts?.map((govDist) => {
+ const { id, tsKey, tsNs, country, parent } = govDist
+
+ const displayName = compact([
+ country.cca2,
+ parent ? t(parent.tsKey, { ns: parent.tsNs }) : null,
+ t(tsKey, { ns: tsNs }),
+ ]).join(' → ')
+
+ return (
+
+
+ {displayName}
+
+ form.setValue('districts', serviceAreaDistricts?.filter(({ id }) => id !== govDist.id))
+ }
+ />
+
+
+ )
+ }),
].flat()
)
+ const handleSave = () => {
+ const initialData = {
+ id: form.formState.defaultValues?.id,
+ countries: compact(form.formState.defaultValues?.countries?.map((country) => country?.id) ?? []),
+ districts: compact(form.formState.defaultValues?.districts?.map((district) => district?.id) ?? []),
+ }
+ const data = form.getValues()
+ const currentData = {
+ id: data.id,
+ countries: data.countries.map((country) => country.id),
+ districts: data.districts.map((district) => district.id),
+ }
+
+ const changes = {
+ id: data.id,
+ countries: compareArrayVals([initialData.countries, currentData.countries]),
+ districts: compareArrayVals([initialData.districts, currentData.districts]),
+ }
+ updateServiceArea.mutate(changes)
+ }
+
return (
<>
(({ id, ...props }
>
- {t('coverage-area')}
+ {t('portal-module.service-area')}
({ ...theme.other.utilityFonts.utility4, color: 'black' })}>
{`${t('organization')}: `}
@@ -207,26 +232,33 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
{activeAreas}
- theme.other.utilityFonts.utility1}>{t('add-coverage-area')}
+ theme.other.utilityFonts.utility1}>
+ {t('add', {
+ item: '$t(portal-module.service-area)',
+ })}
+
{selected.country && !!dataDistrict?.length && (
)}
{selected.govDist && !!dataSubDist?.length && (
)}
@@ -239,7 +271,7 @@ const CoverageAreaModal = forwardRef(({ id, ...props }
-
@@ -256,11 +288,3 @@ export const CoverageArea = createPolymorphicComponent
interface Props extends ButtonProps {
id: string
}
-
-type SelectFieldProps = {
- placeholder: string
- data: string[]
- // inputPropsName: string
-}
-
-type SelectionState = { country: string | null; govDist: string | null; subDist: string | null }