add 'send per post' option to the appreciation dialog - #924
add 'send per post' option to the appreciation dialog #924ivannissimrch wants to merge 23 commits into
Conversation
ReviewThis is effectively an RFC — the PR itself flags that it depends on an unpublished
On the actual question asked (status field vs. date-chain): a |
…need4deed-org/fe into ivannissimrch/640-send-per-post
|
All five addressed. 1, 3, and 4 are gone with the real field. 5: done, the three blocks are a DELIVERY_STATUSES config array mapped over now. |
arturasmckwcz
left a comment
There was a problem hiding this comment.
Automated review findings.
Three are attached as inline comments below (on lines touched by this diff). Two more are real bugs but sit on code this PR doesn't touch a diff hunk for, so GitHub won't let me anchor them inline — flagging here instead:
src/components/Dashboard/Profile/sections/Appreciation/Appreciation.tsx:137— the "Received on" table cell only readsentry.dateDelivery, so POST-status (mailed) rows show an empty placeholder even though a mailed date exists indateDue. A coordinator sets status POST with a mailed date; the status badge shows "Mailed on " but the adjacent "Received on" column shows a blank dash for the same row.src/hooks/useAppreciationTracker.ts:37-41—sortedAppreciationssorts bydateDelivery ?? dateDuewith no regard tostatus, so it now mixes forward-looking PENDING due-dates with backward-looking POST mailed-dates in one ordering. This pre-existing comparator was never updated for the new status semantics this PR introduces, so list order becomes incorrect once POST entries exist alongside PENDING ones.
| @@ -140,7 +162,7 @@ export function AppreciationDialog({ isOpen, onClose, onSave, initialData }: Pro | |||
| } | |||
| }; | |||
|
|
|||
There was a problem hiding this comment.
correctness: handleDeliveryStatusSelect only sets a default date when none is selected; it never clears a stale date when switching between statuses with different valid date ranges.
Failure scenario: user selects PENDING and picks a future due date (allowed, allowFuture: true on that option), then switches to POST or RECEIVED (both disallow future dates). The date picker blocks new future date selections going forward, but doesn't clear the already-selected one — so a stale future date can be submitted as the mailed/received date unless the user manually changes it.
| return `${t("dashboard.appreciationSection.statusDueTo")} ${formatDate(entry.dateDue)}`; | ||
| } | ||
| return EMPTY_PLACEHOLDER_VALUE; | ||
| const STATUS_LABEL: Record<AppreciationStatusType, (entry: ApiAppreciationGet) => string> = { |
There was a problem hiding this comment.
correctness: STATUS_LABEL has no fallback for an unrecognized entry.status; STATUS_LABEL[entry.status](entry) will throw a TypeError instead of degrading gracefully like the old EMPTY_PLACEHOLDER_VALUE-based logic it replaced.
Failure scenario: a stale cached GET response (pre-dating the SDK 0.0.147 upgrade) or a future SDK enum value added before the FE catches up causes STATUS_LABEL[entry.status] to be undefined; calling it as a function throws while rendering that row, crashing the whole appreciation table instead of showing a placeholder.
| export const StatusBadge = styled.div<{ $status: "received" | "pending" }>` | ||
| background: ${(props) => (props.$status === "received" ? "var(--color-green-100)" : "var(--color-red-50)")}; | ||
| export const StatusBadge = styled.div<{ $status: AppreciationStatusType }>` | ||
| background: ${(props) => |
There was a problem hiding this comment.
simplification: StatusBadge's background color is a hand-rolled ternary chain on AppreciationStatusType instead of extending the codebase's existing statusColorMap pattern (used by several other status badges in Dashboard).
A third, independent place now needs updating whenever status colors/tokens change; the established Record<StatusValue, string> + shared StatusBadge convention used elsewhere is bypassed here, increasing drift risk for future status/palette changes.
Re-reviewCorrectness
Test coverage
Simplification / reuse
No contract-first violations — |
…need4deed-org/fe into ivannissimrch/640-send-per-post
arturasmckwcz
left a comment
There was a problem hiding this comment.
Re-review findings (automated).
| [AppreciationStatusType.PENDING]: "var(--color-red-50)", | ||
| }; | ||
|
|
||
| export const StatusBadge = styled.div<{ $status: AppreciationStatusType }>` |
There was a problem hiding this comment.
statusColorMap has no default/fallback entry (CONFIRMED)
If a row's entry.status is not one of RECEIVED/PENDING/POST (e.g. a pre-migration record served before the backend backfill completes), statusColorMap[props.$status] is undefined, producing background: undefined; — the badge silently loses its intended color instead of degrading to a defined default.
| t: TFunction, | ||
| ): Record<AppreciationStatusType, (entry: ApiAppreciationGet) => string> => ({ | ||
| [AppreciationStatusType.RECEIVED]: () => t("dashboard.appreciationSection.statusReceived"), | ||
| [AppreciationStatusType.PENDING]: (entry) => |
There was a problem hiding this comment.
Missing placeholder fallback for null dateDue (PLAUSIBLE)
The old getStatusLabel explicitly returned EMPTY_PLACEHOLDER_VALUE ('–') when an entry had neither dateDelivery nor dateDue. The new createAppreciationStatusLabelMap always concatenates the translated prefix with formatDate(entry.dateDue ?? undefined) for PENDING/POST, so an entry with status PENDING/POST but a null dateDue (e.g. legacy/back-filled rows) renders as "Due on –" / "Mailed on –" instead of the clean placeholder previously shown.
| useEffect(() => { | ||
| if (!isOpen) return; | ||
|
|
||
| if (initialData) { |
There was a problem hiding this comment.
Future-date invariant not enforced on dialog open (PLAUSIBLE)
handleDeliveryStatusSelect clears selectedDate when switching to a non-allowFuture status (RECEIVED/POST) with a future date already set, but this useEffect seeding state from initialData on dialog open does no such check. Editing an existing entry whose stored status is RECEIVED or POST but whose date is in the future (possible via legacy data or direct API writes) loads that future date unfiltered; saving without touching the date field resubmits the invalid future date.
|
@arturasmckwcz I need a dateMailed field from the BE so I can show the mailed date on the appreciation.Right now there's only dateDue and dateDelivery, and I need to display three: due on, mailed on, and received on. Can you help me get it in? On the sort comparator you flagged. Agreed that it mixes forward-looking and backward-looking dates. I want to group by status first, then sort by date inside each group, so a due date is never compared against a mailed date. That grouping works with the fields we have today, so it is not blocked, but without a dateMailed column, I can't sort on the mailed date once a row moves on, because saving as received nulls dateDue and the mailed date is gone. I'll fix it after I get the dateMailed from the be. Screencast.from.2026-08-29.08-55-39.mp4 |
|
@ivannissimrch now we have the following structure: so you suggest adding |
|
@arturasmckwcz Yes, exactly that. date_sent, nullable. Set when the status goes to POST, and kept when it moves to RECEIVED so the row still shows when it was mailed. |
… into ivannissimrch/640-send-per-post
Update 2026-08-26
The status field landed. sdk#210 published
AppreciationStatusTypein 0.0.147, and be#917 added the column with a migration that backfills existing rows. This PR consumes the real contract now:types.tsdeleted, local widening gone.Description
The frontend side is done, but it needs something from the backend, and I'm not sure what the right shape is, so I'm opening this to show what I have and ask.
The problem: an appreciation row only has
dateDueanddateDelivery. There is no status, so the frontend figures out what to show by checking which date is set (Appreciation.tsx,entry.dateDelivery ? "received" : "pending"). With two options, that works. With three it breaks. A posted item and a pending item look identical in the database; both have a due date and no delivery date, so when you reopen the dialog, there is nothing to tell them apart and it shows pending.What I tried: I added a
statusfield on the appreciation on a localbebranch, to see if it worked end to end.That was the field on ApiAppreciation in the SDK, a column on the appreciation table, the same property in sdk-types.json so request validation lets it through, and one line in dtoAppreciation. No route logic changed, GET /volunteer/:id/appreciation, POST /volunteer/:id/appreciation and PATCH /appreciation/:id all pick it up as they are. It worked, and the frontend in this PR is written against it. Until the field is in the published SDK, types.ts widens the type locally, so this does not run against dev or prod.
@arturasmckwcz here is what I did and why. Is a status field the right approach, or would you do it differently?
Can you help me get it built on the backend?
One thing I am unsure about either way: the sent date currently goes into
dateDue, because that is the only column that can hold it, which makes the column name a bit off.Related Issues
Closes #640
Changes
Appreciation/types.tswithDeliveryStatusandAppreciationWithStatus, matching the siblingtypes.tsfiles inActivityLog,OpportunityDetailsand VolunteerAgents`statusdirectly instead of an ordered chain of date checksgetStatusLabelbecomes aRecord<DeliveryStatus, ...>lookup, so a fourth status will not compile until it is handledScreenshots / Demos
Checklist