Skip to content

Tiffany/308 parse unfinalized donor offer - #315

Closed
tifpham021 wants to merge 4 commits into
mainfrom
tiffany/308-parse-unfinalized-donor-offer
Closed

tifpham021 wants to merge 4 commits into
mainfrom
tiffany/308-parse-unfinalized-donor-offer

Conversation

@tifpham021

Copy link
Copy Markdown
Contributor

Description

Resolves ticket number: #308

Explain what your code changes: Admin is now able to turn unfinalized donor offers into csv files. Also when the partner's request in quantity amount is different from the initial quantity, the UI reflects the new quantity and crosses out the old one.

List the steps you took to test your code:

  • made sure that 'Convert to CSV' button is there
  • checked to see that data actually turns into a csv file when clicked on
  • tested out with data to see whether the UI worked when initial quantity and partner requested quantity are the same
  • tested out with data to see whether the UI worked when initial quantity and partner requested quantity are different
  • made sure that change was reflected in Prisma Studio and in the csv file

Before
Screenshot 2026-03-29 at 4 47 00 PM
csv-test-offer-requests.csv

After
Screenshot 2026-03-29 at 4 48 17 PM
csv-test-offer-requests (1).csv

Checklist

  • The ticket is mentioned above
  • The changes fulfill the success criteria of the ticket
  • The changes were self-reviewed
  • A review was requested

@netlify

netlify Bot commented Mar 29, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for hope-for-haiti ready!

Name Link
🔨 Latest commit 2ea1648
🔍 Latest deploy log https://app.netlify.com/projects/hope-for-haiti/deploys/69c990516dea530008f15586
😎 Deploy Preview https://deploy-preview-315--hope-for-haiti.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@greptile-apps

greptile-apps Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds two features: (1) a "Convert to CSV" action for unfinalized donor offers that exports item-level partner request data, and (2) a dual-signature flow for sign-offs requiring both a staff member and a partner representative to sign (with name input). It also refactors SignatureImageTooltip to accept a generic tooltipId prop, and fixes a stale-closure bug in NotificationHandler.

Key changes:

  • New GET /api/donorOffers/[donorOfferId]/csv endpoint with auth and state validation
  • SignOff DB model gains optional partnerSignerName and partnerSignatureUrl fields
  • SignOffModal now captures two signatures (staff + partner) with validation
  • SignatureImageTooltip signOffId → tooltipId refactor applied across SignOffsTable, ShipmentsLineItemChipGroup, and PartnerDistributionTable
  • uploadSignatureIfNeeded helper extracted in signOffService to avoid duplication

Issues found:

  • Partner row always rendered on legacy sign-offs (SignOffsTable.tsx + ShipmentsLineItemChipGroup.tsx): the visibility guard falls back to partnerName (always non-null), so every historical sign-off now shows a spurious "partner" row with no signature link.
  • CSV per-partner quantity inconsistency (donorOfferService.ts): the Request Total column uses finalQuantity ?? quantity ?? 0 but each partner's column uses finalQuantity ?? 0, making the columns non-additive when finalQuantity is null.

Confidence Score: 4/5

Two P1 bugs should be fixed before merging: legacy sign-offs will render an erroneous partner row, and CSV per-partner columns won't sum to the stated Request Total.

Both P1 issues cause visible, present defects — incorrect UI for all pre-existing sign-offs and a misleading CSV that will confuse admins. Neither is a production crash, but both affect data integrity of the key features introduced in this PR.

src/components/SignOffsTable.tsx, src/components/chips/ShipmentsLineItemChipGroup.tsx (partner-row guard), and src/services/donorOfferService.ts (CSV quantity fallback).

Important Files Changed

Filename Overview
src/components/SignOffsTable.tsx Updated to show separate staff/partner signature rows; partner row guard always evaluates true because partnerName is non-nullable, incorrectly showing a partner row on legacy sign-offs.
src/components/chips/ShipmentsLineItemChipGroup.tsx Same always-true partner-row guard bug as SignOffsTable.tsx; needs condition changed to `signOff.partnerSignerName
src/services/donorOfferService.ts Adds getUnfinalizedDonorOfferCsv; per-partner column uses finalQuantity ?? 0 while Request Total uses finalQuantity ?? quantity ?? 0, causing column sums to diverge from the total.
src/app/api/donorOffers/[donorOfferId]/csv/route.ts New GET endpoint that generates and streams a CSV for unfinalized donor offers; auth, param validation, and error handling look correct.
src/components/SignOffModal.tsx Upgraded to collect both staff and partner signatures with separate canvas refs, partner name input, and proper validation before submission.
src/services/signOffService.ts Refactored signature upload into uploadSignatureIfNeeded helper; cleanly handles both staff and partner signatures for create and update paths.
src/components/SignatureImageTooltip.tsx Replaced signOffId prop with generic tooltipId string to support multiple tooltip instances per sign-off.
src/components/NotificationHandler.tsx Fixed stale searchParams closure by using a ref, and corrected event name from notification.message_new to message.new for stream client unsubscription.
prisma/schema.prisma Added optional partnerSignerName and partnerSignatureUrl fields to SignOff model; existing rows remain compatible as both are nullable.
src/screens/DonorOffersScreens/AdminDonorOffersScreen.tsx Added 'Convert to CSV' menu item for UNFINALIZED offers; minor style issue with arrow function assignment expression.

Sequence Diagram

sequenceDiagram
    participant Admin
    participant Browser
    participant CSVRoute as GET /api/donorOffers/:id/csv
    participant DonorOfferService
    participant DB

    Admin->>Browser: Clicks "Convert to CSV" (UNFINALIZED offer)
    Browser->>CSVRoute: GET /api/donorOffers/:id/csv
    CSVRoute->>CSVRoute: auth() + checkPermission("offerWrite")
    CSVRoute->>DonorOfferService: getUnfinalizedDonorOfferCsv(id)
    DonorOfferService->>DB: getAdminDonorOfferDetails(id, true)
    DB-->>DonorOfferService: donorOffer + partners + items w/ requests
    DonorOfferService->>DonorOfferService: validate state === UNFINALIZED
    DonorOfferService->>DonorOfferService: build rows (item × partner matrix)
    DonorOfferService-->>CSVRoute: { csv, fileName }
    CSVRoute-->>Browser: 200 text/csv attachment

    Admin->>Browser: Clicks "Submit" in SignOffModal
    Browser->>Browser: Validate partnerSignerName + both signatures
    Browser->>+SignOffAPI: POST /api/signOffs (FormData w/ 2 sigs)
    SignOffAPI->>SignOffService: createSignOff(data)
    SignOffService->>FileService: uploadSignature(staffSig, userId)
    SignOffService->>FileService: uploadSignature(partnerSig, userId)
    FileService-->>SignOffService: signed URLs
    SignOffService->>DB: db.signOff.create(...)
    DB-->>SignOffService: saved SignOff
    SignOffService-->>SignOffAPI: result
    SignOffAPI-->>Browser: 200 OK
Loading

Reviews (1): Last reviewed commit: "able to turn unfinalized donor offers in..." | Re-trigger Greptile

Comment on lines +34 to +63
const partnerDisplayName =
signOff.partnerSignerName || signOff.partnerName;

return (
<>
<span className="mx-2">•</span>
<span
data-tooltip-id={`signature-tooltip-${signOff.id}`}
className="underline decoration-dotted cursor-pointer text-gray-500"
>
View signature
</span>
<SignatureImageTooltip
signOffId={signOff.id}
signatureUrl={signOff.signatureUrl}
/>
<div className="text-sm text-gray-500 font-light">
<span>{signOff.staffMemberName || "Unknown"}</span>
<span className="mx-2">•</span>
<span>
{signOff.date
? format(new Date(signOff.date), "M/d/yyyy 'at' h:mm a")
: "N/A"}
</span>
{signOff.signatureUrl && (
<>
<span className="mx-2">•</span>
<span
data-tooltip-id={staffTooltipId}
className="underline decoration-dotted cursor-pointer text-gray-500"
>
View staff signature
</span>
<SignatureImageTooltip
tooltipId={staffTooltipId}
signatureUrl={signOff.signatureUrl}
/>
</>
)}
</div>
{(partnerDisplayName || signOff.partnerSignatureUrl) && (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Partner row always visible on pre-existing signoffs

partnerDisplayName falls back to signOff.partnerName, which is a required non-nullable field on every SignOff record. This makes the condition on line 63 always evaluate to true, causing a spurious "partner" row to appear for every signoff — including all historically created sign-offs that were completed before this feature was added (i.e. with no partnerSignerName or partnerSignatureUrl).

The guard should only show the partner row when there is explicit partner data (name OR signature URL).

The condition at line 63 should be:

{(signOff.partnerSignerName || signOff.partnerSignatureUrl) && (

The same issue exists in src/components/chips/ShipmentsLineItemChipGroup.tsx at line 172 — both files need the same fix.

Comment thread src/services/donorOfferService.ts
Comment on lines +203 to +205
onClick={() =>
window.location.href = `/api/donorOffers/${offer.donorOfferId}/csv`
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Arrow function body should use braces for an assignment

An assignment expression used as a concise arrow body is valid JS/TS, but most linters flag it as a potential mistake (easily confused with ===). Wrap the body in braces to be explicit:

Suggested change
onClick={() =>
window.location.href = `/api/donorOffers/${offer.donorOfferId}/csv`
}
onClick={() => {
window.location.href = `/api/donorOffers/${offer.donorOfferId}/csv`;
}}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@tifpham021 tifpham021 closed this Mar 29, 2026
@tifpham021
tifpham021 deleted the tiffany/308-parse-unfinalized-donor-offer branch March 29, 2026 21:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant