fix(ux): replace confirm() with ConfirmModal, fix dark-mode colors, add currency format

- Add reusable ConfirmModal component (themed, Escape key, focus trap)
- Replace all native confirm() calls in Orders, ProductLibrary, Materials, Admin, Billing
- Fix ValidationDialog (Upload.tsx) hardcoded bg-white/text-gray-* → semantic tokens
- Fix NewInvoiceModal (Billing.tsx) hardcoded colors → semantic tokens
- Add formatCurrency (Intl.NumberFormat de-DE/EUR) to NewProductOrder wizard
- Resolves audit issues C1, C3, M3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 19:59:13 +01:00
parent 9f54bc3ab1
commit 915abe9d74
8 changed files with 307 additions and 45 deletions
+7 -4
View File
@@ -13,6 +13,9 @@ import { estimatePrice } from '../api/pricing'
import type { Product, RenderPosition } from '../api/products'
import type { OutputType } from '../api/outputTypes'
const formatCurrency = (amount: number) =>
new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(amount)
const CATEGORIES = [
{ key: 'TRB', label: 'TRB' },
{ key: 'Kugellager', label: 'Kugellager' },
@@ -620,7 +623,7 @@ export default function NewProductOrderPage() {
<span className="text-sm text-content-muted">
{selectedProducts.size} product{selectedProducts.size !== 1 ? 's' : ''} &middot; {orderLines.length} render job{orderLines.length !== 1 ? 's' : ''}
{priceEstimate && priceEstimate.total > 0 && (
<> &middot; Estimated: <span className="font-semibold text-content-secondary">{priceEstimate.total.toFixed(2)}</span></>
<> &middot; Estimated: <span className="font-semibold text-content-secondary">{formatCurrency(priceEstimate.total)}</span></>
)}
</span>
</div>
@@ -695,7 +698,7 @@ export default function NewProductOrderPage() {
{(() => {
const price = getLinePrice(line.product.id, line.outputType.id)
return price != null ? (
<span className="font-medium text-content-secondary">{price.toFixed(2)}</span>
<span className="font-medium text-content-secondary">{formatCurrency(price)}</span>
) : (
<span className="text-content-muted"></span>
)
@@ -741,7 +744,7 @@ export default function NewProductOrderPage() {
<span className="text-sm text-content-muted">
{orderLines.length} render job{orderLines.length !== 1 ? 's' : ''}
{priceEstimate && priceEstimate.total > 0 && (
<> &middot; Estimated: <span className="font-semibold text-content-secondary">{priceEstimate.total.toFixed(2)}</span></>
<> &middot; Estimated: <span className="font-semibold text-content-secondary">{formatCurrency(priceEstimate.total)}</span></>
)}
</span>
<button
@@ -839,7 +842,7 @@ function ProductOutputRow({
<p className="text-xs text-content-muted">
{ot.renderer} &middot; {ot.output_format.toUpperCase()}
{ot.price_per_item != null && (
<> &middot; <span className="text-emerald-600 font-medium">{ot.price_per_item.toFixed(2)}</span></>
<> &middot; <span className="text-emerald-600 font-medium">{formatCurrency(ot.price_per_item)}</span></>
)}
</p>
</div>