Seth commited on
Commit
efad20e
·
1 Parent(s): bb17080
Files changed (1) hide show
  1. frontend/src/pages/Deals.jsx +88 -67
frontend/src/pages/Deals.jsx CHANGED
@@ -240,79 +240,100 @@ function PipelineDealCard({ deal, openDeal }) {
240
  );
241
  }
242
 
243
- function PipelineBoard({ columns, openDeal, patchDeal, createDeal, createBusy }) {
 
 
244
  return (
245
  <div
246
  className={cn(
247
- 'min-h-[min(75vh,720px)] w-full min-w-0 gap-2 pb-2 pt-1 [scrollbar-gutter:stable]',
248
- /* Equal-width columns on lg+ so all stages fit; below lg: narrower columns + horizontal scroll */
249
- 'flex overflow-x-auto lg:grid lg:grid-cols-6 lg:overflow-visible lg:gap-2'
250
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
251
  >
252
- {columns.map((col) => {
253
- const sumDeal = sumNumeric(col.deals, 'deal_value');
254
- const headerBg = PIPELINE_HEADER_BG[col.value] || 'bg-slate-600';
255
- return (
256
- <div
257
- key={col.value}
258
- className={cn(
259
- 'flex shrink-0 flex-col rounded-xl border border-slate-200 bg-slate-100/90 shadow-sm',
260
- 'min-w-[200px] w-[200px] max-w-[220px]',
261
- 'lg:min-w-0 lg:w-auto lg:max-w-none'
262
- )}
263
- onDragOver={(e) => {
264
- e.preventDefault();
265
- e.dataTransfer.dropEffect = 'move';
266
- }}
267
- onDrop={(e) => {
268
- e.preventDefault();
269
- const raw =
270
- e.dataTransfer.getData('application/deal-id') ||
271
- e.dataTransfer.getData('text/plain');
272
- const id = Number(raw);
273
- if (!Number.isFinite(id)) return;
274
- patchDeal(id, { stage: col.value });
275
- }}
 
 
 
 
 
276
  >
277
- <div
278
- className={cn(
279
- 'relative shrink-0 px-2 pb-2.5 pt-2 text-white shadow-sm sm:px-2.5 sm:pb-3 sm:pt-2.5',
280
- headerBg
281
- )}
282
- style={pipelineHeaderClip}
283
- >
284
- <div className="flex items-start justify-between gap-1 pr-4 sm:gap-2 sm:pr-5">
285
- <span className="text-[11px] font-bold leading-tight tracking-wide sm:text-[13px]">
286
- {col.label}
287
- </span>
288
- <span className="shrink-0 rounded-full bg-black/15 px-2 py-0.5 text-xs font-bold tabular-nums">
289
- {col.deals.length}
290
- </span>
291
- </div>
292
- <div className="mt-2 text-xs font-semibold tabular-nums text-white/95">
293
- {fmtMoney(sumDeal)}
294
- </div>
295
- </div>
296
- <div className="flex max-h-[min(62vh,560px)] min-h-[120px] flex-1 flex-col gap-1.5 overflow-y-auto p-1.5 sm:gap-2 sm:p-2">
297
- {col.deals.map((deal) => (
298
- <PipelineDealCard key={deal.id} deal={deal} openDeal={openDeal} />
299
- ))}
300
- <div className="mt-auto shrink-0 pt-1">
301
- <Button
302
- type="button"
303
- variant="ghost"
304
- size="sm"
305
- className="h-8 w-full justify-center text-violet-700 hover:bg-violet-50"
306
- disabled={createBusy}
307
- onClick={() => createDeal(col.value)}
308
- >
309
- + Add deal
310
- </Button>
311
- </div>
312
- </div>
313
- </div>
314
- );
315
- })}
 
 
316
  </div>
317
  );
318
  }
 
240
  );
241
  }
242
 
243
+ function PipelineStageColumn({ col, openDeal, patchDeal, createDeal, createBusy, columnClassName }) {
244
+ const sumDeal = sumNumeric(col.deals, 'deal_value');
245
+ const headerBg = PIPELINE_HEADER_BG[col.value] || 'bg-slate-600';
246
  return (
247
  <div
248
  className={cn(
249
+ 'flex min-h-0 flex-col rounded-xl border border-slate-200 bg-slate-100/90 shadow-sm',
250
+ columnClassName
 
251
  )}
252
+ onDragOver={(e) => {
253
+ e.preventDefault();
254
+ e.dataTransfer.dropEffect = 'move';
255
+ }}
256
+ onDrop={(e) => {
257
+ e.preventDefault();
258
+ const raw =
259
+ e.dataTransfer.getData('application/deal-id') ||
260
+ e.dataTransfer.getData('text/plain');
261
+ const id = Number(raw);
262
+ if (!Number.isFinite(id)) return;
263
+ patchDeal(id, { stage: col.value });
264
+ }}
265
  >
266
+ <div
267
+ className={cn(
268
+ 'relative shrink-0 px-2.5 pb-3 pt-2.5 text-white shadow-sm sm:px-3',
269
+ headerBg
270
+ )}
271
+ style={pipelineHeaderClip}
272
+ >
273
+ <div className="flex items-start justify-between gap-2 pr-5">
274
+ <span className="text-[12px] font-bold leading-tight tracking-wide sm:text-[13px]">
275
+ {col.label}
276
+ </span>
277
+ <span className="shrink-0 rounded-full bg-black/15 px-2 py-0.5 text-xs font-bold tabular-nums">
278
+ {col.deals.length}
279
+ </span>
280
+ </div>
281
+ <div className="mt-2 text-xs font-semibold tabular-nums text-white/95">{fmtMoney(sumDeal)}</div>
282
+ </div>
283
+ <div className="flex max-h-[min(62vh,560px)] min-h-[120px] flex-1 flex-col gap-2 overflow-y-auto p-2">
284
+ {col.deals.map((deal) => (
285
+ <PipelineDealCard key={deal.id} deal={deal} openDeal={openDeal} />
286
+ ))}
287
+ <div className="mt-auto shrink-0 pt-1">
288
+ <Button
289
+ type="button"
290
+ variant="ghost"
291
+ size="sm"
292
+ className="h-8 w-full justify-center text-violet-700 hover:bg-violet-50"
293
+ disabled={createBusy}
294
+ onClick={() => createDeal(col.value)}
295
  >
296
+ + Add deal
297
+ </Button>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ );
302
+ }
303
+
304
+ function PipelineBoard({ columns, openDeal, patchDeal, createDeal, createBusy }) {
305
+ const mainCols = columns.slice(0, 5);
306
+ const lostCol = columns.find((c) => c.value === 'lost');
307
+
308
+ return (
309
+ <div className="flex min-h-[min(75vh,720px)] w-full min-w-0 gap-2 overflow-x-auto pb-2 pt-1 [scrollbar-gutter:stable]">
310
+ {/* New → Won: share full visible width equally */}
311
+ <div className="flex min-h-[min(75vh,720px)] min-w-full shrink-0 gap-2">
312
+ {mainCols.map((col) => (
313
+ <PipelineStageColumn
314
+ key={col.value}
315
+ col={col}
316
+ openDeal={openDeal}
317
+ patchDeal={patchDeal}
318
+ createDeal={createDeal}
319
+ createBusy={createBusy}
320
+ columnClassName="min-w-0 flex-1 basis-0"
321
+ />
322
+ ))}
323
+ </div>
324
+ {/* Lost: fixed column — scroll right to see */}
325
+ {lostCol ? (
326
+ <div className="flex min-h-[min(75vh,720px)] w-[min(280px,28vw)] min-w-[220px] shrink-0 flex-col self-stretch">
327
+ <PipelineStageColumn
328
+ col={lostCol}
329
+ openDeal={openDeal}
330
+ patchDeal={patchDeal}
331
+ createDeal={createDeal}
332
+ createBusy={createBusy}
333
+ columnClassName="min-h-0 flex-1"
334
+ />
335
+ </div>
336
+ ) : null}
337
  </div>
338
  );
339
  }