Skip to content

Commit

Permalink
feat: programs on listings (bloom-housing#4412)
Browse files Browse the repository at this point in the history
* feat: programs on listings

* fix: adding openapi doc updates

* fix: shmeep

* fix: I missed up
  • Loading branch information
YazeedLoonat committed Nov 15, 2024
1 parent 7dbd255 commit 740fe3a
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 153 deletions.
13 changes: 13 additions & 0 deletions api/src/controllers/script-runner.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,4 +198,17 @@ export class ScirptRunnerController {
): Promise<SuccessDTO> {
return await this.scriptRunnerService.insertSanJoseMapLayers(req);
}

@Put('hideProgramsFromListings')
@ApiOperation({
summary:
'A script that hides program multiselect questions from the public detail page',
operationId: 'hideProgramsFromListings',
})
@ApiOkResponse({ type: SuccessDTO })
async hideProgramsFromListings(
@Request() req: ExpressRequest,
): Promise<SuccessDTO> {
return await this.scriptRunnerService.hideProgramsFromListings(req);
}
}
301 changes: 160 additions & 141 deletions api/src/services/script-runner.service.ts

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions api/test/unit/services/script-runner.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import {
LanguagesEnum,
MultiselectQuestionsApplicationSectionEnum,
PrismaClient,
ReviewOrderTypeEnum,
} from '@prisma/client';
Expand Down Expand Up @@ -744,6 +745,53 @@ describe('Testing script runner service', () => {
});
});

it('should hide programs from listing detail page', async () => {
const id = randomUUID();
const scriptName = 'hideProgramsFromListings';

prisma.scriptRuns.findUnique = jest.fn().mockResolvedValue(null);
prisma.scriptRuns.create = jest.fn().mockResolvedValue(null);
prisma.scriptRuns.update = jest.fn().mockResolvedValue(null);
prisma.multiselectQuestions.updateMany = jest.fn().mockResolvedValue(null);

const res = await service.hideProgramsFromListings({
user: {
id,
} as unknown as User,
} as unknown as ExpressRequest);

expect(res.success).toBe(true);

expect(prisma.scriptRuns.findUnique).toHaveBeenCalledWith({
where: {
scriptName,
},
});
expect(prisma.scriptRuns.create).toHaveBeenCalledWith({
data: {
scriptName,
triggeringUser: id,
},
});
expect(prisma.scriptRuns.update).toHaveBeenCalledWith({
data: {
didScriptRun: true,
triggeringUser: id,
},
where: {
scriptName,
},
});
expect(prisma.multiselectQuestions.updateMany).toHaveBeenCalledWith({
data: {
hideFromListing: true,
},
where: {
applicationSection: MultiselectQuestionsApplicationSectionEnum.programs,
},
});
});

// | ---------- HELPER TESTS BELOW ---------- | //
it('should mark script run as started if no script run present in db', async () => {
prisma.scriptRuns.findUnique = jest.fn().mockResolvedValue(null);
Expand Down
3 changes: 3 additions & 0 deletions shared-helpers/src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@
"listings.processInfo": "Información sobre el proceso",
"listings.publicLottery.header": "Lotería pública",
"listings.remainingUnitsAfterPreferenceConsideration": "Después de que se hayan tomado en consideración a los poseedores de preferencias, todas las viviendas restantes estarán a disposición de otros solicitantes calificados.",
"listings.remainingUnitsAfterPrograms": "Una vez que se hayan considerado a todos los titulares de preferencias, las unidades restantes estarán disponibles para otros solicitantes calificados.",
"listings.rentalHistory": "Historial de alquiler",
"listings.rePricing": "Asignar un nuevo precio",
"listings.requiredDocuments": "Documentos requeridos",
Expand Down Expand Up @@ -702,6 +703,8 @@
"listings.sections.featuresTitle": "Características",
"listings.sections.housingPreferencesSubtitle": "Los poseedores de preferencia recibirán la más alta clasificación",
"listings.sections.housingPreferencesTitle": "Preferencias de vivienda",
"listings.sections.housingProgramsSubtitle": "Algunas o todas las unidades de esta propiedad están reservadas para personas que califican para los programas de vivienda particulares que se enumeran a continuación. Es posible que deba calificar para uno de estos programas para poder ser elegible para una unidad en esta propiedad.",
"listings.sections.housingProgramsTitle": "Programas de vivienda",
"listings.sections.neighborhoodSubtitle": "Ubicación y transporte",
"listings.sections.processSubtitle": "Fechas importantes e información de contacto",
"listings.sections.processTitle": "Proceso",
Expand Down
3 changes: 3 additions & 0 deletions shared-helpers/src/locales/general.json
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@
"listings.processInfo": "Process Info",
"listings.publicLottery.header": "Public Lottery",
"listings.remainingUnitsAfterPreferenceConsideration": "After all preference holders have been considered, any remaining units will be available to other qualified applicants.",
"listings.remainingUnitsAfterPrograms": "One or more questions in the application will help to determine whether or not you are eligible for the housing programs listed above. After you have submitted your application, the property manager will ask you to verify your housing program eligibility by providing documentation or another form of verification.",
"listings.rentalHistory": "Rental History",
"listings.rePricing": "Re-Pricing",
"listings.requiredDocuments": "Required Documents",
Expand Down Expand Up @@ -693,6 +694,8 @@
"listings.sections.featuresTitle": "Features",
"listings.sections.housingPreferencesSubtitle": "Preference holders will be given highest ranking.",
"listings.sections.housingPreferencesTitle": "Housing Preferences",
"listings.sections.housingProgramsSubtitle": "Some or all of the units for this property are reserved for persons who qualify for the particular housing program(s) listed below. You may need to qualify for one of these programs in order to be eligible for a unit at this property.",
"listings.sections.housingProgramsTitle": "Housing Programs",
"listings.sections.neighborhoodSubtitle": "Location and transportation",
"listings.sections.processSubtitle": "Important dates and contact information",
"listings.sections.processTitle": "Process",
Expand Down
3 changes: 3 additions & 0 deletions shared-helpers/src/locales/tl.json
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@
"listings.processInfo": "Impormasyon ng Proseso",
"listings.publicLottery.header": "Pampublikong Lottery",
"listings.remainingUnitsAfterPreferenceConsideration": "Pagkatapos na isaalang-alang ang lahat ng preference holder, ang anumang natitirang mga unit ay magiging available sa iba pang mga kwalipikadong aplikante.",
"listings.remainingUnitsAfterPrograms": "Matapos isaalang-alang ang lahat ng mga may hawak ng kagustuhan, ang anumang natitirang mga yunit ay magiging available sa iba pang mga kwalipikadong aplikante.",
"listings.rentalHistory": "History ng Pag-upa",
"listings.rePricing": "Pagbabago ng Presyo",
"listings.requiredDocuments": "Kinakailangang mga Dokumento",
Expand Down Expand Up @@ -701,6 +702,8 @@
"listings.sections.featuresTitle": "Mga feature",
"listings.sections.housingPreferencesSubtitle": "Ang mga preference holder ay bibigyan ng pinakamataas na ranggo.",
"listings.sections.housingPreferencesTitle": "Mga Pagpipilian ng Pabahay",
"listings.sections.housingProgramsSubtitle": "Ang ilan o lahat ng mga unit para sa property na ito ay nakalaan para sa mga taong kwalipikado para sa partikular na (mga) programa sa pabahay na nakalista sa ibaba. Maaaring kailanganin mong maging kwalipikado para sa isa sa mga programang ito upang maging karapat-dapat para sa isang unit sa property na ito.",
"listings.sections.housingProgramsTitle": "Mga Programa sa Pabahay",
"listings.sections.neighborhoodSubtitle": "Lokasyon at transportasyon",
"listings.sections.processSubtitle": "Mahahalagang petsa at impormasyon ng kontak",
"listings.sections.processTitle": "Proseso",
Expand Down
3 changes: 3 additions & 0 deletions shared-helpers/src/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@
"listings.processInfo": "Thông tin về Quy trình",
"listings.publicLottery.header": "Xổ số Công cộng",
"listings.remainingUnitsAfterPreferenceConsideration": "Sau khi tất cả những người nắm giữ phiếu chọn ưu tiên đã được xem xét, bất kỳ căn nhà còn lại sẽ dành cho các ứng viên hội đủ điều kiện khác.",
"listings.remainingUnitsAfterPrograms": "Sau khi tất cả các chủ sở hữu ưu tiên đã được xem xét, mọi đơn vị còn lại sẽ được cung cấp cho những người nộp đơn đủ điều kiện khác.",
"listings.rentalHistory": "Lịch sử Thuê nhà",
"listings.rePricing": "Định giá lại",
"listings.requiredDocuments": "Các tài liệu Cần thiết",
Expand Down Expand Up @@ -702,6 +703,8 @@
"listings.sections.featuresTitle": "Các tính năng",
"listings.sections.housingPreferencesSubtitle": "Những người nắm giữ phiếu chọn ưu tiên sẽ được xếp ở thứ hạng cao nhất.",
"listings.sections.housingPreferencesTitle": "Lựa chọn Ưu tiên Nhà ở",
"listings.sections.housingProgramsSubtitle": "Một số hoặc tất cả căn hộ của khu nhà này được dành riêng cho những người đủ điều kiện tham gia (các) chương trình nhà ở cụ thể được liệt kê bên dưới. Bạn có thể cần phải đủ điều kiện tham gia một trong những chương trình này để đủ điều kiện nhận một căn hộ tại cơ sở kinh doanh này.",
"listings.sections.housingProgramsTitle": "Chương trình nhà ở",
"listings.sections.neighborhoodSubtitle": "Vị trí và phương tiện di chuyển",
"listings.sections.processSubtitle": "Những ngày quan trọng và thông tin liên lạc",
"listings.sections.processTitle": "Quy trình",
Expand Down
3 changes: 3 additions & 0 deletions shared-helpers/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,7 @@
"listings.processInfo": "申請過程資訊",
"listings.publicLottery.header": "公開抽籤",
"listings.remainingUnitsAfterPreferenceConsideration": "在考慮所有優先權持有人之後,任何剩餘單位將提供給其他符合資格的申請人。",
"listings.remainingUnitsAfterPrograms": "申請表中的一個或多個問題將有助於確定您是否有資格參加上述住房計劃。在您提交申請後,物業經理將要求您提供文件或其他形式的驗證來驗證您的住房計畫資格。",
"listings.rentalHistory": "租賃記錄",
"listings.rePricing": "重新定價",
"listings.requiredDocuments": "必須提供的文件",
Expand Down Expand Up @@ -702,6 +703,8 @@
"listings.sections.featuresTitle": "特點",
"listings.sections.housingPreferencesSubtitle": "優先權持有人將獲最高排名。",
"listings.sections.housingPreferencesTitle": "住房優先權",
"listings.sections.housingProgramsSubtitle": "該房產的部分或全部單位專為符合下列特定住房計畫資格的人員保留。您可能需要符合其中一項計劃的資格,才有資格入住該酒店的單位。",
"listings.sections.housingProgramsTitle": "住房計劃",
"listings.sections.neighborhoodSubtitle": "位置和交通",
"listings.sections.processSubtitle": "重要日期和聯絡資料",
"listings.sections.processTitle": "申請程序",
Expand Down
16 changes: 16 additions & 0 deletions shared-helpers/src/types/backend-swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,22 @@ export class ScriptRunnerService {

configs.data = data

axios(configs, resolve, reject)
})
}
/**
* A script that hides program multiselect questions from the public detail page
*/
hideProgramsFromListings(options: IRequestOptions = {}): Promise<SuccessDTO> {
return new Promise((resolve, reject) => {
let url = basePath + "/scriptRunner/hideProgramsFromListings"

const configs: IRequestConfig = getConfigs("put", "application/json", url, options)

let data = null

configs.data = data

axios(configs, resolve, reject)
})
}
Expand Down
82 changes: 70 additions & 12 deletions sites/public/src/components/listing/ListingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
pdfUrlFromListingEvents,
AuthContext,
} from "@bloom-housing/shared-helpers"
import { Card, Heading as SeedsHeading } from "@bloom-housing/ui-seeds"
import dayjs from "dayjs"
import { ErrorPage } from "../../pages/_error"
import { useGetApplicationStatusProps } from "../../lib/hooks"
Expand All @@ -57,6 +58,7 @@ import {
ListingEvent,
ListingEventCreate,
ListingEventsTypeEnum,
ListingMultiselectQuestion,
ListingsStatusEnum,
MultiselectQuestionsApplicationSectionEnum,
ReviewOrderTypeEnum,
Expand Down Expand Up @@ -87,9 +89,20 @@ const getWhatToExpectContent = (listing: Listing) => {
return null
}

const getUnhiddenMultiselectQuestions = (
arrayToSeach: ListingMultiselectQuestion[],
section: MultiselectQuestionsApplicationSectionEnum
): ListingMultiselectQuestion[] => {
return arrayToSeach.filter(
(elem) =>
elem.multiselectQuestions.applicationSection === section &&
!elem.multiselectQuestions.hideFromListing
)
}

export const ListingView = (props: ListingProps) => {
const { initialStateLoaded, profile } = useContext(AuthContext)
let buildingSelectionCriteria, preferencesSection
let buildingSelectionCriteria, preferencesSection, programsSection
const { listing } = props
const { content: appStatusContent, subContent: appStatusSubContent } =
useGetApplicationStatusProps(listing)
Expand Down Expand Up @@ -193,32 +206,76 @@ export const ListingView = (props: ListingProps) => {
)
}

const listingPreferences = listing?.listingMultiselectQuestions.filter(
(listingPref) =>
listingPref.multiselectQuestions.applicationSection ===
MultiselectQuestionsApplicationSectionEnum.preferences &&
!listingPref.multiselectQuestions.hideFromListing
const listingPreferences = getUnhiddenMultiselectQuestions(
listing?.listingMultiselectQuestions || [],
MultiselectQuestionsApplicationSectionEnum.preferences
)

const getPreferenceData = () => {
return listingPreferences.map((listingPref, index) => {
const listingPrograms = getUnhiddenMultiselectQuestions(
listing?.listingMultiselectQuestions || [],
MultiselectQuestionsApplicationSectionEnum.programs
)

const getMultiselectQuestionData = (section: MultiselectQuestionsApplicationSectionEnum) => {
let multiselectQuestionSet: ListingMultiselectQuestion[] = []

if (section === MultiselectQuestionsApplicationSectionEnum.programs) {
multiselectQuestionSet = listingPrograms
} else {
multiselectQuestionSet = listingPreferences
}
return multiselectQuestionSet.map((listingMultiselectQuestion, index) => {
return {
ordinal: index + 1,
links: listingPref?.multiselectQuestions?.links,
title: listingPref?.multiselectQuestions?.text,
description: listingPref?.multiselectQuestions?.description,
links: listingMultiselectQuestion?.multiselectQuestions?.links,
title: listingMultiselectQuestion?.multiselectQuestions?.text,
description: listingMultiselectQuestion?.multiselectQuestions?.description,
}
})
}

if (listingPrograms && listingPrograms?.length > 0) {
programsSection = (
<ListSection
title={t("listings.sections.housingProgramsTitle")}
subtitle={t("listings.sections.housingProgramsSubtitle")}
>
<>
{getMultiselectQuestionData(MultiselectQuestionsApplicationSectionEnum.programs).map(
(msq) => {
return (
<Card spacing="md" className="listing-multiselect-card">
<Card.Header>
<SeedsHeading size="sm" priority={4}>
{msq.title}
</SeedsHeading>
</Card.Header>

<Card.Section>
<p>{msq.description}</p>
</Card.Section>
</Card>
)
}
)}
<p className="text-gray-750 text-sm">{t("listings.remainingUnitsAfterPrograms")}</p>
</>
</ListSection>
)
}

if (listingPreferences && listingPreferences?.length > 0) {
preferencesSection = (
<ListSection
title={t("listings.sections.housingPreferencesTitle")}
subtitle={t("listings.sections.housingPreferencesSubtitle")}
>
<>
<PreferencesList listingPreferences={getPreferenceData()} />
<PreferencesList
listingPreferences={getMultiselectQuestionData(
MultiselectQuestionsApplicationSectionEnum.preferences
)}
/>
<p className="text-gray-750 text-sm">
{t("listings.remainingUnitsAfterPreferenceConsideration")}
</p>
Expand Down Expand Up @@ -715,6 +772,7 @@ export const ListingView = (props: ListingProps) => {
/>
)}

{programsSection}
{preferencesSection}

{(listing.creditHistory ||
Expand Down
19 changes: 19 additions & 0 deletions sites/public/styles/overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
-webkit-font-smoothing: antialiased; // restore macOS styling that had been unset
}

.listing-multiselect-card {
--card-border-radius: var(--seeds-s1);
--card-content-padding-block: var(--seeds-s3);
--card-content-padding-inline: var(--seeds-s6);
padding-block-end: var(--seeds-s4);
margin-bottom: var(--seeds-s4);
color: var(--seeds-text-color);
font-size: var(--seeds-type-caption-size);
header {
.text-heading-sm {
font-family: var(--seeds-font-sans);
color: var(--seeds-text-color-dark);
text-transform: uppercase;
font-weight: bold;
letter-spacing: .025rem;
}
}
}

#seeds-toast-stack {
--toast-stack-inset-top: calc(var(--seeds-s40) + var(--seeds-s2));
}
Expand Down

0 comments on commit 740fe3a

Please sign in to comment.