Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions ffe/app/static/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -1455,12 +1455,42 @@ tr.fam-row td{background:var(--surface-2);border-top:2px solid var(--line)}tr.fa
.nw-kpis .try{border-color:var(--gold);background:var(--gold-soft)}.nw.tried .nw-kpis .try{box-shadow:0 0 0 2px var(--gold)}
.nw .green{color:#15803D}.nw .red{color:#B91C1C}
.nw-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin:6px 0 10px}.nw-chips .pick-chip.on{background:var(--accent-soft);border-color:var(--accent)}
.nw-prod{border-top:1px solid var(--line-2);padding:6px 0}
.nw-prod summary{display:grid;grid-template-columns:minmax(200px,2fr) repeat(3,minmax(110px,1fr));gap:8px;align-items:center;cursor:pointer;list-style:none}
.nw-prod{border-top:1px solid var(--line-2);padding:8px 0}
.nw-prod summary{display:grid;grid-template-columns:minmax(200px,2fr) repeat(3,minmax(110px,1fr));grid-template-areas:"name sheet today new" "meta sheet today new";column-gap:10px;row-gap:2px;align-items:center;cursor:pointer;list-style:none}
.nw-prod summary::-webkit-details-marker{display:none}
.nw-fig{font-variant-numeric:tabular-nums;text-align:right}
.nw-rooms{display:grid;gap:4px;padding:6px 0 6px 12px}
.nw-name{grid-area:name;display:flex;align-items:center;gap:6px;min-width:0}.nw-name b{font-weight:600}
.nw-meta{grid-area:meta;padding-left:22px}
.nw-chev{flex:none;color:var(--muted);transition:transform .15s}.nw-prod[open] .nw-chev{transform:rotate(180deg)}
.nw-fig{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}.nw-fig .l{font-size:.72rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-right:4px}
.nw-sheet{grid-area:sheet}.nw-today{grid-area:today}.nw-new{grid-area:new}
.nw-rooms{display:grid;gap:4px;padding:8px 0 4px 22px}
.nw-room{display:grid;grid-template-columns:20px 1fr 90px 40px;gap:8px;align-items:center}.nw-room input[type=checkbox]{width:18px;height:18px}
.nw-room.out>span:first-of-type{text-decoration:line-through;color:var(--muted)}.nw-room.locked{opacity:.6}
.nw-acts{flex-wrap:wrap;gap:8px}
@media(max-width:700px){.nw-prod summary{grid-template-columns:1fr 1fr}.nw-name{grid-column:1/-1}.nw-fig{text-align:left}}
.nw-acts{flex-wrap:wrap;gap:8px;align-items:center}
.nw-bar{display:none}
@media(max-width:700px){
.nw-kpis{gap:6px}.nw-kpis>div{flex-direction:row;flex-wrap:wrap;align-items:baseline;justify-content:space-between;padding:8px 10px}
.nw-kpis .v{font-size:1rem}.nw-kpis .d{flex-basis:100%;font-size:.75rem}.nw-kpis .try{display:none}
.nw-prod summary{grid-template-columns:1fr 1fr;grid-template-areas:"name name" "meta meta" "sheet today" "new new";row-gap:4px}
.nw-name b{font-size:.95rem;line-height:1.3}.nw-fig{text-align:left;font-size:.88rem}.nw-new:empty{display:none}
.nw-rooms{padding-left:0}.nw-room{grid-template-columns:20px 1fr 72px 30px;gap:6px}.nw-room .in{padding:6px 8px}
.nw-acts{position:sticky;bottom:calc(var(--bottom-h) + env(safe-area-inset-bottom) + 8px);z-index:5;background:var(--surface,#fff);border:1px solid var(--line);border-radius:14px;padding:8px 8px 8px 12px;box-shadow:0 6px 20px rgba(0,0,0,.12);margin-top:12px;flex-wrap:nowrap;gap:6px}
.nw-bar{display:flex;flex-direction:column;flex:1;min-width:0;line-height:1.2}.nw-bar .l{font-size:.65rem;text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}.nw-bar b{font-size:.95rem;white-space:nowrap}.nw-bar .small{font-size:.72rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nw-acts .btn{flex:none;padding:8px 12px;font-size:.85rem}.nw-hint{display:none}
}
/* the saved quotation's lines on a phone: a card per line instead of a seven-column table */
@media(max-width:700px){
.qi-saved thead{display:none}
.qi-saved,.qi-saved tbody,.qi-saved tfoot{display:block;width:100%;min-width:0}
.qi-saved tbody tr{display:grid;grid-template-columns:52px 1fr auto;grid-template-areas:"pic name amt" "pic line qty" "pic line price" "bec bec bec";column-gap:10px;row-gap:2px;padding:10px 12px;border-bottom:1px solid var(--line-2)}
.qi-saved tbody td{display:block;padding:0;border:0;min-width:0}
.qi-saved tbody tr.qi-sec{display:block;padding:8px 12px}
.qi-saved td.qi-td-pic{grid-area:pic}.qi-saved td.qi-td-pic img{width:52px;height:52px;object-fit:cover;border-radius:8px}
.qi-saved tbody td:nth-child(2){grid-area:line}.qi-saved tbody td:nth-child(3){grid-area:name}
.qi-saved tbody td:nth-child(4){grid-area:qty;text-align:right;color:var(--muted);font-size:.85rem}
.qi-saved tbody td:nth-child(5){grid-area:price;text-align:right;color:var(--muted);font-size:.85rem}.qi-saved tbody td:nth-child(5)::before{content:"@ "}
.qi-saved tbody td:nth-child(6){grid-area:amt;text-align:right;font-weight:600}
.qi-saved td.qi-became{grid-area:bec;margin-top:6px}
.qi-saved tbody tr:has(td.qi-td-pic:empty){grid-template-columns:0 1fr auto;column-gap:0}.qi-saved tbody tr:has(td.qi-td-pic:empty) td:nth-child(n+4){padding-left:10px}
.qi-saved tfoot tr{display:flex;justify-content:space-between;padding:10px 12px}.qi-saved tfoot td{display:block;padding:0;border:0}
}
19 changes: 11 additions & 8 deletions ffe/app/templates/buying/quotation.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,10 @@ <h1>{{ q.title or q.filename or 'Quotation' }} <span class="h-note">{{ q.supplie
<div class="nw-list">
{% for r in now.rows %}{% set t = now_amount(r) %}
<details class="nw-prod" data-pid="{{ r.pid }}">
<summary><span class="nw-name"><b>{{ r.title }}</b>{% if r.options %} <span class="pill gold">options</span>{% endif %}<span class="small muted"> · {{ r.rooms|length }} room line{{ 's' if r.rooms|length != 1 }} · {{ r.price|num(2) }} per {{ r.unit or 'unit' }}</span></span>
<span class="nw-fig"><span class="small muted">sheet</span> {{ r.sheet_qty|num(2) }} · {{ r.sheet_amount|money(2) }}</span>
<span class="nw-fig"><span class="small muted">today</span> {{ t[0]|num(2) }} · {{ t[1]|money(2) }}</span>
<summary><span class="nw-name"><svg class="ic sm nw-chev"><use href="#i-chevron-down"/></svg><b>{{ r.title }}</b>{% if r.options %} <span class="pill gold">options</span>{% endif %}</span>
<span class="nw-meta small muted">{{ r.rooms|length }} room line{{ 's' if r.rooms|length != 1 }} · {{ r.price|num(2) }} per {{ r.unit or 'unit' }}</span>
<span class="nw-fig nw-sheet"><span class="l">Sheet</span> {{ r.sheet_qty|num(2) }} · {{ r.sheet_amount|money(2) }}</span>
<span class="nw-fig nw-today"><span class="l">Today</span> {{ t[0]|num(2) }} · {{ t[1]|money(2) }}</span>
<span class="nw-fig nw-new" data-pid="{{ r.pid }}"></span></summary>
<div class="nw-rooms">
{% for x in r.rooms %}
Expand All @@ -63,15 +64,16 @@ <h1>{{ q.title or q.filename or 'Quotation' }} <span class="h-note">{{ q.supplie
{% endfor %}
</div>
<div class="row mt nw-acts">
<button class="btn" data-confirm="Apply these changes to the design? The room lines take the new quantities and the ones you left out are deleted (their photos stay with the product). Buying, the Tracker and the orders follow; the quotation keeps the shop's figures." data-confirm-ok="Apply to the design"><svg class="ic"><use href="#i-check"/></svg>Apply to the design</button>
<button class="btn sec" formaction="/p/{{ p.id }}/quotations/{{ q.id }}/revised.xlsx"><svg class="ic"><use href="#i-download"/></svg>Revised quantities for the shop (Excel)</button>
<span class="small muted">The Excel lists the shop's lines with the quantities tried here, for them to confirm their prices.</span>
<div class="nw-bar"><span class="l">New total</span><b id="nw-bar-total">{{ cur }} {{ now.today|money(2) }}</b><span class="small" id="nw-bar-delta"></span></div>
<button class="btn" data-confirm="Apply these changes to the design? The room lines take the new quantities and the ones you left out are deleted (their photos stay with the product). Buying, the Tracker and the orders follow; the quotation keeps the shop's figures." data-confirm-ok="Apply to the design"><svg class="ic"><use href="#i-check"/></svg><span class="hide-m">Apply to the design</span><span class="hide-d">Apply</span></button>
<button class="btn sec" formaction="/p/{{ p.id }}/quotations/{{ q.id }}/revised.xlsx"><svg class="ic"><use href="#i-download"/></svg><span class="hide-m">Revised quantities for the shop (Excel)</span><span class="hide-d">Excel</span></button>
<span class="small muted nw-hint">The Excel lists the shop's lines with the quantities tried here, for them to confirm their prices.</span>
</div>
<script type="application/json" id="nw-data">{{ now_json|safe }}</script>
</form>
{% endif %}

<div class="tbl"><table class="qi-doc">
<div class="tbl"><table class="qi-doc qi-saved">
<thead><tr><th></th><th>Line</th><th>As written</th><th class="num">Qty</th><th class="num">Unit price</th><th class="num">Amount</th><th>Became</th></tr></thead>
<tbody>
{% set ns = namespace(section=none) %}
Expand Down Expand Up @@ -153,10 +155,11 @@ <h2 class="mb-0">Terms as written</h2>
var sum=0; r.rooms.forEach(function(x){ sum+=q(x); }); var o=order(r, sum); return [o, o*r.price]; }
function update(){ var today=0, tried=0, changed=0;
rows.forEach(function(r){ var a=amount(r,false), b=amount(r,true); today+=a[1]; tried+=b[1]; var cell=form.querySelector('.nw-new[data-pid="'+r.pid+'"]'), d=b[1]-a[1];
if(cell){ cell.innerHTML=Math.abs(d)>0.005?'<span class="small muted">new</span> '+(Math.round(b[0]*100)/100)+' · <b>'+fmt(b[1])+'</b> <span class="'+(d<0?'green':'red')+'">'+(d>0?'+':'')+fmt(d)+'</span>':''; }
if(cell){ cell.innerHTML=Math.abs(d)>0.005?'<span class="l">New</span> '+(Math.round(b[0]*100)/100)+' · <b>'+fmt(b[1])+'</b> <span class="'+(d<0?'green':'red')+'">'+(d>0?'+':'')+fmt(d)+'</span>':''; }
r.rooms.forEach(function(x){ var v=qtyOf(x.id); var lab=form.querySelector('input[name="keep_'+x.id+'"]'); if(lab) lab.closest('.nw-room').classList.toggle('out', v===0); if(v!==null&&Math.abs(v-x.qty)>1e-9) changed++; }); });
var d=tried-today; document.getElementById('nw-total').textContent=cur+' '+fmt(tried);
document.getElementById('nw-delta').innerHTML=changed?'<span class="'+(d<0?'green':'red')+'">'+(d>0?'+':'')+fmt(d)+'</span> against today · '+changed+' room line'+(changed===1?'':'s')+' changed':'no change tried yet';
document.getElementById('nw-bar-total').textContent=cur+' '+fmt(tried); document.getElementById('nw-bar-delta').innerHTML=changed?'<span class="'+(d<0?'green':'red')+'">'+(d>0?'+':'')+fmt(d)+'</span> · '+changed+' changed':'';
form.classList.toggle('tried', changed>0); }
form.addEventListener('input', update); form.addEventListener('change', update);
form.addEventListener('click', function(e){ var chip=e.target.closest('.nw-chips .pick-chip'); if(!chip) return;
Expand Down