diff --git a/backend/__tests__/furnitureItems.json b/backend/__tests__/furnitureItems.json new file mode 100644 index 0000000..e16ab8f --- /dev/null +++ b/backend/__tests__/furnitureItems.json @@ -0,0 +1,266 @@ +[ + { + "category": "bedroom", + "name": "Twin mat.", + "allowMultiple": true, + "categoryIndex": 1 + }, + { + "category": "bedroom", + "name": "Full Mat.", + "allowMultiple": true, + "categoryIndex": 2 + }, + { + "category": "bedroom", + "name": "Queen Mat.", + "allowMultiple": true, + "categoryIndex": 3 + }, + { + "category": "bedroom", + "name": "King Mat.", + "allowMultiple": true, + "categoryIndex": 4 + }, + { + "category": "bedroom", + "name": "Matching Box Spring(s)", + "allowMultiple": true, + "categoryIndex": 5 + }, + { + "category": "bedroom", + "name": "Matching Bed Frame(s)", + "allowMultiple": true, + "categoryIndex": 6 + }, + { + "category": "bedroom", + "name": "Night Stand(s)", + "allowMultiple": true, + "categoryIndex": 7 + }, + { + "category": "bedroom", + "name": "Sheet Set(s)", + "allowMultiple": true, + "categoryIndex": 8 + }, + { + "category": "bedroom", + "name": "Blankets", + "allowMultiple": true, + "categoryIndex": 9 + }, + { + "category": "bedroom", + "name": "Dresser", + "allowMultiple": false, + "categoryIndex": 10 + }, + { + "category": "bathroom", + "name": "Bath Rug(s)", + "allowMultiple": true, + "categoryIndex": 1 + }, + { + "category": "bathroom", + "name": "Towel(s)", + "allowMultiple": true, + "categoryIndex": 2 + }, + { + "category": "bathroom", + "name": "Wash Cloth(s)", + "allowMultiple": true, + "categoryIndex": 3 + }, + { + "category": "kitchen", + "name": "Dish Set(s)", + "allowMultiple": true, + "categoryIndex": 1 + }, + { + "category": "kitchen", + "name": "Glasses", + "allowMultiple": true, + "categoryIndex": 2 + }, + { + "category": "kitchen", + "name": "Stove (Gas)", + "allowMultiple": false, + "categoryIndex": 3 + }, + { + "category": "kitchen", + "name": "Stove (Electric)", + "allowMultiple": false, + "categoryIndex": 4 + }, + { + "category": "kitchen", + "name": "Cups", + "allowMultiple": false, + "categoryIndex": 5 + }, + { + "category": "kitchen", + "name": "Silverware", + "allowMultiple": false, + "categoryIndex": 6 + }, + { + "category": "kitchen", + "name": "Pots and Pans", + "allowMultiple": false, + "categoryIndex": 7 + }, + { + "category": "kitchen", + "name": "Cooking Utensils", + "allowMultiple": false, + "categoryIndex": 8 + }, + { + "category": "kitchen", + "name": "Can Opener", + "allowMultiple": false, + "categoryIndex": 9 + }, + { + "category": "kitchen", + "name": "Refrigerator", + "allowMultiple": false, + "categoryIndex": 10 + }, + { + "category": "kitchen", + "name": "Microwave", + "allowMultiple": false, + "categoryIndex": 11 + }, + { + "category": "kitchen", + "name": "Toaster", + "allowMultiple": false, + "categoryIndex": 12 + }, + { + "category": "kitchen", + "name": "Coffee Pot", + "allowMultiple": false, + "categoryIndex": 13 + }, + { + "category": "living room", + "name": "Desk Lamp(s)", + "allowMultiple": true, + "categoryIndex": 1 + }, + { + "category": "living room", + "name": "Floor Lamp(s)", + "allowMultiple": true, + "categoryIndex": 2 + }, + { + "category": "living room", + "name": "End Table(s)", + "allowMultiple": true, + "categoryIndex": 3 + }, + { + "category": "living room", + "name": "Couch/Loveseat", + "allowMultiple": true, + "categoryIndex": 4 + }, + { + "category": "living room", + "name": "Rug(s)", + "allowMultiple": true, + "categoryIndex": 5 + }, + { + "category": "living room", + "name": "Couch", + "allowMultiple": false, + "categoryIndex": 6 + }, + { + "category": "living room", + "name": "Chair", + "allowMultiple": false, + "categoryIndex": 7 + }, + { + "category": "living room", + "name": "Coffee Table", + "allowMultiple": false, + "categoryIndex": 8 + }, + { + "category": "living room", + "name": "TV", + "allowMultiple": false, + "categoryIndex": 9 + }, + { + "category": "living room", + "name": "TV Stand", + "allowMultiple": false, + "categoryIndex": 10 + }, + { + "category": "living room", + "name": "TV/Stereo Wall Unit", + "allowMultiple": false, + "categoryIndex": 11 + }, + { + "category": "living room", + "name": "Desk", + "allowMultiple": false, + "categoryIndex": 12 + }, + { + "category": "dining room", + "name": "Chairs", + "allowMultiple": true, + "categoryIndex": 1 + }, + { + "category": "dining room", + "name": "China Cabinet/Buffet", + "allowMultiple": false, + "categoryIndex": 2 + }, + { + "category": "dining room", + "name": "Table", + "allowMultiple": false, + "categoryIndex": 3 + }, + { + "category": "other", + "name": "Dryer (Gas)", + "allowMultiple": false, + "categoryIndex": 1 + }, + { + "category": "other", + "name": "Dryer (Electric)", + "allowMultiple": false, + "categoryIndex": 2 + }, + { + "category": "other", + "name": "Washer", + "allowMultiple": false, + "categoryIndex": 3 + } +] diff --git a/backend/src/app.ts b/backend/src/app.ts index 69b9fbf..eaa6610 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -7,6 +7,7 @@ import cors from "cors"; import express, { NextFunction, Request, Response } from "express"; import { isHttpError } from "http-errors"; import vsrRoutes from "../src/routes/vsr"; +import furnitureItemRoutes from "../src/routes/furnitureItem"; import { userRouter } from "src/routes/users"; import env from "src/util/validateEnv"; @@ -54,5 +55,6 @@ app.use((error: unknown, req: Request, res: Response, next: NextFunction) => { }); app.use("/api/vsr", vsrRoutes); +app.use("/api/furnitureItems", furnitureItemRoutes); export default app; diff --git a/backend/src/controllers/furnitureItem.ts b/backend/src/controllers/furnitureItem.ts new file mode 100644 index 0000000..4b50372 --- /dev/null +++ b/backend/src/controllers/furnitureItem.ts @@ -0,0 +1,16 @@ +import { RequestHandler } from "express"; +import createHttpError from "http-errors"; +import FurnitureItemModel from "src/models/furnitureItem"; + +export const getFurnitureItems: RequestHandler = async (req, res, next) => { + try { + const furnitureItems = await FurnitureItemModel.find(); + + if (furnitureItems === null) { + throw createHttpError(404, "Furniture items not found"); + } + res.status(200).json(furnitureItems); + } catch (error) { + next(error); + } +}; diff --git a/backend/src/controllers/vsr.ts b/backend/src/controllers/vsr.ts index 5cb7be9..db2c37d 100644 --- a/backend/src/controllers/vsr.ts +++ b/backend/src/controllers/vsr.ts @@ -48,6 +48,8 @@ export const createVSR: RequestHandler = async (req, res, next) => { militaryID, petCompanion, hearFrom, + selectedFurnitureItems, + additionalItems, } = req.body; try { @@ -87,8 +89,12 @@ export const createVSR: RequestHandler = async (req, res, next) => { // Use current date as timestamp for received & updated dateReceived: currentDate, lastUpdated: currentDate, - }); + status: "Received", + + selectedFurnitureItems, + additionalItems, + }); // 201 means a new resource has been created successfully // the newly created VSR is sent back to the user res.status(201).json(vsr); @@ -97,6 +103,23 @@ export const createVSR: RequestHandler = async (req, res, next) => { } }; +export const updateStatus: RequestHandler = async (req, res, next) => { + try { + // extract any errors that were found by the validator + const errors = validationResult(req); + const { status } = req.body; + + // if there are errors, then this function throws an exception + validationErrorParser(errors); + + const { id } = req.params; + const vsr = await VSRModel.findByIdAndUpdate(id, { status }, { new: true }); + res.status(200).json(vsr); + } catch (error) { + next(error); + } +}; + export const getAllVSRS: RequestHandler = async (req, res, next) => { try { const vsrs = await VSRModel.find(); diff --git a/backend/src/models/furnitureItem.ts b/backend/src/models/furnitureItem.ts new file mode 100644 index 0000000..8258949 --- /dev/null +++ b/backend/src/models/furnitureItem.ts @@ -0,0 +1,13 @@ +import { InferSchemaType, Schema, model } from "mongoose"; + +const furnitureItemSchema = new Schema({ + _id: { type: String, required: true }, + category: { type: String, required: true }, + name: { type: String, required: true }, + allowMultiple: { type: Boolean, required: true }, + categoryIndex: { type: Number, required: true }, +}); + +type FurnitureItem = InferSchemaType; + +export default model("FurnitureItem", furnitureItemSchema); diff --git a/backend/src/models/vsr.ts b/backend/src/models/vsr.ts index 667d53b..b6a949d 100644 --- a/backend/src/models/vsr.ts +++ b/backend/src/models/vsr.ts @@ -1,40 +1,41 @@ import { InferSchemaType, Schema, model } from "mongoose"; +const furntitureInputSchema = new Schema({ + furnitureItemId: { type: String, required: true }, + quantity: { type: Number, required: true }, +}); + const vsrSchema = new Schema({ - name: { type: String, require: true }, - gender: { type: String, require: true }, - age: { type: Number, require: true }, - maritalStatus: { type: String, require: true }, + name: { type: String, required: true }, + gender: { type: String, required: true }, + age: { type: Number, required: true }, + maritalStatus: { type: String, required: true }, spouseName: { type: String }, agesOfBoys: { type: [Number] }, agesOfGirls: { type: [Number] }, - ethnicity: { type: [String], require: true }, - employmentStatus: { type: String, require: true }, - incomeLevel: { type: String, require: true }, - sizeOfHome: { type: String, require: true }, - streetAddress: { type: String, require: true }, - city: { type: String, require: true }, - state: { type: String, require: true }, - zipCode: { type: Number, require: true }, - phoneNumber: { type: String, require: true }, - email: { type: String, require: true }, - branch: { type: [String], require: true }, - conflicts: { type: [String], require: true }, - dischargeStatus: { type: String, require: true }, - serviceConnected: { type: String, require: true }, - lastRank: { type: String, require: true }, - militaryId: { type: Number, require: true }, - petCompanion: { type: String, require: true }, - hearFrom: { type: String, require: true }, - bedroomFurnishing: { type: [String], require: true }, - bathroomFurnishing: { type: [String], require: true }, - kitchenFurnishing: { type: [String], require: true }, - livingRoomFurnishing: { type: [String], require: true }, - diningRoomFurnishing: { type: [String], require: true }, - otherFurnishing: { type: [String], require: true }, - dateReceived: { type: Date, require: true }, - lastUpdated: { type: Date, require: true }, - status: { type: String, require: true }, + ethnicity: { type: [String], required: true }, + employmentStatus: { type: String, required: true }, + incomeLevel: { type: String, required: true }, + sizeOfHome: { type: String, required: true }, + streetAddress: { type: String, required: true }, + city: { type: String, required: true }, + state: { type: String, required: true }, + zipCode: { type: Number, required: true }, + phoneNumber: { type: String, required: true }, + email: { type: String, required: true }, + branch: { type: [String], required: true }, + conflicts: { type: [String], required: true }, + dischargeStatus: { type: String, required: true }, + serviceConnected: { type: Boolean, required: true }, + lastRank: { type: String, required: true }, + militaryID: { type: Number, required: true }, + petCompanion: { type: Boolean, required: true }, + hearFrom: { type: String, required: true }, + selectedFurnitureItems: { type: [furntitureInputSchema], required: true }, + additionalItems: { type: String, required: false }, + dateReceived: { type: Date, required: true }, + lastUpdated: { type: Date, required: true }, + status: { type: String, required: true }, }); type VSR = InferSchemaType; diff --git a/backend/src/routes/furnitureItem.ts b/backend/src/routes/furnitureItem.ts new file mode 100644 index 0000000..3af27bf --- /dev/null +++ b/backend/src/routes/furnitureItem.ts @@ -0,0 +1,8 @@ +import express from "express"; +import * as FurnitureItemController from "src/controllers/furnitureItem"; + +const router = express.Router(); + +router.get("/", FurnitureItemController.getFurnitureItems); + +export default router; diff --git a/backend/src/routes/vsr.ts b/backend/src/routes/vsr.ts index f981e34..8488faf 100644 --- a/backend/src/routes/vsr.ts +++ b/backend/src/routes/vsr.ts @@ -1,4 +1,5 @@ import express from "express"; + import * as VSRController from "src/controllers/vsr"; import * as VSRValidator from "src/validators/vsr"; @@ -7,5 +8,6 @@ const router = express.Router(); router.get("/:id", VSRController.getVSR); router.post("/", VSRValidator.createVSR, VSRController.createVSR); router.get("/", VSRController.getAllVSRS); +router.patch("/:id/status", VSRValidator.updateStatus, VSRController.updateStatus); export default router; diff --git a/backend/src/validators/vsr.ts b/backend/src/validators/vsr.ts index ccd8b66..e332037 100644 --- a/backend/src/validators/vsr.ts +++ b/backend/src/validators/vsr.ts @@ -151,7 +151,7 @@ const makeDischargeStatusValidator = () => const makeServiceConnectedValidator = () => body("serviceConnected") - .exists({ checkFalsy: true }) + .exists({ checkFalsy: false }) .withMessage("Service Connected is required") .isBoolean() .withMessage("Service Connected must be a boolean"); @@ -172,7 +172,7 @@ const makeMilitaryIDValidator = () => const makePetCompanionValidator = () => body("petCompanion") - .exists({ checkFalsy: true }) + .exists({ checkFalsy: false }) .withMessage("Pet interest is required") .isBoolean() .withMessage("Pet interest must be a boolean"); @@ -184,6 +184,24 @@ const makeHearFromValidator = () => .isString() .withMessage("Referral source must be a string"); +const ALLOWED_STATUSES = [ + "Received", + "Appointment Scheduled", + "Approved", + "Resubmit", + "No-show / Incomplete", + "Archived", +]; + +const updateStatusValidator = () => + body("status") + .exists({ checkFalsy: true }) + .withMessage("Status is required") + .isString() + .withMessage("Status must be a string") + .isIn(ALLOWED_STATUSES) + .withMessage("Status must be one of the allowed options"); + export const createVSR = [ makeNameValidator(), makeGenderValidator(), @@ -211,3 +229,5 @@ export const createVSR = [ makePetCompanionValidator(), makeHearFromValidator(), ]; + +export const updateStatus = [updateStatusValidator()]; diff --git a/frontend/__tests__/sampleData/pap.vsrs.json b/frontend/__tests__/sampleData/pap.vsrs.json index 6f7d040..b68ad38 100644 --- a/frontend/__tests__/sampleData/pap.vsrs.json +++ b/frontend/__tests__/sampleData/pap.vsrs.json @@ -22,19 +22,14 @@ "streetAddress": "13571 Marguerite Creek Way", "zipCode": "92130", "agesOfBoys": [10], - "bedroomFurnishing": [], + "selectedFurnitureItems": [], "dischargeStatus": "dischargeStatus-1", "lastRank": "lastRank-1", "militaryId": 1030, "petCompanion": "Yes-1", "serviceConnected": "Yes-1", - "bathroomFurnishing": ["Toiletries"], "dateReceived": "2024-02-26T03:43:15.152Z", - "diningRoomFurnishing": ["Chairs", "Table"], - "kitchenFurnishing": ["Oven"], "lastUpdated": "2024-02-26T03:43:15.152Z", - "livingRoomFurnishing": ["Couch"], - "otherFurnishing": ["Lawn Chair"], "status": "Received", "hearFrom": "Family-1" } diff --git a/frontend/public/icon_minus.svg b/frontend/public/icon_minus.svg new file mode 100644 index 0000000..29b6021 --- /dev/null +++ b/frontend/public/icon_minus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/icon_plus.svg b/frontend/public/icon_plus.svg new file mode 100644 index 0000000..4b551c4 --- /dev/null +++ b/frontend/public/icon_plus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/src/api/FurnitureItems.ts b/frontend/src/api/FurnitureItems.ts new file mode 100644 index 0000000..edb35d2 --- /dev/null +++ b/frontend/src/api/FurnitureItems.ts @@ -0,0 +1,35 @@ +import { APIResult, handleAPIError, get } from "@/api/requests"; +export interface FurnitureItemJson { + _id: string; + category: string; + name: string; + allowMultiple: boolean; + categoryIndex: number; +} +export interface FurnitureItem { + _id: string; + category: string; + name: string; + allowMultiple: boolean; + categoryIndex: number; +} + +function parseFurnitureItem(furnitureItem: FurnitureItemJson) { + return { + _id: furnitureItem._id, + category: furnitureItem.category, + name: furnitureItem.name, + allowMultiple: furnitureItem.allowMultiple, + categoryIndex: furnitureItem.categoryIndex, + }; +} +export async function getFurnitureItems(): Promise> { + try { + const response = await get(`/api/furnitureItems`); + const json = (await response.json()) as FurnitureItemJson[]; + const furnitureItems = json.map(parseFurnitureItem); + return { success: true, data: furnitureItems }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/api/VSRs.ts b/frontend/src/api/VSRs.ts index 2e6e73f..035f41b 100644 --- a/frontend/src/api/VSRs.ts +++ b/frontend/src/api/VSRs.ts @@ -1,4 +1,9 @@ -import { APIResult, get, handleAPIError, post } from "@/api/requests"; +import { APIResult, get, handleAPIError, patch, post } from "@/api/requests"; + +export interface FurnitureInput { + furnitureItemId: string; + quantity: number; +} export interface VSRJson { _id: string; @@ -24,14 +29,10 @@ export interface VSRJson { dischargeStatus: string; serviceConnected: boolean; lastRank: string; - militaryId: number; + militaryID: number; petCompanion: boolean; - bedroomFurnishing: string[]; - bathroomFurnishing: string[]; - kitchenFurnishing: string[]; - livingRoomFurnishing: string[]; - diningRoomFurnishing: string[]; - otherFurnishing: string[]; + selectedFurnitureItems: FurnitureInput[]; + additionalItems: string; dateReceived: string; lastUpdated: string; status: string; @@ -66,14 +67,10 @@ export interface VSR { dischargeStatus: string; serviceConnected: boolean; lastRank: string; - militaryId: number; + militaryID: number; petCompanion: boolean; - bedroomFurnishing: string[]; - bathroomFurnishing: string[]; - kitchenFurnishing: string[]; - livingRoomFurnishing: string[]; - diningRoomFurnishing: string[]; - otherFurnishing: string[]; + selectedFurnitureItems: FurnitureInput[]; + additionalItems: string; dateReceived: Date; lastUpdated: Date; status: string; @@ -106,15 +103,8 @@ export interface CreateVSRRequest { militaryID: number; petCompanion: boolean; hearFrom: string; - - // Comment-out page 3 fields for now because they're not implemented on the form yet - // bedroomFurnishing: string[]; - // bathroomFurnishing: string[]; - // kitchenFurnishing: string[]; - // livingRoomFurnishing: string[]; - // diningRoomFurnishing: string[]; - // otherFurnishing: string[]; - // status: string; + selectedFurnitureItems: FurnitureInput[]; + additionalItems: string; } function parseVSR(vsr: VSRJson) { @@ -142,14 +132,10 @@ function parseVSR(vsr: VSRJson) { dischargeStatus: vsr.dischargeStatus, serviceConnected: vsr.serviceConnected, lastRank: vsr.lastRank, - militaryId: vsr.militaryId, + militaryID: vsr.militaryID, petCompanion: vsr.petCompanion, - bedroomFurnishing: vsr.bedroomFurnishing, - bathroomFurnishing: vsr.bathroomFurnishing, - kitchenFurnishing: vsr.kitchenFurnishing, - livingRoomFurnishing: vsr.livingRoomFurnishing, - diningRoomFurnishing: vsr.diningRoomFurnishing, - otherFurnishing: vsr.otherFurnishing, + selectedFurnitureItems: vsr.selectedFurnitureItems, + additionalItems: vsr.additionalItems, dateReceived: new Date(vsr.dateReceived), lastUpdated: new Date(vsr.lastUpdated), status: vsr.status, @@ -186,3 +172,13 @@ export async function getVSR(id: string): Promise> { return handleAPIError(error); } } + +export async function updateVSRStatus(id: string, status: string): Promise> { + try { + const response = await patch(`/api/vsr/${id}/status`, { status }); + const json = (await response.json()) as VSRJson; + return { success: true, data: parseVSR(json) }; + } catch (error) { + return handleAPIError(error); + } +} diff --git a/frontend/src/api/requests.ts b/frontend/src/api/requests.ts index bc0883d..691e4ce 100644 --- a/frontend/src/api/requests.ts +++ b/frontend/src/api/requests.ts @@ -1,7 +1,7 @@ import env from "@/util/validateEnv"; const API_BASE_URL = env.NEXT_PUBLIC_BACKEND_URL; -type Method = "GET" | "POST" | "PUT"; +type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; /** * A wrapper around the built-in `fetch()` function that abstracts away some of @@ -114,6 +114,24 @@ export async function put( return response; } +/** + * Sends a PATCH request to the provided API URL. + * + * @param url The URL to request + * @param body The body of the request, or undefined if there is none + * @param headers The headers of the request (optional) + * @returns The Response object returned by `fetch()` + */ +export async function patch( + url: string, + body: unknown, + headers: Record = {}, +): Promise { + const response = await fetchRequest("PATCH", API_BASE_URL + url, body, headers); + assertOk(response); + return response; +} + export type APIData = { success: true; data: T }; export type APIError = { success: false; error: string }; /** diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 6767c5d..bcafc39 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,14 +1,14 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; - -const Home = () => { - const router = useRouter(); - useEffect(() => { - router.push("/login"); - }); - return null; -}; - -export default Home; +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +const Home = () => { + const router = useRouter(); + useEffect(() => { + router.push("/login"); + }); + return null; +}; + +export default Home; diff --git a/frontend/src/app/staff/vsr/page.module.css b/frontend/src/app/staff/vsr/page.module.css index 0dd9982..0920eaf 100644 --- a/frontend/src/app/staff/vsr/page.module.css +++ b/frontend/src/app/staff/vsr/page.module.css @@ -82,7 +82,7 @@ text-align: center; /* Desktop/Body 2 */ - font-family: "OpenSans"; + font-family: "Open Sans"; font-size: 16px; font-style: normal; font-weight: 400; diff --git a/frontend/src/app/staff/vsr/page.tsx b/frontend/src/app/staff/vsr/page.tsx index 8d65690..716a2fa 100644 --- a/frontend/src/app/staff/vsr/page.tsx +++ b/frontend/src/app/staff/vsr/page.tsx @@ -7,7 +7,7 @@ import PageTitle from "@/components/VSRTable/PageTitle"; import HeaderBar from "@/components/shared/HeaderBar"; import Image from "next/image"; import React from "react"; -import { DropdownDetail } from "@/components/VSRIndividual"; +import { StatusDropdown } from "@/components/VSRIndividual"; export default function VSRTableView() { return ( @@ -22,7 +22,7 @@ export default function VSRTableView() {

Status:

- +
diff --git a/frontend/src/app/vsr/page.module.css b/frontend/src/app/vsr/page.module.css index dde5d44..c520a45 100644 --- a/frontend/src/app/vsr/page.module.css +++ b/frontend/src/app/vsr/page.module.css @@ -104,6 +104,7 @@ height: 64px; background-color: #102d5f; color: white; + cursor: pointer; font-family: Lora; font-size: 24px; font-style: normal; @@ -114,7 +115,6 @@ } .enabled { background-color: #102d5f; - cursor: pointer; } .disabled { @@ -158,3 +158,100 @@ .childInputWrapper { width: 100%; } + +.page { + margin: 0; + box-sizing: border-box; + background-color: var(--color-tse-primary-light); +} +.canvas { + border-radius: 20px; + background-color: #fff; + width: calc(100vw * 5 / 6); + margin: auto; + margin-top: 128px; + padding: 64px; +} +.title { + color: var(--Accent-Blue-1, #102d5f); + font-family: "Lora"; + font-size: 24px; + font-style: normal; + font-weight: 700; + line-height: normal; +} +.sections { + padding-top: 32px; +} +.section:not(:last-child) { + padding-bottom: 64px; +} + +.furnitureItemsSection { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 4px; + flex: 1 0 0; + font-size: 16px; + color: var(--Light-Gray, #818181); + font-family: "OpenSans"; + font-style: normal; + font-weight: 400; + line-height: normal; + margin-bottom: 64px; +} + +.furnitureItemsSectionLabel { + padding-bottom: 16px; + /* Capitalize names of furniture categories */ + text-transform: capitalize; +} + +.chipContainer { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16px; +} + +.actions { + display: flex; + flex-direction: row; + justify-content: space-between; + width: calc(100vw * 5 / 6); + margin: auto; + align-items: baseline; + padding-top: 64px; + padding-bottom: 128px; +} + +.submit { + width: 306px; + height: 64px; + color: white; + cursor: pointer; + font-family: "Lora"; + font-size: 24px; + font-style: normal; + font-weight: 700; + line-height: normal; + border: none; + border-radius: 4px; +} + +.back { + width: 306px; + height: 64px; + background-color: var(--color-tse-primary-light); + color: #102d5f; + cursor: pointer; + font-family: "Lora"; + font-size: 24px; + font-style: normal; + font-weight: 700; + line-height: normal; + border-radius: 4px; + border: 1px solid var(--Secondary-1, #102d5f); + background: rgba(255, 255, 255, 0); +} diff --git a/frontend/src/app/vsr/page.tsx b/frontend/src/app/vsr/page.tsx index 54db3de..1fdbad1 100644 --- a/frontend/src/app/vsr/page.tsx +++ b/frontend/src/app/vsr/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useState, useEffect } from "react"; +import React, { useEffect, useState } from "react"; import styles from "src/app/vsr/page.module.css"; import { useForm, Controller, SubmitHandler } from "react-hook-form"; import TextField from "@/components/shared/input/TextField"; @@ -7,8 +7,10 @@ import MultipleChoice from "@/components/shared/input/MultipleChoice"; import Dropdown from "@/components/shared/input/Dropdown"; import HeaderBar from "@/components/shared/HeaderBar"; import PageNumber from "@/components/VSRForm/PageNumber"; -import { createVSR, CreateVSRRequest } from "@/api/VSRs"; -import BinaryChoice from "@/components/BinaryChoice"; +import { createVSR, CreateVSRRequest, FurnitureInput } from "@/api/VSRs"; +import { FurnitureItem, getFurnitureItems } from "@/api/FurnitureItems"; +import BinaryChoice from "@/components/shared/input/BinaryChoice"; +import { FurnitureItemSelection } from "@/components/VeteranForm/FurnitureItemSelection"; interface IFormInput { name: string; @@ -59,7 +61,6 @@ const VeteranServiceRequest: React.FC = () => { const [selectedHearFrom, setSelectedHearFrom] = useState(""); const [otherHearFrom, setOtherHearFrom] = useState(""); - const [isFormValid, setIsFormValid] = useState(false); const [pageNumber, setPageNumber] = useState(1); @@ -208,6 +209,50 @@ const VeteranServiceRequest: React.FC = () => { "WY", ]; + const [errorMessage, setErrorMessage] = useState(null); + + const [furnitureCategoriesToItems, setFurnitureCategoriesToItems] = + useState>(); + // Map furniture item IDs to selections for those items + const [selectedFurnitureItems, setSelectedFurnitureItems] = useState< + Record + >({}); + + const [additionalItems, setAdditionalItems] = useState(""); + + // Fetch all available furniture items from database + useEffect(() => { + getFurnitureItems() + .then((result) => { + if (result.success) { + setFurnitureCategoriesToItems( + result.data.reduce( + (prevMap: Record, curItem) => ({ + ...prevMap, + [curItem.category]: [...(prevMap[curItem.category] ?? []), curItem], + }), + {}, + ), + ); + setErrorMessage(null); + } else { + setErrorMessage("Furniture items not found."); + } + }) + .catch((error) => { + setErrorMessage(`An error occurred: ${error.message}`); + }); + }, []); + + // Handle furniture item count whenever a change is made + const handleSelectionChange = (newSelection: FurnitureInput) => { + setSelectedFurnitureItems((prevItems) => ({ + ...prevItems, + [newSelection.furnitureItemId]: newSelection, + })); + }; + + // Execute when submit button is pressed const onSubmit: SubmitHandler = async (data) => { // Construct the request object const createVSRRequest: CreateVSRRequest = { @@ -228,6 +273,7 @@ const VeteranServiceRequest: React.FC = () => { employmentStatus: data.employment_status, incomeLevel: data.income_level, sizeOfHome: data.size_of_home, + streetAddress: data.streetAddress, city: data.city, state: data.state, @@ -242,6 +288,12 @@ const VeteranServiceRequest: React.FC = () => { militaryID: data.militaryID, petCompanion: data.petCompanion, hearFrom: data.hearFrom, + + // Only submit items that the user selected at least 1 of + selectedFurnitureItems: Object.values(selectedFurnitureItems).filter( + (selectedItem) => selectedItem.quantity > 0, + ), + additionalItems, }; try { @@ -259,16 +311,11 @@ const VeteranServiceRequest: React.FC = () => { } }; - useEffect(() => { - setIsFormValid(isValid); - }, [isValid]); - - const incrementPage: SubmitHandler = () => { + const incrementPageNumber = () => { setPageNumber(pageNumber + 1); }; - const decrementPageNumber = (e: React.FormEvent) => { - e.preventDefault(); + const decrementPageNumber = () => { setPageNumber(pageNumber - 1); }; @@ -330,7 +377,7 @@ const VeteranServiceRequest: React.FC = () => { if (pageNumber == 1) { return (
-
+

Veteran Service Request Form

@@ -561,7 +608,10 @@ const VeteranServiceRequest: React.FC = () => {
-
@@ -570,10 +620,10 @@ const VeteranServiceRequest: React.FC = () => { ); - } else { + } else if (pageNumber === 2) { return (
-
+
@@ -912,11 +962,10 @@ const VeteranServiceRequest: React.FC = () => {
@@ -924,6 +973,64 @@ const VeteranServiceRequest: React.FC = () => {
); + } else { + return ( +
+
+ +
+
Furnishings
+
+ {Object.entries(furnitureCategoriesToItems ?? {}).map(([category, items]) => ( +
+

{category}

+
+ {(items ?? []).map((furnitureItem) => ( + handleSelectionChange(newSelection)} + /> + ))} +
+
+ ))} +
+ setAdditionalItems(e.target.value)} + > +
+
+
+
+
+ +
+ +
+ +
+
+ +
+ {/* TODO: better error handling */} + {errorMessage} +
+ ); } }; diff --git a/frontend/src/components/VSRIndividual/CaseDetails/index.tsx b/frontend/src/components/VSRIndividual/CaseDetails/index.tsx index db6cfb6..834153d 100644 --- a/frontend/src/components/VSRIndividual/CaseDetails/index.tsx +++ b/frontend/src/components/VSRIndividual/CaseDetails/index.tsx @@ -1,20 +1,46 @@ import styles from "src/components/VSRIndividual/CaseDetails/styles.module.css"; -import { SingleDetail, DropdownDetail } from "@/components/VSRIndividual"; -import { type VSR } from "@/api/VSRs"; +import { SingleDetail, StatusDropdown } from "@/components/VSRIndividual"; +import { updateVSRStatus, type VSR } from "@/api/VSRs"; import moment from "moment"; -import { VSRIndividualAccordion } from "../VSRIndividualAccordion"; +import { VSRIndividualAccordion } from "@/components/VSRIndividual/VSRIndividualAccordion"; +import { STATUS_OPTIONS } from "@/components/shared/StatusDropdown"; +import { StatusChip } from "@/components/shared/StatusChip"; export interface CaseDetailsProp { vsr: VSR; + onUpdateVSR: (status: VSR) => void; } -export const CaseDetails = ({ vsr }: CaseDetailsProp) => { - /** - * Formats a Date object as a string in our desired format, using Moment.js library - */ - const formatDate = (date: Date) => { - const dateMoment = moment(date); - // We need to do 2 separate format() calls because Moment treats brackets ("[]") as escape chars - return `${dateMoment.format("MM-DD-YYYY")} [${dateMoment.format("hh:mm A")}]`; + +/** + * Formats a Date object as a string in our desired format, using Moment.js library + */ +const formatDate = (date: Date) => { + const dateMoment = moment(date); + // We need to do 2 separate format() calls because Moment treats brackets ("[]") as escape chars + return `${dateMoment.format("MM-DD-YYYY")} [${dateMoment.format("hh:mm A")}]`; +}; + +export const CaseDetails = ({ vsr, onUpdateVSR }: CaseDetailsProp) => { + const renderStatus = () => { + if (vsr.status === "Received" || vsr.status === undefined) { + return ( + statusOption.value === "Received")!} + /> + ); + } + return ( + { + const res = await updateVSRStatus(vsr._id, status); + + // TODO: error handling + + onUpdateVSR(res.success ? res.data : vsr); + }} + value={vsr.status != undefined ? vsr.status : "Received"} + /> + ); }; return ( @@ -32,10 +58,7 @@ export const CaseDetails = ({ vsr }: CaseDetailsProp) => { valueFontSize="20px" /> - } - /> +
); diff --git a/frontend/src/components/VSRIndividual/MilitaryBackground/index.tsx b/frontend/src/components/VSRIndividual/MilitaryBackground/index.tsx index 6edea5e..abd6d5e 100644 --- a/frontend/src/components/VSRIndividual/MilitaryBackground/index.tsx +++ b/frontend/src/components/VSRIndividual/MilitaryBackground/index.tsx @@ -36,7 +36,7 @@ export const MilitaryBackground = ({ vsr }: MilitaryBackgroundProps) => {
- +
); diff --git a/frontend/src/components/VSRIndividual/Page/index.tsx b/frontend/src/components/VSRIndividual/Page/index.tsx index 40d312e..4d422bb 100644 --- a/frontend/src/components/VSRIndividual/Page/index.tsx +++ b/frontend/src/components/VSRIndividual/Page/index.tsx @@ -11,13 +11,32 @@ import { } from "@/components/VSRIndividual"; import styles from "src/components/VSRIndividual/Page/styles.module.css"; import Image from "next/image"; -import { type VSR, getVSR } from "@/api/VSRs"; +import { type VSR, getVSR, updateVSRStatus } from "@/api/VSRs"; import { useParams } from "next/navigation"; +import { FurnitureItem, getFurnitureItems } from "@/api/FurnitureItems"; export const Page = () => { const [vsr, setVSR] = useState({} as VSR); const { id } = useParams(); const [errorMessage, setErrorMessage] = useState(null); + const [furnitureItems, setFurnitureItems] = useState(); + + const renderApproveButton = () => ( + + ); useEffect(() => { getVSR(id as string) @@ -33,6 +52,24 @@ export const Page = () => { setErrorMessage(`An error occurred: ${error.message}`); }); }, [id]); + + // Fetch all available furniture items from database + useEffect(() => { + getFurnitureItems() + .then((result) => { + if (result.success) { + setFurnitureItems(result.data); + + setErrorMessage(null); + } else { + setErrorMessage("Furniture items not found."); + } + }) + .catch((error) => { + setErrorMessage(`An error occurred: ${error.message}`); + }); + }, []); + return (
@@ -65,7 +102,7 @@ export const Page = () => {
- +
@@ -74,11 +111,11 @@ export const Page = () => {
- +
- + {vsr.status == "Received" || vsr.status === undefined + ? renderApproveButton() + : null}
diff --git a/frontend/src/components/VSRIndividual/Page/styles.module.css b/frontend/src/components/VSRIndividual/Page/styles.module.css index 5db6372..08beac9 100644 --- a/frontend/src/components/VSRIndividual/Page/styles.module.css +++ b/frontend/src/components/VSRIndividual/Page/styles.module.css @@ -20,6 +20,7 @@ font-weight: 400; line-height: normal; cursor: pointer; + background-color: transparent; } .toDashboard > * { @@ -129,8 +130,8 @@ margin-right: 32px; } -.approve { - width: calc(100% * 207 / 713); +.approveButton { + width: 207px; border-radius: 4px; border: 1px solid var(--color-tse-secondary-1); color: white; @@ -143,6 +144,7 @@ background: var(--color-tse-secondary-1); align-items: end; padding: 20px 24px; + cursor: pointer; } .footer { diff --git a/frontend/src/components/VSRIndividual/RequestedFurnishings/index.tsx b/frontend/src/components/VSRIndividual/RequestedFurnishings/index.tsx index f088418..c1d4bb7 100644 --- a/frontend/src/components/VSRIndividual/RequestedFurnishings/index.tsx +++ b/frontend/src/components/VSRIndividual/RequestedFurnishings/index.tsx @@ -2,79 +2,75 @@ import styles from "src/components/VSRIndividual/RequestedFurnishings/styles.mod import { SingleDetail, ListDetail } from "@/components/VSRIndividual"; import { type VSR } from "@/api/VSRs"; import { VSRIndividualAccordion } from "../VSRIndividualAccordion"; +import { FurnitureItem } from "@/api/FurnitureItems"; +import { useMemo } from "react"; export interface RequestedFurnishingsProps { vsr: VSR; + furnitureItems: FurnitureItem[]; } -export const RequestedFurnishings = ({ vsr }: RequestedFurnishingsProps) => { - return ( - -
- 0 - ? vsr.bedroomFurnishing - : ["N/A"] - } - /> -
-
- 0 - ? vsr.bathroomFurnishing - : ["N/A"] - } - /> -
-
- 0 - ? vsr.kitchenFurnishing - : ["N/A"] - } - /> -
-
- 0 - ? vsr.livingRoomFurnishing - : ["N/A"] - } - /> -
+export const RequestedFurnishings = ({ vsr, furnitureItems }: RequestedFurnishingsProps) => { + const furnitureItemIdsToItems = useMemo( + () => + furnitureItems?.reduce( + (prevMap: Record, curItem) => ({ + ...prevMap, + [curItem._id]: curItem, + }), + {}, + ) ?? {}, + [furnitureItems], + ); + + const findSelectedItemsByCategory = (category: string) => { + return vsr.selectedFurnitureItems?.filter( + (selectedItem) => + furnitureItemIdsToItems[selectedItem.furnitureItemId]?.category === category, + ); + }; + + const renderItemsSection = (categoryTitle: string, categoryName: string) => { + const selectedItemsForCategory = findSelectedItemsByCategory(categoryName); + + return (
0 - ? vsr.diningRoomFurnishing + selectedItemsForCategory && selectedItemsForCategory.length > 0 + ? selectedItemsForCategory.map( + (selectedItem) => + `${furnitureItemIdsToItems[selectedItem.furnitureItemId].name} ${ + furnitureItemIdsToItems[selectedItem.furnitureItemId]?.allowMultiple + ? ": " + selectedItem.quantity + : "" + }`, + ) : ["N/A"] } />
+ ); + }; + + return ( + + {renderItemsSection("Bedroom:", "bedroom")} + {renderItemsSection("Bathroom:", "bathroom")} + {renderItemsSection("Kitchen:", "kitchen")} + {renderItemsSection("Living Room:", "living room")} + {renderItemsSection("Dining Room:", "dining room")} + {renderItemsSection("Other:", "other")} +
- 0 - ? vsr.otherFurnishing - : ["N/A"] + 0 ? vsr.additionalItems : "n/a" } />
-
- -
); }; diff --git a/frontend/src/components/VSRIndividual/index.ts b/frontend/src/components/VSRIndividual/index.ts index 7ce2b44..f17bdd4 100644 --- a/frontend/src/components/VSRIndividual/index.ts +++ b/frontend/src/components/VSRIndividual/index.ts @@ -8,5 +8,5 @@ export { MilitaryBackground } from "./MilitaryBackground"; export { AdditionalInfo } from "./AdditionalInfo"; export { RequestedFurnishings } from "./RequestedFurnishings"; export { SingleDetail } from "./SingleDetail"; -export { DropdownDetail } from "../shared/StatusDropdown"; +export { StatusDropdown } from "../shared/StatusDropdown"; export { ListDetail } from "./ListDetail"; diff --git a/frontend/src/components/VSRTable/VSRTable/index.tsx b/frontend/src/components/VSRTable/VSRTable/index.tsx index 0de4f02..29d692e 100644 --- a/frontend/src/components/VSRTable/VSRTable/index.tsx +++ b/frontend/src/components/VSRTable/VSRTable/index.tsx @@ -34,7 +34,7 @@ const columns: GridColDef[] = [ hideSortIcons: true, }, { - field: "militaryId", + field: "militaryID", headerName: "Military ID (Last 4)", type: "string", flex: 1, diff --git a/frontend/src/components/VeteranForm/FurnitureItemSelection/index.tsx b/frontend/src/components/VeteranForm/FurnitureItemSelection/index.tsx new file mode 100644 index 0000000..4395633 --- /dev/null +++ b/frontend/src/components/VeteranForm/FurnitureItemSelection/index.tsx @@ -0,0 +1,88 @@ +import { FurnitureItem } from "@/api/FurnitureItems"; +import { FurnitureInput } from "@/api/VSRs"; +import styles from "@/components/VeteranForm/FurnitureItemSelection/styles.module.css"; +import Image from "next/image"; + +export interface FurnitureItemSelectionProps { + furnitureItem: FurnitureItem; + selection: FurnitureInput; + onChangeSelection: (newSelection: FurnitureInput) => unknown; +} + +export const FurnitureItemSelection = ({ + furnitureItem, + selection, + onChangeSelection, +}: FurnitureItemSelectionProps) => { + const handleChipClicked = () => { + if (selection.quantity === 0) { + incrementCount(); + } else if (!furnitureItem.allowMultiple) { + onChangeSelection({ ...selection, quantity: 0 }); + } + }; + + const incrementCount = () => { + onChangeSelection({ ...selection, quantity: selection.quantity + 1 }); + }; + + const decrementCount = () => { + if (selection.quantity > 0) { + onChangeSelection({ ...selection, quantity: selection.quantity - 1 }); + } + }; + + return ( +
0 ? styles.chipSelected : styles.chipUnselected + }`} + onClick={handleChipClicked} + > +
+ {furnitureItem.name} + {furnitureItem.allowMultiple ? ( + <> + + {selection.quantity} + + + ) : null} +
+
+ ); +}; diff --git a/frontend/src/components/VeteranForm/FurnitureItemSelection/styles.module.css b/frontend/src/components/VeteranForm/FurnitureItemSelection/styles.module.css new file mode 100644 index 0000000..c7f73be --- /dev/null +++ b/frontend/src/components/VeteranForm/FurnitureItemSelection/styles.module.css @@ -0,0 +1,86 @@ +.chip { + white-space: nowrap; + border-width: 1px; + border-style: solid; + text-align: center; + font-family: "OpenSans"; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: normal; + border-radius: 64px; + border: 1px solid var(--Secondary-1, #102d5f); +} + +.chipContent { + display: flex; + flex-direction: row; + padding: 9px 16px; + align-content: baseline; +} + +.chipTitle { + padding-right: 8px; +} + +.chip:hover { + cursor: pointer; + background: #c7def1; +} + +.chipSelected { + color: white; + background: #102d5f; +} + +.chipSelected:hover { + cursor: pointer; + background: #102d5f; +} + +.chipUnselected { + color: var(--Accent-Blue-1, #102d5f); + background: rgba(255, 255, 255, 0); +} + +/*Need exact color for this*/ +.chipUnselected:hover { + background: rgb(213, 232, 239); +} + +.requiredAsterisk { + color: var(--Secondary-2, #be2d46); + text-align: left; +} + +.helperText { + color: var(--Secondary-2, #be2d46); + font-size: 12px; +} + +.math { + flex-direction: row; + all: unset; + width: 20px; + height: 20px; +} + +.dec { + filter: brightness(0) saturate(100%) invert(92%) sepia(3%) saturate(3443%) hue-rotate(178deg) + brightness(97%) contrast(94%); +} + +.decSelected { + filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(36deg) + brightness(103%) contrast(103%); +} + +.inc { + filter: brightness(0) saturate(100%) invert(10%) sepia(70%) saturate(2513%) hue-rotate(208deg) + brightness(95%) contrast(92%); +} + +.incSelected { + filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(36deg) + brightness(103%) contrast(103%); +} diff --git a/frontend/src/components/shared/StatusDropdown/index.tsx b/frontend/src/components/shared/StatusDropdown/index.tsx index d644e41..eaa6eec 100644 --- a/frontend/src/components/shared/StatusDropdown/index.tsx +++ b/frontend/src/components/shared/StatusDropdown/index.tsx @@ -2,7 +2,7 @@ import React, { useState } from "react"; import styles from "@/components/shared/StatusDropdown/styles.module.css"; import MenuItem from "@mui/material/MenuItem"; import FormControl from "@mui/material/FormControl"; -import Select from "@mui/material/Select"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; import Image from "next/image"; import { StatusChip } from "@/components/shared/StatusChip"; @@ -38,16 +38,18 @@ export const STATUS_OPTIONS: StatusOption[] = [ }, ]; -export interface DropdownDetailProps { +export interface StatusDropdownProps { value: string; + onChanged?: (value: string) => void; } -export function DropdownDetail({ value }: DropdownDetailProps) { +export function StatusDropdown({ value, onChanged }: StatusDropdownProps) { const [selectedValue, setSelectedValue] = useState(value); const [isOpen, setIsOpen] = useState(false); - const handleChange = (event: { target: { value: React.SetStateAction } }) => { + const handleChange = (event: SelectChangeEvent) => { setSelectedValue(event.target.value); + onChanged?.(event.target.value); }; const DropdownIcon = () => ( diff --git a/frontend/src/components/BinaryChoice.tsx b/frontend/src/components/shared/input/BinaryChoice/index.tsx similarity index 87% rename from frontend/src/components/BinaryChoice.tsx rename to frontend/src/components/shared/input/BinaryChoice/index.tsx index a1ca506..a459430 100644 --- a/frontend/src/components/BinaryChoice.tsx +++ b/frontend/src/components/shared/input/BinaryChoice/index.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import Chip from "@mui/material/Chip"; -import styles from "@/components/shared/input/MultipleChoice/styles.module.css"; +import styles from "@/components/shared/input/BinaryChoice/styles.module.css"; export interface BinaryChoiceProps { label: string; @@ -14,14 +14,7 @@ export interface BinaryChoiceProps { const BinaryChoice = ({ label, value, onChange, required, helperText }: BinaryChoiceProps) => { const [selectedOption, setSelectedOption] = useState(value); - /* - useEffect(() => { - setSelectedOption(value); - }, [value]); - */ - const handleOptionClick = (newOption: boolean | null) => { - console.log("Current value:", newOption); setSelectedOption(newOption); onChange(newOption); }; diff --git a/frontend/src/components/shared/input/BinaryChoice/styles.module.css b/frontend/src/components/shared/input/BinaryChoice/styles.module.css new file mode 100644 index 0000000..2f69852 --- /dev/null +++ b/frontend/src/components/shared/input/BinaryChoice/styles.module.css @@ -0,0 +1,64 @@ +/* BinaryChoice.module.css */ + +.wrapperClass { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + flex: 1 0 0; + font-size: 16px; + color: var(--Light-Gray, #818181); + font-family: "Open Sans"; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +.chip { + border-width: 1px; + border-style: solid; + text-align: center; + font-family: "Open Sans"; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: normal; + border-radius: 64px; + border: 1px solid var(--Secondary-1, #102d5f); +} + +.chipSelected { + color: white; + background: #102d5f; +} + +.chipSelected:hover { + background: #102d5f; +} + +.chipContainer { + display: flex; + flex-direction: row; + gap: 16px; + flex-wrap: wrap; +} + +.chipUnselected { + color: var(--Accent-Blue-1, #102d5f); + background: rgba(255, 255, 255, 0); +} + +/*Need exact color for this*/ +.chipUnselected:hover { + background: rgb(213, 232, 239); +} + +.requiredAsterisk { + color: var(--Secondary-2, #be2d46); + text-align: left; +} + +.helperText { + color: var(--Secondary-2, #be2d46); + font-size: 12px; +} diff --git a/frontend/src/components/shared/input/TextField/index.tsx b/frontend/src/components/shared/input/TextField/index.tsx index a572bec..2dc9e10 100644 --- a/frontend/src/components/shared/input/TextField/index.tsx +++ b/frontend/src/components/shared/input/TextField/index.tsx @@ -33,7 +33,11 @@ const TextField = forwardRef( }} {...props} /> - {helperText ?
{helperText}
: null} + {helperText ? ( +
+ {helperText} +
+ ) : null}
); }, diff --git a/frontend/src/components/shared/input/TextField/styles.module.css b/frontend/src/components/shared/input/TextField/styles.module.css index 971ff18..40565fa 100644 --- a/frontend/src/components/shared/input/TextField/styles.module.css +++ b/frontend/src/components/shared/input/TextField/styles.module.css @@ -29,10 +29,14 @@ } .helperText { - color: var(--Secondary-2, #be2d46); + color: var(--Neutral-Gray6, #484848); font-size: 12px; } +.errorText { + color: var(--Secondary-2, #be2d46); +} + .input::placeholder { font-size: 16px; font-style: italic; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1c31bfb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "PAP-Inventory-Processing", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}