redesign: Layout2 design-system + dashboard/tasks rework + perf
Design system (Layout2): - Solid token-driven theme (no animated grid); single-source tokens for bg, cards (bg/border/radius), popups, fonts (Inter), and full semantic + data-viz color palette. Swept ~168 hardcoded colors to tokens. Dashboard: - Reflow: To Do + Calendar row (fixed right rail) over Activity / In-Progress / Team Performance; CSS height-match (To Do = Calendar); responsive stacking. - 'Tasks Not Started' -> 'To Do' card with R#/Assigned columns; compact money fmt. Tasks page: - Combined tabs into Tasks + Completed with a Status column. - Column-header sort + filter (Status, R#/new-vs-revision, Project, Task Type, Company) via portaled, scrollable FilterDropdown; removed toolbar filters and the projects side panel; headers stay visible when empty; renamed to 'Tasks'. Perf: - FK/filter index migration; removed redundant client/external scope waterfall (rely on RLS); parallel follow-up queries. Mobile: - Responsive grids; mobile topbar = hamburger only, blends with bg, proper offset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+21
-21
@@ -879,7 +879,7 @@ export default function BrandBook() {
|
||||
<LoadingButton className="btn btn-primary btn-sm" loading={generating} loadingText="Generating..." onClick={handleGenerate}>Generate PDF</LoadingButton>
|
||||
</div>
|
||||
{notification && (
|
||||
<span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)' }}>
|
||||
<span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, var(--danger-strong))' : 'var(--success, var(--success-strong))' }}>
|
||||
{notification.msg}
|
||||
</span>
|
||||
)}
|
||||
@@ -1182,7 +1182,7 @@ export default function BrandBook() {
|
||||
<LoadingButton className="btn btn-primary btn-sm" loading={generating} loadingText="Generating..." onClick={handleGenerate}>Generate PDF</LoadingButton>
|
||||
</div>
|
||||
{notification && (
|
||||
<span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, #dc2626)' : 'var(--success, #16a34a)' }}>
|
||||
<span style={{ fontSize: 13, color: notification.type === 'error' ? 'var(--danger, var(--danger-strong))' : 'var(--success, var(--success-strong))' }}>
|
||||
{notification.msg}
|
||||
</span>
|
||||
)}
|
||||
@@ -1230,7 +1230,7 @@ function SignCard({ sign, index, onChange, onPhotoChange, onRemove, canRemove, t
|
||||
{sign.photo && <span style={{ fontSize: 11, color: 'var(--accent)' }}>unsaved photo</span>}
|
||||
{canRemove && (
|
||||
<span role="button" onClick={onRemove}
|
||||
style={{ fontSize: 13, color: 'var(--danger, #dc2626)', padding: '2px 6px', cursor: 'pointer' }}>✕</span>
|
||||
style={{ fontSize: 13, color: 'var(--danger, var(--danger-strong))', padding: '2px 6px', cursor: 'pointer' }}>✕</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1336,7 +1336,7 @@ function PhotoField({ label, preview, fileName, dragging, inputRef, onDragEnter,
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: 12,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
@@ -1407,7 +1407,7 @@ function SiteMapDropZone({ preview, onFile, onClear, inputRef }) {
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: '24px 16px', textAlign: 'center', cursor: 'pointer',
|
||||
color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13, transition: 'all 0.15s',
|
||||
}}
|
||||
@@ -1438,7 +1438,7 @@ function SitePhotosDropZone({ photoItems, onFiles, onRemove, inputRef }) {
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: '20px 16px', textAlign: 'center', cursor: 'pointer',
|
||||
color: dragging ? 'var(--accent)' : 'var(--text-muted)', fontSize: 13,
|
||||
marginBottom: photoItems.length > 0 ? 12 : 0, transition: 'all 0.15s',
|
||||
@@ -1458,14 +1458,14 @@ function SitePhotosDropZone({ photoItems, onFiles, onRemove, inputRef }) {
|
||||
style={{ width: 80, height: 60, objectFit: 'cover', borderRadius: 4, border: '1px solid var(--border)', display: 'block' }}
|
||||
/>
|
||||
{item.file && (
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, background: 'rgba(245,165,35,0.8)', fontSize: 8, textAlign: 'center', borderRadius: '0 0 4px 4px', padding: '1px 2px', color: '#1a1a1a', fontWeight: 400 }}>NEW</div>
|
||||
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, background: 'color-mix(in srgb, var(--accent) 80%, transparent)', fontSize: 8, textAlign: 'center', borderRadius: '0 0 4px 4px', padding: '1px 2px', color: '#1a1a1a', fontWeight: 400 }}>NEW</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onRemove(i)}
|
||||
style={{
|
||||
position: 'absolute', top: -6, right: -6,
|
||||
background: '#dc2626', border: 'none', borderRadius: '50%',
|
||||
background: 'var(--danger-strong)', border: 'none', borderRadius: '50%',
|
||||
width: 18, height: 18, fontSize: 10, color: '#fff',
|
||||
cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', lineHeight: 1,
|
||||
}}
|
||||
@@ -1510,7 +1510,7 @@ function CombinedMockupPhotoField({
|
||||
const tileStyle = {
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: 12,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
@@ -1649,7 +1649,7 @@ function RecommendationPhotoField({ preview, fileName, dragging, inputRef, onDra
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: 12,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
@@ -1732,7 +1732,7 @@ function SignDetailPhotoField({ preview, fileName, dragging, inputRef, onDragEnt
|
||||
style={{
|
||||
border: `2px dashed ${dragging ? 'var(--accent)' : 'var(--border)'}`,
|
||||
borderRadius: 4,
|
||||
background: dragging ? 'rgba(245,165,35,0.05)' : 'var(--input-bg, var(--card-bg))',
|
||||
background: dragging ? 'color-mix(in srgb, var(--accent) 5%, transparent)' : 'var(--input-bg, var(--card-bg))',
|
||||
padding: 12,
|
||||
cursor: preview ? 'pointer' : 'default',
|
||||
display: 'flex',
|
||||
@@ -2420,7 +2420,7 @@ function DimensionEditorModal({ sourceImage, onApply, onCancel }) {
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ overflow: 'auto', flex: 1, background: '#2a2a2a', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative' }}>
|
||||
<div style={{ overflow: 'auto', flex: 1, background: 'var(--card-bg-2)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative' }}>
|
||||
{!loaded && <div style={{ padding: 48, color: '#888', fontSize: 13 }}>Loading image…</div>}
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
@@ -2833,7 +2833,7 @@ function PhotoEditorModal({
|
||||
ctx.restore();
|
||||
if (showGuides && item.id === selectedArtworkId) {
|
||||
const rotateHandle = getRotateHandle(item);
|
||||
ctx.strokeStyle = '#f5a523';
|
||||
ctx.strokeStyle = 'var(--accent)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.setLineDash([6, 4]);
|
||||
ctx.beginPath();
|
||||
@@ -2851,7 +2851,7 @@ function PhotoEditorModal({
|
||||
ctx.stroke();
|
||||
getArtworkHandles(item).forEach(({ x, y }) => {
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#f5a523';
|
||||
ctx.strokeStyle = 'var(--accent)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.rect(x - HANDLE_SIZE / 2, y - HANDLE_SIZE / 2, HANDLE_SIZE, HANDLE_SIZE);
|
||||
@@ -2859,13 +2859,13 @@ function PhotoEditorModal({
|
||||
ctx.stroke();
|
||||
});
|
||||
ctx.fillStyle = '#1a1a1a';
|
||||
ctx.strokeStyle = '#f5a523';
|
||||
ctx.strokeStyle = 'var(--accent)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(rotateHandle.x, rotateHandle.y, HANDLE_SIZE / 1.5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.fillStyle = '#f5a523';
|
||||
ctx.fillStyle = 'var(--accent)';
|
||||
ctx.font = '700 10px Helvetica, Arial, sans-serif';
|
||||
ctx.fillText('↻', rotateHandle.x - 4, rotateHandle.y + 4);
|
||||
}
|
||||
@@ -2895,14 +2895,14 @@ function PhotoEditorModal({
|
||||
}
|
||||
|
||||
if (showGuides && calibrationPoints.length > 0) {
|
||||
ctx.fillStyle = '#22c55e';
|
||||
ctx.fillStyle = 'var(--success)';
|
||||
calibrationPoints.forEach((point) => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
if (calibrationPoints.length === 2) {
|
||||
ctx.strokeStyle = '#22c55e';
|
||||
ctx.strokeStyle = 'var(--success)';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(calibrationPoints[0].x, calibrationPoints[0].y);
|
||||
@@ -3604,7 +3604,7 @@ function PhotoEditorModal({
|
||||
}}
|
||||
style={{
|
||||
border: `1px solid ${item.id === activeDimensionId ? 'var(--accent)' : 'var(--border)'}`,
|
||||
background: item.id === activeDimensionId ? 'rgba(245,165,35,0.12)' : 'var(--card-bg-2)',
|
||||
background: item.id === activeDimensionId ? 'color-mix(in srgb, var(--accent) 12%, transparent)' : 'var(--card-bg-2)',
|
||||
color: 'var(--text-primary)',
|
||||
borderRadius: 4,
|
||||
padding: '8px 10px',
|
||||
@@ -3648,7 +3648,7 @@ function PhotoEditorModal({
|
||||
}}
|
||||
style={{
|
||||
border: `1px solid ${item.id === selectedArtworkId ? 'var(--accent)' : 'var(--border)'}`,
|
||||
background: item.id === selectedArtworkId ? 'rgba(245,165,35,0.12)' : 'var(--card-bg-2)',
|
||||
background: item.id === selectedArtworkId ? 'color-mix(in srgb, var(--accent) 12%, transparent)' : 'var(--card-bg-2)',
|
||||
color: 'var(--text-primary)',
|
||||
borderRadius: 4,
|
||||
padding: '8px 10px',
|
||||
@@ -3675,7 +3675,7 @@ function PhotoEditorModal({
|
||||
<div
|
||||
onDragOver={e => e.preventDefault()}
|
||||
onDrop={handleArtworkDrop}
|
||||
style={{ overflow: 'auto', background: '#2a2a2a', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative', minHeight: 420 }}
|
||||
style={{ overflow: 'auto', background: 'var(--card-bg-2)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', position: 'relative', minHeight: 420 }}
|
||||
>
|
||||
{!loaded && (
|
||||
<div style={{ padding: 48, color: '#888', fontSize: 13 }}>Loading image…</div>
|
||||
|
||||
Reference in New Issue
Block a user