feat: visual layout editor for check field positioning

- SVG canvas showing all layout fields scaled to check dimensions
- Click or dropdown to select a field; drag to reposition
- Sidebar shows X/Y coordinates in decimal inches with fraction
  equivalents (¼", ½", ¹⁄₁₆", etc.)
- End X/Y inputs appear for Line and Graph fields
- Nudge buttons move selected field by ¹⁄₁₆" per click
- Auto-saves on drag end; debounced save on input/nudge changes
- Visible toggle hides fields from PDF without deleting them
- Admin-only Reset to Default wipes and re-seeds the layout
- Accessible to editor+ role via ⊞ button in account header
This commit is contained in:
2026-04-01 15:01:30 -06:00
parent 8a944d1d20
commit d70081159d
4 changed files with 429 additions and 1 deletions
+70
View File
@@ -85,6 +85,7 @@
<span class="header-brand" id="company-name">ezcheck</span>
<select id="account-switcher" class="account-switcher" title="Switch account"></select>
<button id="btn-account-settings" class="btn-header-icon" title="Account settings" data-admin-only></button>
<button id="btn-layout-editor" class="btn-header-icon" title="Edit check layout" data-editor-only></button>
<button id="btn-add-account" class="btn-header-icon" title="Add checking account" data-admin-only>+</button>
</div>
<div class="header-right">
@@ -743,6 +744,75 @@
</div>
</div>
<!-- Layout Editor Modal -->
<div id="layout-editor-overlay" class="modal-overlay"></div>
<div id="layout-editor-modal" class="modal modal-layout-editor" role="dialog" aria-labelledby="layout-editor-title">
<div class="modal-header">
<h2 id="layout-editor-title">Layout Editor</h2>
<button id="btn-close-layout-editor" class="btn-icon" title="Close">×</button>
</div>
<div class="modal-body layout-editor-body">
<div id="layout-canvas-container"><!-- SVG rendered by JS --></div>
<div id="layout-sidebar">
<div class="form-group">
<label for="layout-field-select">Field</label>
<select id="layout-field-select" style="font-size:12px"></select>
</div>
<label style="display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer">
<input type="checkbox" id="layout-field-visible"> Visible
</label>
<div class="form-group">
<label for="layout-field-x">X Position</label>
<div style="display:flex;align-items:center;gap:6px">
<input type="number" id="layout-field-x" step="0.0625" min="0" max="8.5" style="width:72px">
<span id="layout-field-x-frac" style="font-size:11px;color:var(--text-muted)"></span>
</div>
</div>
<div class="form-group">
<label for="layout-field-y">Y Position</label>
<div style="display:flex;align-items:center;gap:6px">
<input type="number" id="layout-field-y" step="0.0625" min="0" max="3.5" style="width:72px">
<span id="layout-field-y-frac" style="font-size:11px;color:var(--text-muted)"></span>
</div>
</div>
<div id="layout-end-pos-group" hidden>
<div class="form-group">
<label for="layout-field-x2">End X</label>
<div style="display:flex;align-items:center;gap:6px">
<input type="number" id="layout-field-x2" step="0.0625" min="0" max="8.5" style="width:72px">
<span id="layout-field-x2-frac" style="font-size:11px;color:var(--text-muted)"></span>
</div>
</div>
<div class="form-group">
<label for="layout-field-y2">End Y</label>
<div style="display:flex;align-items:center;gap:6px">
<input type="number" id="layout-field-y2" step="0.0625" min="0" max="3.5" style="width:72px">
<span id="layout-field-y2-frac" style="font-size:11px;color:var(--text-muted)"></span>
</div>
</div>
</div>
<div>
<div style="font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);margin-bottom:6px">Nudge ¹⁄₁₆"</div>
<div style="display:grid;grid-template-columns:repeat(3,28px);grid-template-rows:repeat(3,28px);gap:3px;justify-content:center">
<span></span>
<button id="nudge-up" class="btn-sm btn-secondary" style="padding:0;text-align:center"></button>
<span></span>
<button id="nudge-left" class="btn-sm btn-secondary" style="padding:0;text-align:center"></button>
<span></span>
<button id="nudge-right" class="btn-sm btn-secondary" style="padding:0;text-align:center"></button>
<span></span>
<button id="nudge-down" class="btn-sm btn-secondary" style="padding:0;text-align:center"></button>
<span></span>
</div>
</div>
<div id="layout-save-status" style="font-size:11px;color:var(--text-muted);text-align:center;min-height:14px"></div>
<div style="margin-top:auto;padding-top:8px;border-top:1px solid var(--border)">
<button id="btn-layout-reset" class="btn-secondary btn-sm" style="width:100%;font-size:11px" data-admin-only>↺ Reset to Default</button>
</div>
</div>
</div>
</div>
<script src="/js/app.js"></script>
</body>
</html>