# Proposals List

**Date:** 2026-06-01
**Status:** Draft
**Spec:** 156
**Tier:** All tiers
**Depends on:** `marketing-catalogs-campaigns`, `proposal-editor`, `proposal-expiry-deadline`, `lead-to-proposal-flow`, `foundation-auth-rbac`
**Referenced by:** `marketing-catalogs-campaigns`, `proposal-editor`

---

## Overview

Spec 23 (`marketing-catalogs-campaigns`) defines the `proposals` table and CRUD API. Spec 130 (`proposal-editor`) defines the editor. Spec 96 (`proposal-expiry-deadline`) adds `expires_at`. No spec defines the `/proposals` list page — the primary view for managing all proposals in the pipeline. This spec defines the list view, status filter, and quick actions.

### Mobile route behavior

At phone widths, the proposal list uses 16px inline padding; heading controls wrap without losing create, search, or filter actions.

Rationale: preserve proposal management within the mobile app frame.

---

## Route

`/proposals` — requires `marketing:read`.

---

## Page Layout

```
┌──────────────────────────────────────────────────────────────┐
│  Proposals                          [+ New proposal]         │
│                                                              │
│  [Search...]   [Status: All ▾]  [Customer ▾]  [Expiry ▾]   │
│                                                              │
│  [Table view]  [Pipeline view]                               │
└──────────────────────────────────────────────────────────────┘
```

---

## Table View (default)

```
┌──────────────────────────────────────────────────────────────────────────────┐
│  Title                       Customer      Value       Status     Expires    │
│  ────────────────────────────────────────────────────────────────────────── │
│  Website Redesign 2026        Acme Corp    ₪24,000   ● SENT      In 5d      │
│  SEO Package Q3               Beta Ltd     ₪8,500    ● VIEWED    In 12d     │
│  Mobile App MVP               —            ₪55,000   ● DRAFT     —          │
│  Annual Support Retainer      Gamma Inc    ₪36,000   ✓ ACCEPTED  —          │
│  Logo Refresh                 Delta Co     ₪4,200    ✗ REJECTED  Expired    │
└──────────────────────────────────────────────────────────────────────────────┘
```

**Value column:** `proposals.total_amount` (denormalized by spec 130 editor on save); "—" if NULL.

**Status badges:**

| Status | Color token | Icon |
|--------|-------------|------|
| `DRAFT` | `--ink-soft` | pencil |
| `SENT` | `--info` (blue) | paper plane |
| `VIEWED` | `--warning` (amber) | eye |
| `ACCEPTED` | `--success` (green) | check |
| `REJECTED` | `--danger` (red) | × |
| `EXPIRED` | `--ink-soft` (grey) | clock |

**Expiry column:**
- `In Xd` — days until `expires_at` (green if > 7d, amber if ≤ 7d)
- `Expired` — `expires_at` in the past, status still SENT/VIEWED
- `—` — no expiry set or status is ACCEPTED/REJECTED/EXPIRED

**Sorting:** click column header. Default: `created_at DESC`.

---

## Pipeline View

Kanban columns by status. Cards show: title, customer, value, expiry countdown.

```
DRAFT (3)     SENT (5)      VIEWED (2)    ACCEPTED (8)  REJECTED (4)
─────────     ──────────    ──────────    ────────────  ────────────
[Website...]  [SEO Pack.]   [Mobile App]  [Retainer]    [Logo...]
₪24,000       ₪8,500        ₪55,000       ₪36,000       ₪4,200
Acme Corp     Beta Ltd      —             Gamma Inc      Delta Co
              In 5d ⚠        In 12d
```

Cards are non-draggable (status transitions require explicit user action, not drag). Click card → proposal detail.

---

## Filters

| Filter | Values |
|--------|--------|
| Status | All · Draft · Sent · Viewed · Accepted · Rejected · Expired |
| Customer | Type-ahead from `customers` |
| Expiry | Any · Expiring this week · Expiring this month · No expiry |
| Date range | Created after / before (date pickers) |

---

## Quick Actions (row hover / kebab menu)

- **Edit** — opens proposal editor (only for DRAFT; greyed out otherwise)
- **Copy link** — copies `zync.is/p/{token}` to clipboard
- **Resend** — re-sends proposal email (SENT/VIEWED only)
- **Mark accepted** — staff can accept on behalf of customer (confirmation prompt)
- **Duplicate** — creates DRAFT copy with "Copy of…" title
- **Delete** — only DRAFT; confirmation dialog

---

## Empty State

No proposals:
```
No proposals yet
Create a proposal to share with potential clients.
[New proposal]
```

No results for current filter:
```
No proposals match your filters.
[Clear filters]
```

---

## API

No new endpoints. Uses spec 23 API:

```
GET /api/proposals
    → list proposals
      Query: status?, customer_id?, expires_before?, expires_after?,
             created_after?, created_before?, sort?, page?, per_page?
      Returns: [{ id, title, customer_id, customer_name, status,
                  total_value, expires_at, sent_at, created_at, public_token }]
      Requires: marketing:read
```

`total_value` = `proposals.total_amount` column (denormalized by spec 130 on save); 0 if NULL.

---

## Sidebar Navigation

`/proposals` entry in sidebar under Marketing:

```
Marketing
  Leads
  Proposals          ← this spec
  Campaigns
  Catalogs
```

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Table view as default | Not pipeline | Most tenants have >20 proposals; table handles density better; pipeline is opt-in for sales funnel visualization |
| Cards non-draggable in pipeline | Not drag-to-change-status | Status transitions have side effects (webhook, email, lead stage advance); accidental drag would trigger them silently |
| `total_value` from JSONB server-side | Not stored column | Value can be updated in editor; a denorm column would need a trigger; JSONB traversal is fast for list queries |
| EXPIRED included as pipeline column | Not hidden | Expired proposals need follow-up; hiding them requires a filter click; the column is a reminder to act |
