added on WebSTLs (webstls.com) at 2026-07-01 22:27:00
| 1 | <!-- Fulfillment detail. Backed by /fulfill_order.cgi. --> | |
| 2 | ||
| 3 | <style> | |
| 4 | .ord-grid { display:grid; grid-template-columns:2fr 1fr; gap:18px; align-items:start; } | |
| 5 | \@media (max-width: 1100px) { .ord-grid { grid-template-columns:1fr; } } | |
| 6 | ||
| 7 | .ord-card { background:var(--col-surface-1); border:1px solid var(--col-border); border-radius:12px; padding:18px; } | |
| 8 | .ord-card h3 { font-family:var(--font-display,inherit); font-size:14px; text-transform:uppercase; letter-spacing:1.5px; color:var(--col-text-3); margin:0 0 12px; font-weight:700; } | |
| 9 | .ord-card .stack > * + * { margin-top:10px; } | |
| 10 | ||
| 11 | .ord-addr { font-size:13px; color:var(--col-text); line-height:1.5; } | |
| 12 | .ord-addr em { color:var(--col-text-3); } | |
| 13 | ||
| 14 | .ord-kv { display:grid; grid-template-columns:auto 1fr; column-gap:14px; row-gap:5px; font-size:13px; } | |
| 15 | .ord-kv dt { color:var(--col-text-3); } | |
| 16 | .ord-kv dd { margin:0; color:var(--col-text); } | |
| 17 | ||
| 18 | .ord-item-row { display:grid; grid-template-columns:1fr auto auto; gap:10px; align-items:center; padding:10px 0; border-bottom:1px solid var(--col-border); } | |
| 19 | .ord-item-row:last-child { border-bottom:none; } | |
| 20 | .ord-item-name { font-weight:600; color:var(--col-text); font-size:14px; } | |
| 21 | .ord-item-meta { font-size:11px; color:var(--col-text-3); margin-top:2px; letter-spacing:0.4px; text-transform:uppercase; } | |
| 22 | .ord-item-actions { display:flex; gap:6px; align-items:center; } | |
| 23 | ||
| 24 | .ord-status-row { display:flex; gap:8px; align-items:center; flex-wrap:wrap; } | |
| 25 | .ord-status-row form { display:flex; gap:6px; align-items:center; flex-wrap:wrap; } | |
| 26 | ||
| 27 | /* Reuse the chip palette from /fulfill.cgi. */ | |
| 28 | .ful-chip { display:inline-flex; align-items:center; padding:3px 10px; border-radius:999px; font-size:11px; font-weight:700; letter-spacing:0.5px; text-transform:uppercase; } | |
| 29 | .fs-new { background:rgba(96,165,250,0.18); color:#60a5fa; } | |
| 30 | .fs-processing { background:rgba(167,139,250,0.20); color:#a78bfa; } | |
| 31 | .fs-printing { background:rgba(251,191,36,0.20); color:#fbbf24; } | |
| 32 | .fs-printed { background:rgba(251,191,36,0.32); color:#fde68a; } | |
| 33 | .fs-packed { background:rgba(6,182,212,0.22); color:#22d3ee; } | |
| 34 | .fs-shipped { background:rgba(34,197,94,0.20); color:#4ade80; } | |
| 35 | .fs-partially_shipped { background:rgba(34,197,94,0.12); color:#86efac; } | |
| 36 | .fs-delivered { background:rgba(34,197,94,0.32); color:#86efac; } | |
| 37 | .fs-completed { background:rgba(156,163,175,0.20); color:#d1d5db; } | |
| 38 | .fs-on_hold { background:rgba(245,158,11,0.18); color:#fbbf24; } | |
| 39 | .fs-cancelled { background:rgba(248,113,113,0.18); color:#f87171; } | |
| 40 | .fs-returned { background:rgba(248,113,113,0.32); color:#fca5a5; } | |
| 41 | .fs-not_required { background:rgba(156,163,175,0.10); color:var(--col-text-3); } | |
| 42 | ||
| 43 | .ord-label { display:flex; gap:10px; align-items:flex-start; padding:10px 12px; border:1px solid var(--col-border); border-radius:10px; margin-bottom:8px; background:var(--col-surface-2); } | |
| 44 | .ord-label.is-voided { opacity:0.55; } | |
| 45 | .ord-label-main { flex:1; } | |
| 46 | .ord-label-actions { display:flex; gap:6px; flex-shrink:0; } | |
| 47 | .ord-label-cost { font-family:var(--font-mono,monospace); font-weight:700; color:var(--col-accent-bright); } | |
| 48 | ||
| 49 | .ord-note { background:var(--col-surface-2); border-left:3px solid var(--col-accent); padding:8px 12px; border-radius:0 6px 6px 0; font-size:13px; line-height:1.4; } | |
| 50 | .ord-note + .ord-note { margin-top:6px; } | |
| 51 | .ord-note-time { font-size:11px; color:var(--col-text-3); margin-top:4px; } | |
| 52 | ||
| 53 | .ord-est { padding:10px 12px; background:rgba(34,197,94,0.10); border:1px solid rgba(34,197,94,0.3); border-radius:8px; font-size:13px; color:var(--col-text); } | |
| 54 | .ord-est strong { color:#86efac; } | |
| 55 | ||
| 56 | .ord-back { color:var(--col-text-3); text-decoration:none; font-size:13px; display:inline-flex; gap:6px; align-items:center; margin-bottom:10px; } | |
| 57 | .ord-back:hover { color:var(--col-text); } | |
| 58 | </style> | |
| 59 | ||
| 60 | <a href="/fulfill.cgi" class="ord-back"> | |
| 61 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px"><path d="M19 12H5"/><polyline points="12 19 5 12 12 5"/></svg> | |
| 62 | Back to all orders | |
| 63 | </a> | |
| 64 | ||
| 65 | <div class="page-head"> | |
| 66 | <div> | |
| 67 | <span class="page-eyebrow"><span class="dot"></span> Studio · Fulfillment</span> | |
| 68 | <h1 class="page-title">Order #$order_id <span class="ful-chip $order_status_class" style="font-size:13px;margin-left:8px;vertical-align:middle">$order_status_label</span></h1> | |
| 69 | <p class="page-subtitle">$buyer_email · Placed $created_at · \$$total $currency</p> | |
| 70 | </div> | |
| 71 | </div> | |
| 72 | ||
| 73 | [if:$has_flash] | |
| 74 | <div class="banner $flash_kind mb-3"> | |
| 75 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/></svg> | |
| 76 | <div class="text-sm fw-600">$flash_msg</div> | |
| 77 | </div> | |
| 78 | [/if] | |
| 79 | ||
| 80 | <!-- Status changer --> | |
| 81 | <div class="ord-card" style="margin-bottom:18px"> | |
| 82 | <div class="ord-status-row"> | |
| 83 | <strong style="font-size:13px;color:var(--col-text)">Order status:</strong> | |
| 84 | <form method="POST" action="/fulfill_order.cgi"> | |
| 85 | <input type="hidden" name="_act" value="set_status"> | |
| 86 | <input type="hidden" name="order_id" value="$order_id"> | |
| 87 | <select name="new_status" class="select" style="height:36px;min-width:160px"> | |
| 88 | <option value="new">New</option> | |
| 89 | <option value="processing">Processing</option> | |
| 90 | <option value="printing">Printing</option> | |
| 91 | <option value="printed">Printed</option> | |
| 92 | <option value="packed">Packed</option> | |
| 93 | <option value="shipped">Shipped</option> | |
| 94 | <option value="partially_shipped">Partially shipped</option> | |
| 95 | <option value="delivered">Delivered</option> | |
| 96 | <option value="completed">Completed</option> | |
| 97 | <option value="on_hold">On hold</option> | |
| 98 | <option value="cancelled">Cancelled</option> | |
| 99 | <option value="returned">Returned</option> | |
| 100 | </select> | |
| 101 | <label style="display:inline-flex;gap:6px;align-items:center;font-size:12px;color:var(--col-text-2);cursor:pointer"> | |
| 102 | <input type="checkbox" name="cascade" value="1" checked> apply to items | |
| 103 | </label> | |
| 104 | <button type="submit" class="btn btn-primary" style="height:36px">Update</button> | |
| 105 | </form> | |
| 106 | </div> | |
| 107 | </div> | |
| 108 | ||
| 109 | <div class="ord-grid"> | |
| 110 | ||
| 111 | <!-- LEFT COLUMN: items + labels + notes --> | |
| 112 | <div class="stack"> | |
| 113 | ||
| 114 | <div class="ord-card"> | |
| 115 | <h3>Items in this order</h3> | |
| 116 | [if:$has_items] | |
| 117 | <div> | |
| 118 | [loop:@items] | |
| 119 | <div class="ord-item-row"> | |
| 120 | <div> | |
| 121 | <div class="ord-item-name">$loop1.model_title <span style="color:var(--col-text-3);font-weight:400">× $loop1.quantity</span></div> | |
| 122 | <div class="ord-item-meta"> | |
| 123 | $loop1.purchase_kind | |
| 124 | [if:$loop1.color] · $loop1.color[/if] | |
| 125 | [if:$loop1.material] · $loop1.material[/if] | |
| 126 | [if:$loop1.weight] · $loop1.weight g[/if] | |
| 127 | [if:$loop1.has_tracking] · <a href="$loop1.tracking_url" target="_blank" class="text-brand">$loop1.tracking</a>[/if] | |
| 128 | </div> | |
| 129 | </div> | |
| 130 | <div style="font-family:var(--font-mono,monospace);font-weight:700">\$$loop1.price</div> | |
| 131 | <div class="ord-item-actions"> | |
| 132 | <span class="ful-chip $loop1.status_class">$loop1.status_label</span> | |
| 133 | [if:$loop1.is_physical] | |
| 134 | <form method="POST" action="/fulfill_order.cgi" style="display:inline-flex;gap:4px"> | |
| 135 | <input type="hidden" name="_act" value="update_item"> | |
| 136 | <input type="hidden" name="order_id" value="$order_id"> | |
| 137 | <input type="hidden" name="item_id" value="$loop1.id"> | |
| 138 | <select name="new_status" class="select" style="height:30px;font-size:12px"> | |
| 139 | <option value="new">New</option> | |
| 140 | <option value="processing">Processing</option> | |
| 141 | <option value="printing">Printing</option> | |
| 142 | <option value="printed">Printed</option> | |
| 143 | <option value="packed">Packed</option> | |
| 144 | <option value="shipped">Shipped</option> | |
| 145 | <option value="delivered">Delivered</option> | |
| 146 | <option value="completed">Completed</option> | |
| 147 | <option value="cancelled">Cancelled</option> | |
| 148 | <option value="returned">Returned</option> | |
| 149 | </select> | |
| 150 | <button type="submit" class="btn btn-secondary btn-sm">Set</button> | |
| 151 | </form> | |
| 152 | [/if] | |
| 153 | </div> | |
| 154 | </div> | |
| 155 | [/loop] | |
| 156 | </div> | |
| 157 | [/if] | |
| 158 | [if:!$has_items] | |
| 159 | <p class="text-xs text-dim">(no items)</p> | |
| 160 | [/if] | |
| 161 | </div> | |
| 162 | ||
| 163 | <div class="ord-card"> | |
| 164 | <h3>Shipping labels & tracking</h3> | |
| 165 | ||
| 166 | [if:$has_estimate] | |
| 167 | <div class="ord-est" style="margin-bottom:12px"> | |
| 168 | <strong>Rate estimate</strong> for this destination: <span style="font-family:var(--font-mono,monospace);font-weight:700">\$$est_dollars</span> via <em>$est_zone</em> | |
| 169 | [if:$est_min] · ETA $est_min–$est_max days[/if] | |
| 170 | <div class="text-xs text-dim" style="margin-top:2px">Formula: $est_formula cents (weight $weight_kg kg)</div> | |
| 171 | </div> | |
| 172 | [/if] | |
| 173 | [if:!$has_estimate] | |
| 174 | [if:!$has_zones] | |
| 175 | <div class="ord-est" style="background:rgba(245,158,11,0.10);border-color:rgba(245,158,11,0.32);margin-bottom:12px"> | |
| 176 | No shipping zones defined — <a href="$settings_url" class="text-brand">create one in Shipping Settings</a> to get rate estimates per destination. | |
| 177 | </div> | |
| 178 | [/if] | |
| 179 | [/if] | |
| 180 | ||
| 181 | [if:$has_labels] | |
| 182 | <div> | |
| 183 | [loop:@labels] | |
| 184 | <div class="ord-label[if:$loop1.is_voided] is-voided[/if]"> | |
| 185 | <div class="ord-label-main"> | |
| 186 | <div style="font-weight:600;font-size:14px">$loop1.carrier_label[if:$loop1.service] · $loop1.service[/if]</div> | |
| 187 | [if:$loop1.has_tracking] | |
| 188 | <div style="font-family:var(--font-mono,monospace);font-size:12px;margin-top:2px"><a href="$loop1.tracking_url" target="_blank" class="text-brand">$loop1.tracking</a></div> | |
| 189 | [/if] | |
| 190 | <div class="text-xs text-dim" style="margin-top:4px"> | |
| 191 | <span class="ful-chip" style="background:rgba(124,58,237,0.15);color:var(--col-accent-bright);font-size:10px">$loop1.status_label</span> | |
| 192 | [if:$loop1.has_cost] · <span class="ord-label-cost">\$$loop1.cost</span>[/if] | |
| 193 | [if:$loop1.weight] · $loop1.weight g[/if] | |
| 194 | [if:$loop1.purchased_at] · created $loop1.purchased_at[/if] | |
| 195 | [if:$loop1.shipped_at] · shipped $loop1.shipped_at[/if] | |
| 196 | </div> | |
| 197 | </div> | |
| 198 | <div class="ord-label-actions"> | |
| 199 | <a href="$loop1.print_href" class="btn btn-secondary btn-sm" target="_blank">Print</a> | |
| 200 | [if:$loop1.is_active] | |
| 201 | <form method="POST" action="/fulfill_order.cgi" style="display:inline"> | |
| 202 | <input type="hidden" name="_act" value="mark_shipped"> | |
| 203 | <input type="hidden" name="order_id" value="$order_id"> | |
| 204 | <input type="hidden" name="label_id" value="$loop1.id"> | |
| 205 | <button type="submit" class="btn btn-primary btn-sm">Mark shipped</button> | |
| 206 | </form> | |
| 207 | <form method="POST" action="/fulfill_order.cgi" style="display:inline" | |
| 208 | data-confirm-title="Void this label?" | |
| 209 | data-confirm-message="Voiding marks it cancelled in the system. If you paid the carrier, request a refund through their portal." | |
| 210 | data-confirm-style="danger"> | |
| 211 | <input type="hidden" name="_act" value="void_label"> | |
| 212 | <input type="hidden" name="order_id" value="$order_id"> | |
| 213 | <input type="hidden" name="label_id" value="$loop1.id"> | |
| 214 | <button type="submit" class="btn btn-danger btn-sm">Void</button> | |
| 215 | </form> | |
| 216 | [/if] | |
| 217 | </div> | |
| 218 | </div> | |
| 219 | [/loop] | |
| 220 | </div> | |
| 221 | [/if] | |
| 222 | ||
| 223 | <h3 style="margin-top:18px">Create label / record tracking</h3> | |
| 224 | <form method="POST" action="/fulfill_order.cgi" style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;align-items:end"> | |
| 225 | <input type="hidden" name="_act" value="create_label"> | |
| 226 | <input type="hidden" name="order_id" value="$order_id"> | |
| 227 | <div class="form-group" style="margin:0"> | |
| 228 | <label class="form-label">Carrier</label> | |
| 229 | <select name="carrier" class="select">$carrier_opts_html</select> | |
| 230 | </div> | |
| 231 | <div class="form-group" style="margin:0"> | |
| 232 | <label class="form-label">Service <span class="text-xs text-dim">(optional)</span></label> | |
| 233 | <input type="text" name="service" class="input" placeholder="e.g. Priority Mail"> | |
| 234 | </div> | |
| 235 | <div class="form-group" style="margin:0"> | |
| 236 | <label class="form-label">Tracking number</label> | |
| 237 | <input type="text" name="tracking_number" class="input" placeholder="9400 1000 0000 0000 0000 00"> | |
| 238 | </div> | |
| 239 | <div class="form-group" style="margin:0"> | |
| 240 | <label class="form-label">Cost (\$)</label> | |
| 241 | <input type="text" name="cost_cents" class="input" placeholder="Cents, e.g. 950 = \$9.50"> | |
| 242 | </div> | |
| 243 | <div class="form-group" style="margin:0;grid-column:span 2"> | |
| 244 | <label class="form-label">Tracking URL <span class="text-xs text-dim">(optional — auto-filled for common carriers)</span></label> | |
| 245 | <input type="text" name="tracking_url" class="input" placeholder="https://…"> | |
| 246 | </div> | |
| 247 | <div class="form-group" style="margin:0"> | |
| 248 | <label class="form-label">Weight (g)</label> | |
| 249 | <input type="text" name="weight_grams" class="input" value="$total_weight"> | |
| 250 | </div> | |
| 251 | <div class="form-group" style="margin:0"> | |
| 252 | <button type="submit" class="btn btn-primary" style="width:100%">Save label</button> | |
| 253 | </div> | |
| 254 | <div class="form-group" style="margin:0;grid-column:span 4"> | |
| 255 | <label class="form-label">Notes <span class="text-xs text-dim">(optional, only visible to you)</span></label> | |
| 256 | <input type="text" name="notes" class="input" placeholder="e.g. Picked up by USPS truck"> | |
| 257 | </div> | |
| 258 | </form> | |
| 259 | <p class="text-xs text-dim" style="margin-top:8px;line-height:1.5">Adding a tracking number auto-advances the order to <strong>Shipped</strong>. The label record is for your records; print it to generate an HTML label / packing slip.</p> | |
| 260 | </div> | |
| 261 | ||
| 262 | <div class="ord-card"> | |
| 263 | <h3>Notes & history</h3> | |
| 264 | <form method="POST" action="/fulfill_order.cgi" style="display:flex;gap:8px;align-items:start;margin-bottom:14px"> | |
| 265 | <input type="hidden" name="_act" value="add_note"> | |
| 266 | <input type="hidden" name="order_id" value="$order_id"> | |
| 267 | <input type="text" name="note" class="input" placeholder="Add a note (only visible to you)…" style="flex:1"> | |
| 268 | <button type="submit" class="btn btn-primary">Add</button> | |
| 269 | </form> | |
| 270 | [if:$has_notes] | |
| 271 | [loop:@notes] | |
| 272 | <div class="ord-note"> | |
| 273 | <div>$loop1.note</div> | |
| 274 | <div class="ord-note-time">$loop1.created_at</div> | |
| 275 | </div> | |
| 276 | [/loop] | |
| 277 | [/if] | |
| 278 | [if:!$has_notes] | |
| 279 | <p class="text-xs text-dim">No notes yet.</p> | |
| 280 | [/if] | |
| 281 | </div> | |
| 282 | </div> | |
| 283 | ||
| 284 | <!-- RIGHT COLUMN: addresses + totals --> | |
| 285 | <div class="stack"> | |
| 286 | <div class="ord-card"> | |
| 287 | <h3>Ship to</h3> | |
| 288 | <div class="ord-addr">$ship_to_html</div> | |
| 289 | </div> | |
| 290 | ||
| 291 | [if:$has_bill_to] | |
| 292 | <div class="ord-card"> | |
| 293 | <h3>Bill to</h3> | |
| 294 | <div class="ord-addr">$bill_to_html</div> | |
| 295 | </div> | |
| 296 | [/if] | |
| 297 | ||
| 298 | <div class="ord-card"> | |
| 299 | <h3>Ship from <span class="text-xs text-dim" style="float:right;letter-spacing:0">(<a href="$settings_url" class="text-brand">edit</a>)</span></h3> | |
| 300 | [if:$has_origin] | |
| 301 | <div class="ord-addr">$origin_html</div> | |
| 302 | [/if] | |
| 303 | [if:!$has_origin] | |
| 304 | <p class="text-xs text-dim" style="margin:0">No origin address yet. <a href="$settings_url" class="text-brand">Set one →</a></p> | |
| 305 | [/if] | |
| 306 | </div> | |
| 307 | ||
| 308 | <div class="ord-card"> | |
| 309 | <h3>Totals</h3> | |
| 310 | <dl class="ord-kv"> | |
| 311 | <dt>Subtotal</dt> <dd style="text-align:right;font-family:var(--font-mono,monospace)">\$$subtotal</dd> | |
| 312 | <dt>Shipping</dt> <dd style="text-align:right;font-family:var(--font-mono,monospace)">\$$shipping_paid</dd> | |
| 313 | <dt>Tax</dt> <dd style="text-align:right;font-family:var(--font-mono,monospace)">\$$tax_amount</dd> | |
| 314 | <dt style="font-weight:700;color:var(--col-text)">Total</dt> | |
| 315 | <dd style="text-align:right;font-family:var(--font-mono,monospace);font-weight:700;color:var(--col-accent-bright)">\$$total $currency</dd> | |
| 316 | <dt>Payment</dt> <dd style="text-align:right">$pay_status[if:$has_paid_at] · <span class="text-xs text-dim">$paid_at</span>[/if]</dd> | |
| 317 | </dl> | |
| 318 | </div> | |
| 319 | </div> | |
| 320 | </div> |