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:
+36
-1
@@ -11,7 +11,7 @@ const session = require('express-session');
|
||||
|
||||
const db = require('./db/database');
|
||||
const { seedLayoutFields } = require('./db/database');
|
||||
const { requireAuth, requireAdmin, canAccessAccount } = require('./middleware/auth');
|
||||
const { requireAuth, requireAdmin, canAccessAccount, isEditorForAccount } = require('./middleware/auth');
|
||||
|
||||
const app = express();
|
||||
const upload = multer({ dest: os.tmpdir() });
|
||||
@@ -261,6 +261,41 @@ app.post('/api/import', requireAdmin, upload.single('mdbfile'), (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ── Layout editor routes ───────────────────────────────────────────────────────
|
||||
|
||||
// GET /api/layout/:accountId — all layout_fields for an account
|
||||
app.get('/api/layout/:accountId', requireAuth, (req, res) => {
|
||||
const accountId = parseInt(req.params.accountId, 10);
|
||||
if (!canAccessAccount(req.session, accountId)) return res.status(403).json({ error: 'Access denied.' });
|
||||
const fields = db.prepare('SELECT * FROM layout_fields WHERE account_id = ? ORDER BY id').all(accountId);
|
||||
res.json(fields);
|
||||
});
|
||||
|
||||
// PUT /api/layout/:accountId/:fieldId — update position/visibility of one field
|
||||
app.put('/api/layout/:accountId/:fieldId', requireAuth, (req, res) => {
|
||||
const accountId = parseInt(req.params.accountId, 10);
|
||||
const fieldId = parseInt(req.params.fieldId, 10);
|
||||
if (!isEditorForAccount(req.session, accountId)) return res.status(403).json({ error: 'Write access required.' });
|
||||
const { x_pos, y_pos, x_end_pos, y_end_pos, visible } = req.body;
|
||||
db.prepare(`
|
||||
UPDATE layout_fields SET x_pos=?, y_pos=?, x_end_pos=?, y_end_pos=?, visible=?
|
||||
WHERE id=? AND account_id=?
|
||||
`).run(
|
||||
parseFloat(x_pos) || 0, parseFloat(y_pos) || 0,
|
||||
parseFloat(x_end_pos) || 0, parseFloat(y_end_pos) || 0,
|
||||
visible ? 1 : 0, fieldId, accountId
|
||||
);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// POST /api/layout/:accountId/reset — wipe and re-seed default layout (admin only)
|
||||
app.post('/api/layout/:accountId/reset', requireAdmin, (req, res) => {
|
||||
const accountId = parseInt(req.params.accountId, 10);
|
||||
db.prepare('DELETE FROM layout_fields WHERE account_id = ?').run(accountId);
|
||||
seedLayoutFields(accountId);
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Catch-all: serve index.html
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '../public/index.html'));
|
||||
|
||||
Reference in New Issue
Block a user