Wygląd
This commit is contained in:
2026-07-11 00:51:33 +02:00
parent af44af5027
commit f0813894a8
5 changed files with 48 additions and 17 deletions

View File

@@ -15,6 +15,12 @@ server {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
} }
# Always revalidate the HTML shell so it never points at a stale, no-longer-existing
# hashed JS/CSS bundle after a new deploy.
location = /index.html {
add_header Cache-Control "no-cache";
}
location ~* \.(?:js|css|png|jpg|jpeg|svg|woff2?)$ { location ~* \.(?:js|css|png|jpg|jpeg|svg|woff2?)$ {
expires 7d; expires 7d;
add_header Cache-Control "public"; add_header Cache-Control "public";

View File

@@ -1,4 +1,5 @@
import { useBalance, useSummary, useSettleUp } from '../api/queries.js'; import { useNavigate } from 'react-router-dom';
import { useBalance, useSummary } from '../api/queries.js';
import { useHouseholdContext } from '../household/HouseholdContext.jsx'; import { useHouseholdContext } from '../household/HouseholdContext.jsx';
import CategoryPieChart from '../components/CategoryPieChart.jsx'; import CategoryPieChart from '../components/CategoryPieChart.jsx';
import Icon from '../components/Icon.jsx'; import Icon from '../components/Icon.jsx';
@@ -8,10 +9,10 @@ function memberName(members, id) {
} }
export default function Dashboard() { export default function Dashboard() {
const navigate = useNavigate();
const { activeHousehold: household } = useHouseholdContext(); const { activeHousehold: household } = useHouseholdContext();
const { data: balance } = useBalance(); const { data: balance } = useBalance();
const { data: summary } = useSummary(); const { data: summary } = useSummary();
const settleUp = useSettleUp();
const members = household?.members || []; const members = household?.members || [];
const currency = household?.currency || 'PLN'; const currency = household?.currency || 'PLN';
@@ -40,12 +41,8 @@ export default function Dashboard() {
</p> </p>
))} ))}
</div> </div>
<button <button className="btn-primary btn-center" onClick={() => navigate('/settlements')}>
className="btn-primary btn-center" Rozlicz się
disabled={settleUp.isPending}
onClick={() => settleUp.mutate()}
>
{settleUp.isPending ? 'Rozliczanie…' : 'Rozlicz się'}
</button> </button>
</> </>
)} )}

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { useHouseholdContext } from '../household/HouseholdContext.jsx'; import { useHouseholdContext } from '../household/HouseholdContext.jsx';
import { useBalance, useSettleUp, useSettlements, useCreateManualSettlement } from '../api/queries.js'; import { useBalance, useSettleUp, useSettlements, useCreateManualSettlement } from '../api/queries.js';
import PayerToggle from '../components/PayerToggle.jsx'; import PayerToggle from '../components/PayerToggle.jsx';
@@ -26,12 +26,23 @@ export default function Settlements() {
const [error, setError] = useState(''); const [error, setError] = useState('');
const [success, setSuccess] = useState(false); const [success, setSuccess] = useState(false);
const [editingSettlement, setEditingSettlement] = useState(null); const [editingSettlement, setEditingSettlement] = useState(null);
const initializedRef = useRef(false);
// Prefill the form from the first suggested transfer as soon as the balance is
// known, so the user starts from "who actually owes whom" instead of a blank guess.
useEffect(() => { useEffect(() => {
if (!fromUserId && members[0]) setFromUserId(members[0].id); if (initializedRef.current || !balance) return;
if (!toUserId && members[1]) setToUserId(members[1].id); initializedRef.current = true;
// eslint-disable-next-line react-hooks/exhaustive-deps const suggestion = balance.transactions?.[0];
}, [members.length]); if (suggestion) {
setFromUserId(suggestion.from);
setToUserId(suggestion.to);
setAmount(String(suggestion.amount));
} else if (members[0] && members[1]) {
setFromUserId(members[0].id);
setToUserId(members[1].id);
}
}, [balance, members]);
const toOptions = members.filter((m) => m.id !== fromUserId); const toOptions = members.filter((m) => m.id !== fromUserId);
@@ -43,6 +54,12 @@ export default function Settlements() {
} }
} }
function applySuggestion(tx) {
setFromUserId(tx.from);
setToUserId(tx.to);
setAmount(String(tx.amount));
}
async function handleSubmit(e) { async function handleSubmit(e) {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
@@ -77,12 +94,12 @@ export default function Settlements() {
</> </>
) : ( ) : (
<> <>
<p>Sugerowane rozliczenie</p> <p>Sugerowane rozliczenie (dotknij, aby wypełnić formularz poniżej)</p>
<div className="settlement-transactions"> <div className="settlement-transactions">
{balance.transactions.map((tx, i) => ( {balance.transactions.map((tx, i) => (
<p key={i} className="amount owed"> <button key={i} type="button" className="amount owed suggestion-btn" onClick={() => applySuggestion(tx)}>
{memberName(members, tx.from)} {memberName(members, tx.to)}: {tx.amount.toFixed(2)} {currency} {memberName(members, tx.from)} {memberName(members, tx.to)}: {tx.amount.toFixed(2)} {currency}
</p> </button>
))} ))}
</div> </div>
<button <button

View File

@@ -232,7 +232,7 @@ input, select, button, textarea { font-family: inherit; font-size: 1rem; color:
} }
.btn-full { width: 100%; display: block; } .btn-full { width: 100%; display: block; }
.btn-center { width: fit-content; margin: 0 auto; } .btn-center { width: fit-content; margin: 16px auto 0; }
.theme-toggle { display: flex; gap: 8px; } .theme-toggle { display: flex; gap: 8px; }
.theme-toggle .tab { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; } .theme-toggle .tab { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 4px; }
@@ -264,6 +264,15 @@ input, select, button, textarea { font-family: inherit; font-size: 1rem; color:
.settlement-tile .amount.owed { color: var(--danger); } .settlement-tile .amount.owed { color: var(--danger); }
.settlement-transactions { display: flex; flex-direction: column; gap: 4px; } .settlement-transactions { display: flex; flex-direction: column; gap: 4px; }
.settlement-transactions .amount { font-size: 1.3rem; margin: 0; } .settlement-transactions .amount { font-size: 1.3rem; margin: 0; }
.suggestion-btn {
background: none;
border: none;
font-family: inherit;
cursor: pointer;
border-radius: 8px;
padding: 4px 8px;
}
.suggestion-btn:hover, .suggestion-btn:focus-visible { background: var(--accent-bg); }
.summary-grid { .summary-grid {
display: grid; display: grid;

View File

@@ -24,6 +24,8 @@ export default defineConfig({
}, },
workbox: { workbox: {
navigateFallbackDenylist: [/^\/api\//], navigateFallbackDenylist: [/^\/api\//],
clientsClaim: true,
skipWaiting: true,
runtimeCaching: [ runtimeCaching: [
{ {
urlPattern: ({ url }) => url.pathname.startsWith('/api/') && url.pathname !== '/api/expenses', urlPattern: ({ url }) => url.pathname.startsWith('/api/') && url.pathname !== '/api/expenses',