Files
TREK/client/src/components/Budget/BudgetPanel.tsx
T
Maurice e800699191 fix(appearance): scale inline px font sizes so text-size reaches all content
The global text-size control only set the root font-size, which scales rem-based text (navbar, menus) but not the dense inline px sizes used across the trip planner, budget, journey and panels — so place titles and addresses stayed fixed. applyAppearance now also exposes the factor as --fs-scale-text, and a codemod wraps inline numeric fontSize in calc(<px> * var(--fs-scale-text, 1)) across components and pages (map popups and PDF excluded). Sizes are byte-identical at 100%; the control now visibly resizes the actual content.
2026-06-29 11:47:11 +02:00

147 lines
8.9 KiB
TypeScript

import { Plus, Calculator, Download } from 'lucide-react'
import CustomSelect from '../shared/CustomSelect'
import { CURRENCIES, SYMBOLS } from './BudgetPanel.constants'
import { useBudgetPanel } from './useBudgetPanel'
import type { TripMember } from './BudgetPanelMemberChips'
import BudgetCategoryTable from './BudgetPanelCategoryTable'
import BudgetSummary from './BudgetPanelSummary'
export { splitColorFor } from './BudgetPanel.helpers'
// ── Main Component ───────────────────────────────────────────────────────────
interface BudgetPanelProps {
tripId: number
tripMembers?: TripMember[]
}
export default function BudgetPanel({ tripId, tripMembers = [] }: BudgetPanelProps) {
const {
budgetItems,
setBudgetItemMembers, toggleBudgetMemberPaid, reorderBudgetItems, reorderBudgetCategories,
t, locale, isDark, theme,
newCategoryName, setNewCategoryName,
editingCat, setEditingCat,
settlement, settlementOpen, setSettlementOpen,
currency, canEdit, fmt, hasMultipleMembers,
dragCat, setDragCat, dragOverCat, setDragOverCat,
dragItem, setDragItem, dragOverItem, setDragOverItem, dragItemCat, setDragItemCat,
setCurrency,
grouped, categoryNames, categoryColor, grandTotal, pieSegments,
handleAddItem, handleUpdateField, handleDeleteItem, handleDeleteCategory, handleRenameCategory, handleAddCategory, handleExportCsv,
th, td,
} = useBudgetPanel(tripId, tripMembers)
// ── Empty State ──────────────────────────────────────────────────────────
if (!budgetItems || budgetItems.length === 0) {
return (
<div style={{ padding: 24, maxWidth: 600, margin: '60px auto', textAlign: 'center' }}>
<div style={{ width: 64, height: 64, borderRadius: 16, background: 'var(--bg-tertiary)', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 20px' }}>
<Calculator size={28} color="#6b7280" />
</div>
<h2 style={{ fontSize: 'calc(20px * var(--fs-scale-text, 1))', fontWeight: 700, color: 'var(--text-primary)', margin: '0 0 8px' }}>{t('budget.emptyTitle')}</h2>
<p style={{ fontSize: 'calc(14px * var(--fs-scale-text, 1))', color: 'var(--text-muted)', margin: '0 0 24px', lineHeight: 1.5 }}>{t('budget.emptyText')}</p>
{canEdit && (
<div style={{ display: 'flex', gap: 6, justifyContent: 'center', alignItems: 'stretch', maxWidth: 320, margin: '0 auto' }}>
<input value={newCategoryName} onChange={e => setNewCategoryName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleAddCategory()}
placeholder={t('budget.emptyPlaceholder')}
style={{ flex: 1, padding: '9px 14px', borderRadius: 10, border: '1px solid var(--border-primary)', fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontFamily: 'inherit', outline: 'none', background: 'var(--bg-input)', color: 'var(--text-primary)', minWidth: 0 }} />
<button onClick={handleAddCategory} disabled={!newCategoryName.trim()}
style={{ background: 'var(--accent)', color: 'var(--accent-text)', border: 'none', borderRadius: 10, padding: '0 12px', cursor: 'pointer', display: 'flex', alignItems: 'center', opacity: newCategoryName.trim() ? 1 : 0.5, flexShrink: 0 }}>
<Plus size={16} />
</button>
</div>
)}
</div>
)
}
// ── Main Layout ──────────────────────────────────────────────────────────
return (
<div>
<div style={{ padding: '24px 28px 0' }} className="max-md:!px-4 max-md:!pt-4">
<div style={{
background: 'var(--bg-tertiary)', borderRadius: 18,
padding: '14px 16px 14px 22px',
display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap',
}}>
<h2 style={{ margin: 0, fontSize: 'calc(18px * var(--fs-scale-text, 1))', fontWeight: 600, color: 'var(--text-primary)', letterSpacing: '-0.01em', flexShrink: 0 }}>
{t('budget.title')}
</h2>
<div className="flex flex-wrap max-md:!w-full max-md:!mt-2" style={{ alignItems: 'center', gap: 8, marginLeft: 'auto', flexShrink: 0 }}>
<div className="max-md:!w-full" style={{ width: 150 }}>
<CustomSelect
value={currency}
onChange={setCurrency}
disabled={!canEdit}
options={CURRENCIES.map(c => ({ value: c, label: `${c} (${SYMBOLS[c] || c})` }))}
searchable
/>
</div>
{canEdit && (
<div className="max-md:!w-full" style={{ display: 'flex', gap: 6, width: 260 }}>
<input
value={newCategoryName}
onChange={e => setNewCategoryName(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') handleAddCategory() }}
placeholder={t('budget.categoryName')}
style={{ flex: 1, minWidth: 0, border: '1px solid var(--border-primary)', borderRadius: 10, padding: '9px 14px', fontSize: 'calc(13px * var(--fs-scale-text, 1))', outline: 'none', fontFamily: 'inherit', background: 'var(--bg-card)', color: 'var(--text-primary)' }}
/>
<button onClick={handleAddCategory} disabled={!newCategoryName.trim()}
title={t('budget.addCategory')}
style={{
appearance: 'none', border: 'none', cursor: newCategoryName.trim() ? 'pointer' : 'default', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
opacity: newCategoryName.trim() ? 1 : 0.4,
transition: 'opacity 0.15s ease',
}}>
<Plus size={14} strokeWidth={2.5} />
</button>
</div>
)}
<button onClick={handleExportCsv} title={t('budget.exportCsv')}
style={{
appearance: 'none', border: 'none', cursor: 'pointer', fontFamily: 'inherit',
display: 'inline-flex', alignItems: 'center', gap: 6,
padding: '9px 14px', borderRadius: 10, fontSize: 'calc(13px * var(--fs-scale-text, 1))', fontWeight: 500,
background: 'var(--accent)', color: 'var(--accent-text)', flexShrink: 0,
transition: 'opacity 0.15s ease',
}}
onMouseEnter={e => e.currentTarget.style.opacity = '0.88'}
onMouseLeave={e => e.currentTarget.style.opacity = '1'}
>
<Download size={14} strokeWidth={2.5} /> <span className="hidden sm:inline">CSV</span>
</button>
</div>
</div>
</div>
<div style={{ display: 'flex', gap: 20, padding: '24px 28px 40px', alignItems: 'flex-start', flexWrap: 'wrap' }} className="max-md:!px-4">
<div style={{ flex: 1, minWidth: 0 }}>
{categoryNames.map(cat => (
<BudgetCategoryTable key={cat} cat={cat} grouped={grouped} categoryColor={categoryColor}
canEdit={canEdit} editingCat={editingCat} setEditingCat={setEditingCat}
dragCat={dragCat} setDragCat={setDragCat} dragOverCat={dragOverCat} setDragOverCat={setDragOverCat}
dragItem={dragItem} setDragItem={setDragItem} dragOverItem={dragOverItem} setDragOverItem={setDragOverItem}
dragItemCat={dragItemCat} setDragItemCat={setDragItemCat}
categoryNames={categoryNames} reorderBudgetCategories={reorderBudgetCategories} reorderBudgetItems={reorderBudgetItems}
handleRenameCategory={handleRenameCategory} handleDeleteCategory={handleDeleteCategory} handleDeleteItem={handleDeleteItem}
handleUpdateField={handleUpdateField} handleAddItem={handleAddItem}
tripId={tripId} currency={currency} locale={locale} t={t} fmt={fmt}
hasMultipleMembers={hasMultipleMembers} tripMembers={tripMembers}
setBudgetItemMembers={setBudgetItemMembers} toggleBudgetMemberPaid={toggleBudgetMemberPaid}
th={th} td={td} />
))}
</div>
<BudgetSummary theme={theme} currency={currency} locale={locale} grandTotal={grandTotal}
hasMultipleMembers={hasMultipleMembers} budgetItems={budgetItems} settlement={settlement}
settlementOpen={settlementOpen} setSettlementOpen={setSettlementOpen} pieSegments={pieSegments}
isDark={isDark} tripId={tripId} t={t} fmt={fmt} />
</div>
</div>
)
}