v1.0.2
Wygląd
This commit is contained in:
@@ -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";
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
Reference in New Issue
Block a user