Skip to content

Commit

Permalink
feat: create new service (#1278)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

# Pull Request type

<!-- Please try to limit your pull request to one type; submit multiple
pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [x] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no API changes)
- [ ] Build-related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying, or
link to a relevant issue. -->

Issue Number: IN-971

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

## Does this introduce a breaking change?

- [ ] Yes
- [ ] No

<!-- If this does introduce a breaking change, please describe the
impact and migration path for existing applications below. -->

## Other information

<!-- Any other information that is important to this PR, such as
screenshots of how the component looks before and after the change. -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added the ability to create and attach services to specific locations.
  - Introduced a new style variant for text with a strikethrough effect.

- **Bug Fixes**
- Improved error handling during service editing to ensure smoother user
experience.
- Enhanced visibility and filtering logic for service information
display.

- **Improvements**
  - Updated UI components for better service selection and addition.
- Refined service modal logic to conditionally render sections based on
available data.

- **Dependencies**
- Updated `@sentry/integrations` and `@sentry/node` to version
`7.116.0`.
  - Updated `@tanstack/react-table` to version `8.16.0`.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
  • Loading branch information
JoeKarow authored May 29, 2024
1 parent b731302 commit e7f599b
Show file tree
Hide file tree
Showing 15 changed files with 392 additions and 331 deletions.
29 changes: 20 additions & 9 deletions apps/app/src/pages/org/[slug]/[orgLocationId]/edit.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createStyles, Grid, Stack, Tabs, Title } from '@mantine/core'
import { createStyles, Grid, Group, Stack, Tabs, Title } from '@mantine/core'
import { compareArrayVals } from 'crud-object-diff'
import { type InferGetServerSidePropsType, type NextPage } from 'next'
import Head from 'next/head'
Expand All @@ -12,15 +12,18 @@ import { z } from 'zod'
import { prefixedId } from '@weareinreach/api/schemas/idPrefix'
import { trpcServerClient } from '@weareinreach/api/trpc'
import { checkServerPermissions } from '@weareinreach/auth'
import { Button } from '@weareinreach/ui/components/core'
import { AlertMessage } from '@weareinreach/ui/components/core/AlertMessage'
import { Toolbar } from '@weareinreach/ui/components/core/Toolbar'
import { MultiSelectPopover } from '@weareinreach/ui/components/data-portal/MultiSelectPopover/hook-form'
import { ServiceEditDrawer } from '@weareinreach/ui/components/data-portal/ServiceEditDrawer'
import { ContactSection } from '@weareinreach/ui/components/sections/ContactSection'
import { ListingBasicInfo } from '@weareinreach/ui/components/sections/ListingBasicInfo'
import { PhotosSection } from '@weareinreach/ui/components/sections/Photos'
import { ReviewSection } from '@weareinreach/ui/components/sections/Reviews'
import { ServicesInfoCard } from '@weareinreach/ui/components/sections/ServicesInfo'
import { VisitCard } from '@weareinreach/ui/components/sections/VisitCard'
import { useCustomVariant } from '@weareinreach/ui/hooks/useCustomVariant'
import { useEditMode } from '@weareinreach/ui/hooks/useEditMode'
import { useNewNotification } from '@weareinreach/ui/hooks/useNewNotification'
import { OrgLocationPageLoading } from '@weareinreach/ui/loading-states/OrgLocationPage'
Expand All @@ -46,6 +49,7 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
}) => {
const apiUtils = api.useUtils()
const { t } = useTranslation()
const variants = useCustomVariant()
const router = useRouter<'/org/[slug]/[orgLocationId]'>()
const { query } = router.isReady ? router : { query: { slug: '', orgLocationId: '' } }
const { slug, orgLocationId } = query
Expand Down Expand Up @@ -86,7 +90,8 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide

const updateLocation = api.page.LocationEditUpdate.useMutation({
onSuccess: () => {
apiUtils.location.forLocationPageEdits.invalidate()
apiUtils.location.invalidate()
apiUtils.service.invalidate()
notifySave()
},
})
Expand Down Expand Up @@ -205,13 +210,19 @@ const OrgLocationPage: NextPage<InferGetServerSidePropsType<typeof getServerSide
<Stack spacing={20} ref={servicesRef}>
<Stack spacing={8}>
<Title order={3}>{'Associate service(s) to this location'}</Title>
<MultiSelectPopover
label='Services available'
data={orgServices}
control={formMethods.control}
name='services'
indicateWhenDirty
/>
<Group noWrap position='apart'>
<MultiSelectPopover
label='Services available'
data={orgServices}
control={formMethods.control}
name='services'
indicateWhenDirty
/>
{/* eslint-disable-next-line i18next/no-literal-string */}
<ServiceEditDrawer createNew component={Button} variant={variants.Button.primarySm}>
Add new service
</ServiceEditDrawer>
</Group>
</Stack>
<Stack spacing={8}>
<Title order={3}>{'Associated services'}</Title>
Expand Down
12 changes: 6 additions & 6 deletions apps/app/src/types/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
// This file will be automatically regenerated when your Next.js server is running.
// nextjs-routes version: 2.0.1
// nextjs-routes version: 2.2.0
/* eslint-disable */

// prettier-ignore
Expand All @@ -11,6 +11,7 @@ declare module "nextjs-routes" {
} from "next";

export type Route =
| StaticRoute<"/">
| StaticRoute<"/401">
| StaticRoute<"/403">
| StaticRoute<"/404">
Expand All @@ -19,21 +20,20 @@ declare module "nextjs-routes" {
| StaticRoute<"/account/reviews">
| StaticRoute<"/account/saved">
| StaticRoute<"/admin">
| StaticRoute<"/admin/quicklink/email">
| StaticRoute<"/admin/quicklink">
| StaticRoute<"/admin/quicklink/email">
| StaticRoute<"/admin/quicklink/phone">
| StaticRoute<"/admin/quicklink/services">
| DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }>
| StaticRoute<"/api/i18n/load">
| StaticRoute<"/api/i18n/webhook">
| StaticRoute<"/api/panel">
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| StaticRoute<"/api/trpc-playground">
| StaticRoute<"/">
| DynamicRoute<"/org/[slug]/[orgLocationId]/edit", { "slug": string; "orgLocationId": string }>
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| DynamicRoute<"/org/[slug]", { "slug": string }>
| DynamicRoute<"/org/[slug]/[orgLocationId]", { "slug": string; "orgLocationId": string }>
| DynamicRoute<"/org/[slug]/[orgLocationId]/edit", { "slug": string; "orgLocationId": string }>
| DynamicRoute<"/org/[slug]/edit", { "slug": string }>
| DynamicRoute<"/org/[slug]", { "slug": string }>
| DynamicRoute<"/org/[slug]/remote", { "slug": string }>
| StaticRoute<"/profile">
| DynamicRoute<"/search/[...params]", { "params": string[] }>
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@
}
},
"overrides": {
"@sentry/integrations@<7.116.0": "@sentry/[email protected]",
"@sentry/node@<7.116.0": "@sentry/[email protected]",
"@tanstack/[email protected]": "@tanstack/[email protected]",
"better-sqlite3@<9": "^9.0.0",
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec"
Expand Down
6 changes: 5 additions & 1 deletion packages/api/router/service/mutation.upsert.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const upsert = async ({ ctx, input }: TRPCHandlerParams<TUpsertSchema, 'protecte
const { generateId } = ctx

const id = input.id ?? generateId('orgService')
const { published, deleted, organizationId: orgId } = input
const { published, deleted, organizationId: orgId, attachToLocation } = input

const hasServiceUpdates = Boolean(
input.services?.createdVals?.length ?? input.services?.deletedVals?.length
Expand Down Expand Up @@ -56,6 +56,7 @@ const upsert = async ({ ctx, input }: TRPCHandlerParams<TUpsertSchema, 'protecte
id,
deleted,
published,
organization: { connect: { id: orgId } },
...(input.services?.createdVals && {
services: {
createMany: {
Expand All @@ -66,6 +67,9 @@ const upsert = async ({ ctx, input }: TRPCHandlerParams<TUpsertSchema, 'protecte
}),
...(serviceName && { serviceName: { create: serviceName.upsert.create } }),
...(description && { description: { create: description.upsert.create } }),
...(attachToLocation && {
locations: { create: { location: { connect: { id: attachToLocation } } } },
}),
},
update: {
published,
Expand Down
1 change: 1 addition & 0 deletions packages/api/router/service/mutation.upsert.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export const ZUpsertSchema = z.object({
deletedVals: z.string().array().nullable(),
})
.optional(),
attachToLocation: prefixedId('orgLocation').optional(),
})
export type TUpsertSchema = z.infer<typeof ZUpsertSchema>
117 changes: 61 additions & 56 deletions packages/api/router/service/query.forServiceEditDrawer.handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { prisma } from '@weareinreach/db'
import { formatAttributes } from '~api/formatters/attributes'
import { formatHours } from '~api/formatters/hours'
import { handleError } from '~api/lib/errorHandler'
import { type TRPCHandlerParams } from '~api/types/handler'

import { type TForServiceEditDrawerSchema } from './query.forServiceEditDrawer.schema'
Expand All @@ -9,66 +10,70 @@ const freeTextSelect = {
select: { tsKey: { select: { key: true, text: true, ns: true, crowdinId: true } } },
} as const
const forServiceEditDrawer = async ({ input }: TRPCHandlerParams<TForServiceEditDrawerSchema>) => {
const result = await prisma.orgService.findUniqueOrThrow({
where: { id: input },
select: {
id: true,
published: true,
deleted: true,
attributes: formatAttributes.prismaSelect(true),
description: freeTextSelect,
phones: { select: { phone: { select: { id: true } } } },
emails: { select: { email: { select: { id: true } } } },
locations: {
select: { orgLocationId: true, location: { select: { country: { select: { cca2: true } } } } },
},
hours: formatHours.prismaSelect(true),
services: { select: { tag: { select: { id: true, tsKey: true, tsNs: true } } } },
serviceAreas: {
select: {
id: true,
countries: { select: { countryId: true } },
districts: { select: { govDistId: true } },
try {
const result = await prisma.orgService.findUniqueOrThrow({
where: { id: input },
select: {
id: true,
published: true,
deleted: true,
attributes: formatAttributes.prismaSelect(true),
description: freeTextSelect,
phones: { select: { phone: { select: { id: true } } } },
emails: { select: { email: { select: { id: true } } } },
locations: {
select: { orgLocationId: true, location: { select: { country: { select: { cca2: true } } } } },
},
hours: formatHours.prismaSelect(true),
services: { select: { tag: { select: { id: true, tsKey: true, tsNs: true } } } },
serviceAreas: {
select: {
id: true,
countries: { select: { countryId: true } },
districts: { select: { govDistId: true } },
},
},

serviceName: freeTextSelect,
},
})
const {
attributes: rawAttributes,
phones,
emails,
// locations,
services,
serviceAreas,
hours,
description,
serviceName,
...rest
} = result
const { attributes, accessDetails } = formatAttributes.processAndSeparateAccessDetails(rawAttributes)

serviceName: freeTextSelect,
},
})
const {
attributes: rawAttributes,
phones,
emails,
// locations,
services,
serviceAreas,
hours,
description,
serviceName,
...rest
} = result
const { attributes, accessDetails } = formatAttributes.processAndSeparateAccessDetails(rawAttributes)
const transformed = {
...rest,
name: serviceName?.tsKey,
description: description?.tsKey,
phones: phones.map(({ phone }) => phone.id),
emails: emails.map(({ email }) => email.id),
// locations: locations.map(({ orgLocationId }) => orgLocationId),
services: services.map(({ tag }) => tag.id),
hours: formatHours.process(hours),
serviceAreas: serviceAreas
? {
id: serviceAreas.id,
countries: serviceAreas.countries.map(({ countryId }) => countryId),
districts: serviceAreas.districts.map(({ govDistId }) => govDistId),
}
: null,
attributes,
accessDetails,
}

const transformed = {
...rest,
name: serviceName?.tsKey,
description: description?.tsKey,
phones: phones.map(({ phone }) => phone.id),
emails: emails.map(({ email }) => email.id),
// locations: locations.map(({ orgLocationId }) => orgLocationId),
services: services.map(({ tag }) => tag.id),
hours: formatHours.process(hours),
serviceAreas: serviceAreas
? {
id: serviceAreas.id,
countries: serviceAreas.countries.map(({ countryId }) => countryId),
districts: serviceAreas.districts.map(({ govDistId }) => govDistId),
}
: null,
attributes,
accessDetails,
return transformed
} catch (err) {
return handleError(err)
}

return transformed
}
export default forServiceEditDrawer
22 changes: 15 additions & 7 deletions packages/api/router/service/query.forServiceInfoCard.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ import { type TRPCHandlerParams } from '~api/types/handler'

import { type TForServiceInfoCardSchema } from './query.forServiceInfoCard.schema'

const forServiceInfoCard = async ({ input }: TRPCHandlerParams<TForServiceInfoCardSchema>) => {
const forServiceInfoCard = async ({ input, ctx }: TRPCHandlerParams<TForServiceInfoCardSchema>) => {
const { parentId, isEditMode, remoteOnly } = input

const canSeeAll = isEditMode && ctx.session?.user
const recordVisiblity = { ...(!canSeeAll && globalWhere.isPublic()) }

const result = await prisma.orgService.findMany({
where: {
...globalWhere.isPublic(),
...(isIdFor('organization', input.parentId)
? { organization: { id: input.parentId, ...globalWhere.isPublic() } }
: { locations: { some: { location: { id: input.parentId, ...globalWhere.isPublic() } } } }),
...(input.remoteOnly
...recordVisiblity,
...(isIdFor('organization', parentId)
? { organization: { id: parentId, ...recordVisiblity } }
: { locations: { some: { location: { id: parentId, ...recordVisiblity } } } }),
...(remoteOnly
? { attributes: { some: { attribute: { active: true, tag: 'offers-remote-services' } } } }
: {}),
OR: [{ crisisSupportOnly: null }, { crisisSupportOnly: false }],
Expand All @@ -31,11 +36,14 @@ const forServiceInfoCard = async ({ input }: TRPCHandlerParams<TForServiceInfoCa
where: { attribute: { active: true, tag: 'offers-remote-services' } },
select: { attributeId: true },
},
published: true,
deleted: true,
},
})

const transformed = result.map(({ id, serviceName, services, attributes }) => ({
const transformed = result.map(({ id, serviceName, services, attributes, ...status }) => ({
id,
...status,
serviceName: serviceName
? { tsKey: serviceName.key, tsNs: serviceName.ns, defaultText: serviceName.tsKey.text }
: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ import { prefixedId } from '~api/schemas/idPrefix'
export const ZForServiceInfoCardSchema = z.object({
parentId: prefixedId(['organization', 'orgLocation']),
remoteOnly: z.boolean().optional(),
isEditMode: z.boolean().optional(),
})
export type TForServiceInfoCardSchema = z.infer<typeof ZForServiceInfoCardSchema>
Loading

0 comments on commit e7f599b

Please sign in to comment.