Enforce granular capabilities across cashup
Backend (server-side enforcement, not just UI): - auth.js: read caps from JWT; hasCap() + requireCap() helpers; legacy-token fallback (full access minus settings) so existing sessions keep working until re-login - finalise: submit final, delete draft, bulk-finalise, attachments - reports: multiday report, cash summary, debtors - floats: float management + safe count - settings: settings mutations (was is_admin) - count: draft save, newbook fetch Frontend: - can(user, cap) helper; User.caps from /verify - Nav items, routes and actions (Submit Final, delete, bulk-finalise) gated on capabilities; non-finalisers see a draft-only hint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
43c332be9b
commit
be670f724d
13 changed files with 117 additions and 52 deletions
|
|
@ -2,11 +2,12 @@ import { useState, useEffect, useCallback } from 'react'
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { api } from '../api'
|
||||
import { PageHeader, Card, Btn, StatusBadge } from '../components/Layout'
|
||||
import { fmtGBP, today } from '../types'
|
||||
import type { CashUp } from '../types'
|
||||
import { fmtGBP, today, can } from '../types'
|
||||
import type { CashUp, User } from '../types'
|
||||
|
||||
export function History() {
|
||||
export function History({ user }: { user: User }) {
|
||||
const navigate = useNavigate()
|
||||
const canFinalise = can(user, 'finalise')
|
||||
const [rows, setRows] = useState<CashUp[]>([])
|
||||
const [total, setTotal] = useState(0)
|
||||
const [offset, setOffset] = useState(0)
|
||||
|
|
@ -99,7 +100,7 @@ export function History() {
|
|||
<input type="date" value={to} onChange={e => { setTo(e.target.value); setOffset(0) }} style={inpSt} />
|
||||
</div>
|
||||
<Btn onClick={() => { setOffset(0); load() }} small>Filter</Btn>
|
||||
{selected.size > 0 && (
|
||||
{canFinalise && selected.size > 0 && (
|
||||
<Btn onClick={bulkFinalize} small>Finalise {selected.size} selected</Btn>
|
||||
)}
|
||||
</Card>
|
||||
|
|
@ -129,7 +130,7 @@ export function History() {
|
|||
{rows.map(row => (
|
||||
<tr key={row.id} style={{ borderBottom: '1px solid var(--card-border)' }}>
|
||||
<td style={tdSt}>
|
||||
{row.status === 'draft' && (
|
||||
{canFinalise && row.status === 'draft' && (
|
||||
<input type="checkbox" checked={selected.has(row.id)}
|
||||
onChange={() => toggleSelect(row.id)} />
|
||||
)}
|
||||
|
|
@ -151,7 +152,7 @@ export function History() {
|
|||
onClick={() => navigate(`/daily?date=${row.session_date.slice(0, 10)}`)}>
|
||||
{row.status === 'draft' ? 'Edit' : 'View'}
|
||||
</Btn>
|
||||
{row.status === 'draft' && (
|
||||
{canFinalise && row.status === 'draft' && (
|
||||
<Btn small variant="danger" onClick={() => deleteDraft(row.id)}>Delete</Btn>
|
||||
)}
|
||||
</td>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue