Add the user-submission flow for the catalog
Lets users submit a private catalog entry (port type, cable type, device template) for promotion to the public catalog, or suggest an edit to an existing public entry — both go into the catalog_submissions review queue per organized-ideas.md §3. No Admin review UI yet (next sub-phase); this covers the submitter's side only. - data/SubmissionRepository + SupabaseSubmissionRepository: submit, resubmit, withdraw, list-mine, backed by the existing catalog_submissions RLS policies (no schema changes needed). - data/catalogRowMapping.ts: shared domain<->row mappers, in both directions, so a submission's proposed_data is always shaped like the underlying table row (what an eventual admin-approval would write directly) and can be turned back into form-editable fields for revision. - state/submissionStore.ts: mySubmissions + submit/resubmit/withdraw, plus syncProposedData — called from catalogStore's updateCustom* actions so a submission about your own still-private entry never goes stale relative to it (edits from the library and from My Submissions are the same action and always agree). - UI: "Submit"/"Suggest edit" wired into PortTypeManager, CableTypeManager, DevicePalette/DeviceTemplateEditor; new MySubmissionsModal (opened from TopBar, with a pending-count badge) shows status, rejection reasons, and lets you edit/resubmit or withdraw. - Deliberately deferred: device_category submissions (no listing UI to hang a button on yet) and the normalized manufacturer catalog. Verified: tsc -b and oxlint clean; supabase db reset + 23/23 pgTAP RLS tests still pass (no schema changes this round); manually tested submit, suggest-edit, edit-from-either-side sync, reject/resubmit, and withdraw.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/** Which catalog table a submission is about. Mirrors `catalog_submissions.entity_type`'s
|
||||
* check constraint — `manufacturer` is omitted since the app doesn't wire up the
|
||||
* normalized manufacturer catalog yet (see organized-ideas.md §3, deferred alongside
|
||||
* device_templates.manufacturer staying free text for now). */
|
||||
export type CatalogEntityType = 'device_template' | 'port_type' | 'cable_type' | 'device_category'
|
||||
|
||||
/**
|
||||
* A user's request to change the public catalog — either promoting one of
|
||||
* their own private entries to public, or proposing an edit to an existing
|
||||
* public entry. Regular users can never write a public catalog row directly
|
||||
* (RLS only allows Admins to), so this is the only path either kind of
|
||||
* change can take; an Admin review queue (not yet built — organized-ideas.md
|
||||
* §9's next sub-phase) is what actually applies an approved one.
|
||||
*/
|
||||
export interface CatalogSubmission {
|
||||
id: string
|
||||
entityType: CatalogEntityType
|
||||
/** The row this submission is about — always an existing row in this
|
||||
* app's flow: either your own private entry (being promoted, possibly
|
||||
* with edits) or a public entry (you're suggesting an edit to). */
|
||||
entityId: string
|
||||
/** Proposed field values, shaped like the underlying table's row (see
|
||||
* data/catalogRowMapping.ts) — so applying an approval is just writing
|
||||
* this object onto the row at `entityId`, not a separate translation
|
||||
* step future Admin-approval logic would otherwise have to redo. */
|
||||
proposedData: Record<string, unknown>
|
||||
submitterId: string
|
||||
status: 'pending' | 'approved' | 'rejected'
|
||||
reviewerId?: string
|
||||
/** Set by an Admin on rejection — organized-ideas.md §3's "notified either
|
||||
* way, with a reason attached on rejection rather than a silent disappearance." */
|
||||
reviewReason?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
/** Storage abstraction for the catalog submission/review queue — mirrors
|
||||
* DiagramRepository/CatalogRepository's role for this concern. Only covers
|
||||
* the submitter's-eye view (create, revise, withdraw your own) for now; an
|
||||
* Admin's view (list all pending, approve/reject) is the next sub-phase. */
|
||||
export interface SubmissionRepository {
|
||||
/** Every submission the current user has made, most recent first. */
|
||||
listMine(): Promise<CatalogSubmission[]>
|
||||
/** Submits `entityId` (a row you own privately, or a public row you're
|
||||
* proposing an edit to) for Admin review. */
|
||||
submit(entityType: CatalogEntityType, entityId: string, proposedData: Record<string, unknown>): Promise<CatalogSubmission>
|
||||
/** Revises a still-pending or previously-rejected submission of your own
|
||||
* and puts it (back) into the pending queue. */
|
||||
resubmit(id: string, proposedData: Record<string, unknown>): Promise<void>
|
||||
/** Updates only `proposed_data`, leaving status/reviewer/reason untouched.
|
||||
* Used to keep a submission's snapshot in sync with the live private
|
||||
* entry it's about (see catalogStore) — a plain edit to your library
|
||||
* entry shouldn't silently pull a rejected submission back into the
|
||||
* pending queue, so status changes stay a separate, explicit action
|
||||
* (`resubmit`), while the data itself is kept fresh automatically. */
|
||||
updateProposedData(id: string, proposedData: Record<string, unknown>): Promise<void>
|
||||
/** Withdraws your own still-pending submission. */
|
||||
withdraw(id: string): Promise<void>
|
||||
}
|
||||
Reference in New Issue
Block a user