Skip to main content
A Consolidated School & Youth Development Plan is a FormSubmission. Status enum: draft → submitted → under_review → approved | rejected

Default step keys

Fallback keys in src/lib/formSteps.js (order is the live bank when published):
  1. tableOfContents
  2. childAbuseIntervention
  3. sexualHarassment
  4. respectForAll
  5. suicidePrevention
  6. attendancePlan
  7. temporaryHousing
  8. serviceInSchools
  9. planningInterviews
  10. militaryRecruitment
  11. schoolCulture
  12. afterSchoolPrograms
  13. cellPhonePolicy
  14. counselingPlan
principalLetter exists in some legacy maps but is not in the fallback key list. Do not revive it as a required step without a bank migration that keeps answer ids stable.

Form workspace

/form/[id] uses DashboardShell like the rest of the product.
  • Header title is school + year. The body heading is the current section only.
  • Status strip: editing/saved time (autosave ~3s), archived/view-only, optional due date, thin progress bar. There is no Save draft button.
  • Header tools: Compare, Duplicate (level ≥ 4), Share (level 5), Comment (level 5), View all, Submit.
  • Copied plans show unreviewed needsUpdate flags (“Same as last year” / “New this year” / changed) with Mark reviewed.
  • Last step shows a submit summary; submit opens a confirm dialog, then attestation if the plan was copied.
Read-only view is /view/[id] (same shell, print/PDF/Word, jump list). New plans: /form/new.

How to fill a table question

Some questions are type table (staff lists, program hours, contacts). Answers are stored as { headers, rows } on formData[stepKey].data[questionId], not as a single string. Super Admins configure columns in the question bank; you fill the grid here.
1

Open the section that has the table

Use the step list on /form/[id]. A table question shows a grid (not a textarea) and a short hint under the prompt.
2

Type in cells, or paste from Excel / Google Sheets

Click a cell and type. For dropdown columns, choose Select one or a listed option.To paste a spreadsheet: copy the range in Excel or Sheets, click anywhere on the table (not inside a single cell as if it were a paragraph), and paste. Tab-separated columns stay in columns. If the first pasted row matches the locked headers, that row is treated as a header and dropped.
3

Add or remove rows

Add row appends an empty row. The trash control on a row removes it (the last remaining row cannot be deleted). Clear table empties every cell but keeps the column headers the question bank defined.
4

Add a column only when headers are unlocked

If Super Admin left Table columns empty, you can Add column and edit header names. If they locked a preset (for example Staff contact), headers are fixed and Add column is hidden so every school uses the same shape.
Dropdown columns keep a pasted value when it matches a choice (case-insensitive). A value that does not match still appears in the cell so you can fix it. Grade / timeline presets use the district lists in src/lib/tableAnswer.js.
Caps are 100 rows and 20 columns. Do not paste a whole workbook into one cell — that becomes a single-column blob and will not export as a grid. Autosave (~3s) writes the structured table; required tables need at least one non-empty cell.
Legacy free-text contact notes may still be a string. The editor can parse those into rows when the question uses a contact-style column preset. Super Admins can also run the contact-table migration on Year setup.
/view/[id] and archived years show the same grid without edit controls. PDF and Word print real tables (headers + rows), not a joined paragraph.

Editing a step

PUT /api/forms/[id]/step/[stepNumber]
The route:
  1. Resolves stepNumber → stepKey via the question bank.
  2. Rejects writes if the school year is archived.
  3. Acquires a 5-minute lock on form:{id}:step:{stepKey}.
  4. Rate-limits saves (rl:save:{userId}:{formId}, 30 / 60s when Redis is up).
  5. Writes only dirty fields on formData[stepKey] (revision 409 on conflict).
Unlock: POST /api/forms/[id]/step/[stepNumber]/unlock.

Needs-update flags

Copied plans may include needsUpdate[]:
reason is new | changed | revisit. Clear a flag with POST /api/forms/[id]/review-flag { "questionId": "..." }.

Attestation

Copied drafts require principal sign-off before submit: POST /api/forms/[id]/attest { "name": "Jane Principal" } Stores attestation.confirmed, name, signedAt, signedBy.