The Brotherhood Fishing Trip Dashboard is a single-page web application that manages every moving part of the annual Brotherhood fishing trip: who signed up, who has paid, where the money is right now, who's recruiting whom, and how to reach the whole roster in one click. It replaces a folder of spreadsheets, a Zelle statement, a group text, and a mental Post-It note with one screen you can open from any phone or laptop.
The system has three audiences:
When a trainee signs up, Jotform posts a webhook to /api/jotform-webhook. The webhook writes a row into fishing_signups, matches the trainee against the prospects list (by email or phone), and auto-fills the sponsor if there's a match. That's the moment the signup appears on your Fishing tab and on the Trip Status cards.
When the trainee pays via Zelle, the money hits your bank account. You export a CSV from Truist, paste it into the Reconciliation tab, and split each transaction across categories — most commonly fishing_trip credited to a specific signup. The credit shows up as a green bar on the Trip Status card, and the signup's balance drops toward zero.
When the trainee pays cash, Venmo, Cash App, or a check, none of that touches the bank yet. You use the 💰 Record payment button on their Trip Status card to log a manual payment. That instantly credits their account and adds to the Cash on Hand tile on the Treasury tab. When you eventually deposit the cash to the bank, you reconcile the resulting bank credit as Cash Deposit — linking the deposit back to the specific manual payments it represents, so no dollar gets counted twice.
2026-fishing. Extending to multiple concurrent trips is a follow-up feature, not the current design.The dashboard is locked behind Supabase Auth. There is no anonymous access; every page load requires a signed-in user. Sign-in works two ways:
Anyone with a Google account can click Sign in with Google on the login screen. On first sign-in they land in the system as a pending user with no dashboard access — just a message that says an admin will review the request. A super_user promotes them to user, manager, or super_user from the Users management panel.
Members whose email is already on the roster can request a magic-link sign-in without going through Google. Type email, click Send magic link, click the link in the resulting email, land signed in. This exists specifically for members who don't want to associate a Google identity with the Brotherhood.
| Role | What they can do |
|---|---|
pending |
Nothing. They see a "your access is pending approval" screen. No tabs are accessible. |
user |
Read-only access to Fishing, Trip Status, Communications templates, Recruiting (scoped to their own prospects). Cannot reconcile, cannot see Treasury or Trip Expenses. Intended for core members. |
manager |
Everything user can do PLUS: reconcile bank transactions, send communications, edit any prospect, archive signups, edit sponsor assignments. Intended for the trip coordinator and assistant coordinators. |
super_user |
Everything manager can do PLUS: Treasury tab (Bank Balance, Cash on Hand, Net Brotherhood, carryover), Trip Expenses tab, manual payments (Record Payment button), Cash Ledger, user role management. Intended for the treasurer. |
manager — they run day-to-day operations without seeing dollar-level treasury detail.super_user — sole owner of the money view, sole recorder of manual payments.user. This gives them visibility into their own recruiting pipeline and the ability to see who's signed up for the trip, without exposing the Brotherhood's finances.pvme2013@gmail.com, hard-coded so the system can be initialized. Every other role comes from the brotherhood_users table.pvme2013@gmail.com and lose the password to every other super_user account at the same time, you will need database access to recover admin. Keep at least two active super_users on file.
The Fishing tab is the list of everyone who has signed up for the current fishing trip. It's the most-used tab during the recruitment window and the source of every other roster in the system.
fishing_trip_payments.paid flag AND the sum of bank allocations for this signup. See Trip Status for the actual paid-vs-balance detail.Trip Status is the payment-focused view of the same roster. Where the Fishing tab is a table, Trip Status is a grid of cards — one card per active signup, colored by payment progress:
Each card is packed with information; here's what every piece means:
| Element | Meaning |
|---|---|
| Name (with ⭐) | Signup's full name. A star means they're also a core Brotherhood member (their name appears on the dues roster). |
| Sponsor chip | Which core member recruited this trainee. Tap the chip to reassign. Amber "⚠ Unassigned" means we don't know who brought them in. |
| Contact address on file. Used for payment confirmation emails. | |
| Progress bar | Percentage paid, capped at 100%. |
| Amounts row | Left: dollar amount reconciled. Right: balance still owed (or $0 if paid). |
| Status pill | "Not paid" / "N% paid" / "✓ GOOD TO GO" / "✓ PAID +$X OVER". |
| 💰 Record payment | Super_user only. Opens the manual payment modal — see chapter 5. |
Sponsors get set two ways: automatically at signup time (if the trainee's email or phone matches a prospect on the Recruiting tab), or manually by tapping the sponsor chip on the card. The picker lists every active core member plus a special Core option for group-recruited trainees (people who showed up because of a general announcement, not a specific brother's outreach).
Sponsor assignments do not affect payment math. They exist purely for the recruiting scoreboard on the Recruiting tab, which credits each core member for the people they brought in.
If someone accidentally pays more than $175 — a duplicate Zelle, a mis-typed cash amount — the card turns orange and shows the amount over. Your options:
other_income on the same bank transaction so it stops crediting the trip.This chapter covers the most-used treasurer workflow: recording money that arrives outside the bank. It combines what the dashboard calls the "manual payment ledger" with the deposit-linking flow. The two together form a complete, no-double-count model of cash-in-hand.
The system created one row in brotherhood_bank_allocations with these properties:
transaction_id = null — there's no bank transaction to point at yet.payment_method = 'cash' (or whatever you picked).category = 'fishing_trip', signup_id = <their submission_id>, amount = 175.00.deposit_transaction_id = null — this stays null until you actually deposit the cash.Because signups.js already sums fishing_trip allocations without filtering on transaction_id, the payment immediately counts toward the trainee's paid balance. No separate calculation, no parallel table.
Eventually you take the cash to the bank. The next time you import your bank statement, that deposit will show up as a credit — say a $525 credit for the three brothers whose cash you deposited together. Here's how to reconcile it without double-counting:
The cash_deposit allocation was created with category = 'cash_deposit' and the amount matching the bank credit. Behind the scenes, each linked manual payment's deposit_transaction_id got PATCH'd to the bank txn's id. Two important consequences:
cash_deposit allocation is explicitly excluded from every income bucket in the treasury calculation. It doesn't inflate fishing_trip.collected. It doesn't inflate other_income. It's just a bookkeeping bridge.Every step in this flow is reversible:
deposit_transaction_id gets cleared back to null, restoring them to Cash on Hand.On the Treasury tab, expand 💵 Cash on hand — manual payment ledger. This shows every undeposited manual payment with date, trainee name, method, amount, notes, and a Delete button. There's also a "Show already-deposited payments" toggle that reveals historically deposited rows with a green ✓ badge — useful for audit or year-end reporting.
Reconciliation is where you tell the system what each bank transaction was for. Every Zelle deposit that shows up on your Truist statement needs a category (usually fishing_trip, credited to a specific signup) and every fee, transfer, or purchase needs to be labeled too.
(date, description, amount, balance), so re-importing the same statement is safe.Each transaction expands into an editor with these controls:
| Category | Meaning | Requires |
|---|---|---|
| fishing_trip | Credit toward a trainee's $175 trip cost. | signup_id |
| dues_annual | Full-year dues payment ($120) — spreads across 12 months in dues_payments. | member_id, fiscal_year |
| dues_monthly | Partial dues, one month at a time. | member_id, fiscal_year, month |
| rental_income | Income from something the Brotherhood rents out (e.g., trailer, equipment). | — |
| bank_interest | Interest paid by Truist. Usually a few cents. | — |
| account_transfer | Internal moves between accounts (e.g., savings to checking). | — |
| outgoing_payment | General outgoing payment that isn't a trip expense. | — |
| other_income | Anything credit-side that doesn't fit above. | — |
| other_expense | Anything debit-side that doesn't fit above. | — |
| cash_deposit | A bank credit that deposits already-recorded manual payments. Requires linking specific manual payment rows. | manual_payment_ids (checklist) |
Before a save, the system checks whether the new allocation would put anyone over the cap:
If it would, you get a confirm dialog listing exactly who would go over and by how much. You can proceed anyway (someone actually paid too much and you want to record it as an overage) or cancel and adjust.
At the top of the Reconciliation tab is a panel that surfaces issues automatically:
Errors sort first, then warnings, then info. Click a row to drill into the person's transaction history.
Above the transactions list is a global toggle that controls whether trainee notifications fire when a save changes their fishing_trip contribution. Default on. Turn it off for bulk imports or backfills where you don't want to spam the whole roster.
When you allocate a new deposit from someone who has paid before, a hint appears above the allocation editor: "Use same allocations as [previous date]". Click it and the previous split gets applied to this transaction. Saves clicks when a member pays the same amount monthly.
Treasury is the treasurer's one-screen dashboard for "how are we doing." Every number here derives from other tabs; nothing is entered here except the carryover figure.
| Tile | Question it answers | Formula |
|---|---|---|
| Carryover from 2025 | How much did we bring into the new year? | Editable, stored in brotherhood_config. |
| Bank Balance | How much is in the checking account right now? | Latest balance column from the most recent bank txn. |
| Cash on Hand | How much do we have that hasn't been deposited? | Sum of undeposited manual payments. |
| Net Brotherhood | What are our total assets, everywhere they live? | Carryover + all reconciled income − trip expenses. |
Expand the ⚙️ Edit carryover panel to change the "brought into 2026" figure. Default is $1,324.76 per your books. Bank-actual carryover was $1,284.35 — the $40.41 gap is acknowledged and intentionally not reconciled to a specific transaction.
Expand the panel below the tiles to see every manual payment currently sitting in your custody, plus a toggle to reveal already-deposited history. See chapter 5 for the full flow.
Trip Expenses tracks money going OUT for the fishing trip: food, fuel, gear, lodging. It's separate from general Brotherhood expenses because the fishing trip has an internal budget the group cares about.
The 📸 Snap Receipt button on mobile opens the camera. Take a picture of the receipt; the image gets uploaded to Supabase Storage, and the expense form pre-populates with what the vision model reads from the image (vendor, total, date). Verify the extracted fields, adjust if needed, save.
When a reimbursable expense gets paid back, check the Reimbursed box on the row. The "reimbursable outstanding" total on the Treasury tab drops accordingly. This is a cross-check for the treasurer to make sure everyone got paid back before year-end.
Trip Expenses is super_user only. Members don't see it. This is intentional — expense records include payment details and reimbursement status that aren't broadly relevant.
The Dues tab tracks the annual $120 obligation for every core Brotherhood member. It's a fiscal-year grid: rows are members, columns are the 12 months (March through February), cells are green if paid, red if unpaid.
Unlike signups, which come and go every year, the core member roster is deliberately closed. There is no "Add member" button and no "Paste from Excel" import on this tab. The eight core members are:
To add or remove a core member you edit brotherhood_dues_members directly in Supabase, then re-sync from the Dues tab. This friction is intentional — changing the core roster is a governance decision, not a click.
When a member pays their dues, the money hits Truist via Zelle. You reconcile that transaction on the Reconciliation tab as dues_annual credited to the member for the appropriate fiscal year. The system spreads the $120 across all 12 months of that fiscal year (March through February) and flips all 12 cells green.
Partial dues payments use dues_monthly with a specific month picked. Useful for members catching up mid-year.
Each row has a ✎ pencil icon to edit the member's email and phone. This information is used for the Communications tab (email nudges) and the SMS launcher.
Access to the Dues tab is gated by the treasurer passcode. Core members bypass the passcode automatically because they need to see their own dues history. Everyone else needs to enter it.
Recruiting is the pre-signup pipeline. It answers the question: "who might sign up, and whose responsibility is that person?"
| Status | What it means |
|---|---|
| new | Added but not yet contacted. |
| invited | Nudged at least once. Auto-set the first time you email or SMS them. |
| signed_up | They filled out the Jotform. Auto-set by the webhook when the email/phone matches. |
| declined | They told you no. Set manually. |
| archived | Removed from the roll-up but preserved for history. |
The top of the Recruiting tab shows a scoreboard: each core member with their prospect count and status breakdown. The top scorer gets a gold border. This gives the group friendly accountability without shaming anyone.
Each prospect card has a 📱 Log contact button. Tap it after you texted or called the person outside the dashboard (i.e., you didn't use the SMS launcher). It bumps invited_count and stamps last_invited_at to now, so the pipeline reflects that you actually reached out. Includes an ↶ Undo last button if you tapped by mistake.
When a prospect fills out the Jotform, the webhook matches their email or phone against the prospects table. On a match, the resulting signup's sponsor_name gets auto-populated from the prospect's sponsor. The prospect's status flips to signed_up and their signed_up_submission_id gets stamped.
Regular user-role core members see only prospects they own (sponsor_name matches their name) or created. Manager+ sees all prospects. This lets core members manage their own outreach without seeing everyone else's list.
Communications is the outbound-message hub. From here you can email the whole roster, text a single prospect, send a personalized nudge to every core member, or copy-paste a message to yourself for reference.
brotherhood_communication_templates and can be edited or created inline.{first}, {name}, {last}, {email}, {paid}, {balance}, {trip_cost}, and (for core-member sends) {prospects_list}, {prospects_count}, {new_prospects_count}, {invited_prospects_count}, {signed_up_prospects_count}.brotherhood_communications.At the top of the compose panel, switch between Email and SMS. Email sends through Gmail SMTP via the brotherhood-send-comm edge function. SMS opens iOS Messages one recipient at a time — you tap Send in Messages manually.
When Channel = SMS, a new panel appears below the send row:
Tapping a row does three things simultaneously: syncs-copies the personalized text to the clipboard, opens iOS Messages with the recipient's number, and fires the auto-log call for prospects.
sms: URLs when they contain URLs, newlines, or certain characters. The dashboard uses the RFC-standard sms:PHONE?body=TEXT format, which works most of the time. When iOS drops the body, the clipboard fallback saves the day — the personalized text is already copied, so you long-press the Messages text field and hit Paste.
A specific template ships in the system for keeping recruiters on track. Filter = "All core members", template = "Nudge — Your recruiting pipeline". Each core member gets ONE email listing only THEIR active prospects with contact info and status. No one sees anyone else's list. Send this weekly during the recruitment window and pipelines will not go stale.
Every payment-affecting change on a trainee's account triggers a confirmation email. The design principle: trainees should never wonder if a payment was received or where they stand. Proactive communication builds trust.
Subject: Brotherhood Fishing Trip — Payment received ($175.00) Hi Bob, Just recorded a payment on your Brotherhood Fishing Trip account: Payment: $175.00 (Cash) Date received: 2026-07-03 Notes: Handed to Rothstein at Bible study Updated status: Trip cost: $175.00 Total paid: $175.00 Balance due: $0.00 Status: ✓ GOOD TO GO Keeping you in the loop. Reply if anything looks off. — Brotherhood Fishing Trip
Subject: Brotherhood Fishing Trip — Correction to your account Hi Bob, We removed a payment entry ($175.00) from your Brotherhood Fishing Trip account. This happens when something was recorded in error, reconciled differently, or corrected. Updated status: Trip cost: $175.00 Total paid: $0.00 Balance due: $175.00 Status: No payments on file If this looks wrong, reply and we'll sort it out. — Brotherhood Fishing Trip
If a trainee has no email address on file, the notification is silently skipped. On recon saves you get a summary alert: "Save succeeded, but 2 trainee notifications could not be sent — no email on file." Reach those people via SMS or phone.
Every notification goes through the same brotherhood-send-comm edge function as regular Communications. Every send lands in brotherhood_communications. You can search send history to see when Bob's confirmation went out, what the exact subject and body were, and whether delivery succeeded.
The System Map is a hidden-by-default tab (visible to super_user) that describes every architectural piece of the dashboard in plain language. It exists so that when you hand this system off to someone else — or when you come back after a year and forget how something works — the documentation lives inside the app.
Cards on the System Map cover: who can sign in, how "who's online" is tracked, the audit trail, concurrent-edit protection, data freshness, the Supabase schema, mobile responsiveness, deploy flow, reminder-email pipeline, bank cross-checks, read-only Paid column, Communications tab walkthrough, Recruiting tab walkthrough, sponsor propagation, per-core-member nudges, SMS launcher, manual payment ledger, trainee notifications, Treasury, Trip Expenses, receipt snap workflow, bank-debit reconciliation, and archived-signup re-signup handling.
The map is not a replacement for this manual — it's the developer-facing view of the same system. When something changes, both should be updated together.
These are the assumptions baked into the code. Changing any of them requires a code change; none can be tuned from the UI.
TRIP_COST in signups.js, treasury.js, and recon-discrepancies.js.ANNUAL_CAP in the reconciliation overage check.expected_updated_at against the current row's updated_at. Mismatch returns HTTP 409 and the UI prompts to reload.The dashboard talks to a single Supabase project. Every write goes through Cloudflare Functions using the service key; the client never touches the database directly except for auth. Here are the tables that matter:
| Table | Purpose |
|---|---|
| fishing_signups | One row per trainee who filled out the Jotform. |
| fishing_trip_payments | The legacy manual-paid flag per signup; also holds archived state. |
| brotherhood_bank_transactions | Every row of every imported bank statement. Unique on (date, description, amount, balance). |
| brotherhood_bank_allocations | Categorized splits of bank transactions AND manual payments. The unified ledger. |
| brotherhood_dues_members | The fixed core-member roster. |
| brotherhood_dues_payments | Cross-writes from dues_annual and dues_monthly allocations. Cell = (member_id, year, month). |
| brotherhood_config | Editable key-value store. Currently holds the carryover figure. |
| brotherhood_trip_expenses | One row per receipt logged on the Trip Expenses tab. |
| brotherhood_users | Dashboard user roles. Role = pending / user / manager / super_user. |
| brotherhood_communications | Log of every email/SMS batch sent through the Communications tab. |
| brotherhood_communication_templates | Reusable subject + body templates. |
| brotherhood_prospects | Pre-signup pipeline. One row per potential attendee. |
| brotherhood_audit_log | Every mutation across the dashboard. Read from the Audit Log tab. |
The brotherhood_bank_allocations table is worth understanding in detail because it's the heart of the payment system:
id uuid primary key transaction_id uuid FK to brotherhood_bank_transactions, NULLABLE category text from the category set amount numeric positive signup_id text FK-ish to fishing_signups.submission_id member_id uuid FK to brotherhood_dues_members fiscal_year integer month integer notes text payment_method text cash / venmo / cashapp / zelle_external / check / other payment_date date when a manual payment was received deposit_transaction_id uuid FK to brotherhood_bank_transactions created_at timestamptz
A row must have EITHER transaction_id (bank origin) OR payment_method (manual origin) — never both, never neither. Enforced by a CHECK constraint. When a manual payment gets deposited, its deposit_transaction_id gets set to the bank txn representing the deposit — establishing a many-to-one link.
Cause is almost always cache. Force-refresh: on iOS Safari, tap-and-hold the reload icon → Request Website. On desktop, Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows). If it still doesn't load, the Cloudflare deploy may not have completed — wait 60 seconds and try again.
Legacy manual paid flag with no reconciled money behind it. Open the Reconciliation tab and find the bank credit that corresponds to this person's payment, then allocate it as fishing_trip → them. The green checkmark and Trip Status will reconcile once the allocation exists.
You reconciled more than $175 to them. Open their card, click through to see all transactions attached, and either: (a) refund the excess and remove the extra allocation, (b) reassign the excess to other_income, or (c) roll it to next year manually with a note.
This is the expected state. The manual payment counts toward Cash on Hand on the Treasury tab. When you eventually deposit the cash, reconcile the bank credit as cash_deposit and check off the specific manual payments the deposit represents (chapter 5).
Now the trainee is showing paid twice — once from the manual payment, once from the bank credit. On the Reconciliation tab, expand that bank credit, delete the fishing_trip allocation, and re-save as cash_deposit with the appropriate rows checked. The trip status will correct itself.
Check the Communications tab send history for their name and the time you saved. If the send row exists with an error, follow up on the SMTP failure (usually a Gmail rate limit — send fewer at once). If the send row doesn't exist, the notification was skipped — either the trainee has no email on file, or you unchecked the Email checkbox on the record-payment modal.
The Bank Balance tile reads the balance column of the most recent bank transaction. If it looks wrong, you probably haven't imported the latest statement — or the latest import didn't include a balance column. Re-export from Truist including the running balance.
List the manual payments in the Cash Ledger panel and reconcile against your physical stack. Either (a) a payment was recorded but the physical cash was already deposited (mark it deposited via cash_deposit reconciliation) or (b) you have cash that wasn't recorded (record it via the 💰 button on the appropriate person's card).
| Term | Definition |
|---|---|
| Allocation | A row in brotherhood_bank_allocations. Represents a specific dollar amount attributed to a specific category, and optionally a specific person. |
| Bank Balance | The dollar figure the bank shows in the account right now. Derived from the latest imported bank transaction's balance column. |
| Cash on Hand | Manual payments received but not yet deposited to the bank. Includes cash, Venmo balance, uncashed checks, etc. |
| cash_deposit | Allocation category that links a bank credit to specific undeposited manual payments. Does not count as income. |
| Core member | One of the eight named Brotherhood brothers listed in brotherhood_dues_members. Owes $120/year in dues. Sees a star (⭐) next to their name on the roster. |
| Fiscal year | The Brotherhood's financial year runs March through February. Fiscal 2026 = Mar 2026 through Feb 2027. |
| Manual payment | Money received outside the bank channel (cash, Venmo, Cash App, check). Recorded via the 💰 Record payment button. Sits on the Cash on Hand ledger until deposited. |
| Net Brotherhood | Total assets across bank and cash on hand. Formula: carryover + reconciled income − trip expenses. |
| Nudge | A pipeline-review email sent to a core member listing their currently active prospects and status. |
| Overage | Amount reconciled in excess of the $175 trip cost for a single person. |
| Prospect | A pre-signup pipeline entry. Someone a core member is trying to bring in. |
| Reconciliation | The act of categorizing a bank transaction — telling the system what it was for. |
| Signup | A row in fishing_signups. Created by the Jotform webhook when someone fills out the form. |
| Sponsor | The core member who recruited a specific signup. Free-text but restricted by the UI picker. |
| Trip cost | $175 per trainee, hard-coded across the system. |
| Trainee | A trip participant. Includes core members who sign up AND non-members. |
Brotherhood Fishing Trip Dashboard · Operator's Manual · Save this page for offline reading.
For questions or corrections, message the trip coordinator.