diff --git a/install.sh b/install.sh old mode 100644 new mode 100755 diff --git a/public/sw.js b/public/sw.js index 0ffd9ab..6360f83 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const SHELL_CACHE='fixfin-shell-v3.19'; +const SHELL_CACHE='fixfin-shell-v3.20'; const DATA_CACHE='fixfin-data-v1'; const APP_SHELL=['/','/manifest.webmanifest','/icons/icon-192.png','/icons/icon-512.png','/icons/apple-touch-icon.png']; diff --git a/src/main.jsx b/src/main.jsx index f43aa78..2bdbd8b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -21,6 +21,22 @@ function parseAmount(value) { const number = Number(normalized); return Number.isFinite(number) ? number : Number.NaN; } +let modalLockDepth = 0; +function useModalScrollLock() { + useEffect(() => { + modalLockDepth += 1; + document.documentElement.classList.add('modal-open'); + document.body.classList.add('modal-open'); + return () => { + modalLockDepth = Math.max(0, modalLockDepth - 1); + if (modalLockDepth === 0) { + document.documentElement.classList.remove('modal-open'); + document.body.classList.remove('modal-open'); + } + }; + }, []); +} + function uid() { if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID(); if (globalThis.crypto?.getRandomValues) { @@ -578,6 +594,7 @@ function ScenariosPage({data,persist}){ } function ScenarioAdjustmentDialog({data,item,onClose,onSave}){ + useModalScrollLock(); const [name,setName]=useState(item?.name||''); const [type,setType]=useState(item?.type||'expense'); const [amount,setAmount]=useState(item?.amount??''); const [accountId,setAccountId]=useState(item?.accountId||data.accounts[0]?.id||''); const [fromAccountId,setFromAccountId]=useState(item?.fromAccountId||data.accounts[0]?.id||''); const [toAccountId,setToAccountId]=useState(item?.toAccountId||data.accounts[1]?.id||data.accounts[0]?.id||''); const [personId,setPersonId]=useState(item?.personId||''); const [error,setError]=useState(''); function submit(e){e.preventDefault();const numeric=parseAmount(amount);if(!name.trim())return setError('Bitte eine Bezeichnung eingeben.');if(!Number.isFinite(numeric)||numeric<0)return setError('Bitte einen gültigen Betrag eingeben.');if(!personId)return setError('Bitte eine Person auswählen.');if(type==='transfer'&&fromAccountId===toAccountId)return setError('Quell- und Zielkonto müssen verschieden sein.');const value={id:item?.id||uid(),name:name.trim(),type,amount:numeric,personId:personId||null};if(type==='transfer'){value.fromAccountId=fromAccountId;value.toAccountId=toAccountId;}else value.accountId=accountId;onSave(value);} @@ -635,6 +652,7 @@ function DataPage({data,importFile}){ function Empty({text}){return
{text}
} function EditorDialog({dialog,data,onClose,onSave}){ + useModalScrollLock(); const type=dialog.type,item=dialog.item; const editing=Boolean(item?.id); const [name,setName]=useState(item?.name||''),[amount,setAmount]=useState(item?.amount??''),[accountId,setAccountId]=useState(item?.accountId||data.accounts[0]?.id||''),[fromAccountId,setFromAccountId]=useState(item?.fromAccountId||data.accounts[0]?.id||''),[toAccountId,setToAccountId]=useState(item?.toAccountId||data.accounts[1]?.id||data.accounts[0]?.id||''),[interval,setInterval]=useState(item?.interval||'monthly'),[dueMonth,setDueMonth]=useState(item?.dueMonth?String(item.dueMonth):''),[categoryId,setCategoryId]=useState(item?.categoryId||''),[newCategoryName,setNewCategoryName]=useState(''),[personId,setPersonId]=useState(item?.personId||''),[newPersonName,setNewPersonName]=useState(''),[pushoverReminder,setPushoverReminder]=useState(item?.pushoverReminder===true),[endDate,setEndDate]=useState(item?.endDate||''),[amountChanges,setAmountChanges]=useState(Array.isArray(item?.amountChanges)?item.amountChanges:[]),[changeDate,setChangeDate]=useState(''),[changeAmount,setChangeAmount]=useState(''),[localError,setLocalError]=useState(''); const labels={accounts:'Konto',categories:'Kategorie',persons:'Person',incomes:'Eingang',expenses:'Ausgang',transfers:'Transfer'}; diff --git a/src/styles.css b/src/styles.css index bd0001a..488a8e7 100644 --- a/src/styles.css +++ b/src/styles.css @@ -80,8 +80,8 @@ tbody tr:last-child td { border-bottom:0; } .button-row { display:flex;gap:10px;flex-wrap:wrap; } .button-link { text-decoration:none;display:inline-flex;align-items:center;cursor:pointer; } .json-preview { margin:0;max-height:420px;overflow:auto;background:#0a0f17;border:1px solid #202a39;border-radius:12px;padding:14px;font-size:11px;color:#abb8c8; } -.modal-backdrop { position:fixed;inset:0;background:#02050abf;display:grid;place-items:center;padding:20px;z-index:50;backdrop-filter:blur(5px); } -.modal { width:min(470px,100%);background:#111722;border:1px solid #334158;border-radius:18px;padding:20px;box-shadow:0 30px 100px #0009; } +.modal-backdrop { position:fixed;inset:0;background:#02050abf;display:grid;place-items:center;padding:20px;z-index:50;backdrop-filter:blur(5px);overflow:hidden;overscroll-behavior:contain; } +.modal { width:min(470px,100%);max-height:calc(100dvh - 40px);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;background:#111722;border:1px solid #334158;border-radius:18px;padding:20px;box-shadow:0 30px 100px #0009; } .modal-head { display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:18px; } .modal-head h2 { font-size:24px;margin-top:3px; } .close { background:transparent;color:#8b99ab;font-size:24px;padding:2px 8px;border:0; } @@ -97,6 +97,33 @@ tbody tr:last-child td { border-bottom:0; } .loading { min-height:100vh;display:grid;place-items:center;color:#94a3b8; } code { color:#b8c3ff; } +html.modal-open, body.modal-open { overflow:hidden !important; overscroll-behavior:none; } + +@media (max-width: 760px) { + .modal-backdrop { + display:flex; + align-items:center; + justify-content:center; + padding:max(10px, env(safe-area-inset-top)) 10px max(10px, env(safe-area-inset-bottom)); + } + .modal { + width:100%; + max-height:calc(100dvh - max(20px, env(safe-area-inset-top)) - max(20px, env(safe-area-inset-bottom))); + padding:18px; + border-radius:17px; + } + .modal-actions { + position:sticky; + bottom:-18px; + z-index:6; + margin:20px -18px -18px; + padding:12px 18px calc(12px + env(safe-area-inset-bottom)); + background:linear-gradient(180deg,#111722e8 0%,#111722 28%); + border-top:1px solid #263246; + } + .modal-actions button { min-height:44px; } +} + @media (max-width: 1050px) { .summary-grid { grid-template-columns:1fr 1fr; } .account-summary-grid { grid-template-columns:1fr 1fr; } diff --git a/start-local.sh b/start-local.sh old mode 100644 new mode 100755