feat: add month switcher to Finance page - fix transactions not showing
All checks were successful
CI / ci (push) Successful in 40s

This commit is contained in:
Cosmo
2026-03-01 05:02:23 +00:00
parent c898c0063c
commit 72915aa6c4
6 changed files with 76 additions and 28 deletions

13
Dockerfile.dev Normal file
View File

@@ -0,0 +1,13 @@
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV VITE_API_URL=http://192.168.31.60:8081
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -5,7 +5,9 @@ networks:
services:
web-dev:
build: .
build:
context: .
dockerfile: Dockerfile.dev
container_name: pulse-web-dev
restart: always
ports:

View File

@@ -18,16 +18,16 @@ const MONTH_NAMES = [
"Июл", "Авг", "Сен", "Окт", "Ноя", "Дек",
]
export default function FinanceAnalytics() {
export default function FinanceAnalytics({ month, year }) {
const [analytics, setAnalytics] = useState(null)
const [summary, setSummary] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const now = new Date()
setLoading(true)
Promise.all([
financeApi.getAnalytics({ months: 6 }),
financeApi.getSummary({ month: now.getMonth() + 1, year: now.getFullYear() }),
financeApi.getSummary({ month, year }),
])
.then(([a, s]) => {
setAnalytics(a)
@@ -35,7 +35,7 @@ export default function FinanceAnalytics() {
})
.catch(console.error)
.finally(() => setLoading(false))
}, [])
}, [month, year])
if (loading) {
return (
@@ -72,7 +72,6 @@ export default function FinanceAnalytics() {
return (
<div className="space-y-6">
{/* Summary cards */}
<div className="grid grid-cols-2 gap-3">
<div className="card p-4">
<p className="text-xs text-gray-500 dark:text-gray-400">
@@ -97,7 +96,6 @@ export default function FinanceAnalytics() {
</div>
</div>
{/* Bar chart */}
{barData.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">
@@ -131,7 +129,6 @@ export default function FinanceAnalytics() {
</div>
)}
{/* Donut chart */}
{pieData.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">
@@ -179,7 +176,6 @@ export default function FinanceAnalytics() {
</div>
)}
{/* Monthly trend */}
{monthlyData.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">

View File

@@ -12,18 +12,18 @@ const COLORS = [
const fmt = (n) => Number(n).toLocaleString("ru-RU") + " ₽"
export default function FinanceDashboard() {
export default function FinanceDashboard({ month, year }) {
const [summary, setSummary] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const now = new Date()
setLoading(true)
financeApi
.getSummary({ month: now.getMonth() + 1, year: now.getFullYear() })
.getSummary({ month, year })
.then(setSummary)
.catch(console.error)
.finally(() => setLoading(false))
}, [])
}, [month, year])
if (loading) {
return (
@@ -63,7 +63,6 @@ export default function FinanceDashboard() {
return (
<div className="space-y-6">
{/* Balance Card */}
<div className="card p-6 bg-gradient-to-br from-primary-950 to-primary-800 text-white">
<p className="text-sm opacity-70">Баланс за месяц</p>
<p className="text-3xl font-bold mt-1">{fmt(summary.balance)}</p>
@@ -83,7 +82,6 @@ export default function FinanceDashboard() {
</div>
</div>
{/* Top Categories */}
{expenseCategories.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">
@@ -120,7 +118,6 @@ export default function FinanceDashboard() {
</div>
)}
{/* Donut Chart */}
{pieData.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">
@@ -168,7 +165,6 @@ export default function FinanceDashboard() {
</div>
)}
{/* Daily Line Chart */}
{dailyData.length > 0 && (
<div className="card p-5">
<h3 className="font-display font-bold text-gray-900 dark:text-white mb-4">

View File

@@ -8,7 +8,7 @@ const formatDate = (d) => {
return dt.toLocaleDateString("ru-RU", { day: "numeric", month: "long" })
}
export default function TransactionList({ onAdd }) {
export default function TransactionList({ onAdd, month, year }) {
const [transactions, setTransactions] = useState([])
const [categories, setCategories] = useState([])
const [loading, setLoading] = useState(true)
@@ -17,11 +17,12 @@ export default function TransactionList({ onAdd }) {
const [search, setSearch] = useState("")
useEffect(() => {
setLoading(true)
Promise.all([
financeApi.listCategories(),
financeApi.listTransactions({
month: new Date().getMonth() + 1,
year: new Date().getFullYear(),
month,
year,
limit: 100,
}),
])
@@ -31,7 +32,7 @@ export default function TransactionList({ onAdd }) {
})
.catch(console.error)
.finally(() => setLoading(false))
}, [])
}, [month, year])
const filtered = transactions.filter((t) => {
if (filter !== "all" && t.type !== filter) return false
@@ -67,7 +68,6 @@ export default function TransactionList({ onAdd }) {
return (
<div className="space-y-4">
{/* Search */}
<input
className="w-full px-4 py-2.5 rounded-xl bg-gray-100 dark:bg-gray-800 text-sm text-gray-900 dark:text-white placeholder-gray-400 outline-none"
placeholder="Поиск по описанию..."
@@ -75,7 +75,6 @@ export default function TransactionList({ onAdd }) {
onChange={(e) => setSearch(e.target.value)}
/>
{/* Type filter */}
<div className="flex gap-2">
{[
["all", "Все"],
@@ -96,7 +95,6 @@ export default function TransactionList({ onAdd }) {
))}
</div>
{/* Category filter */}
<div className="flex gap-2 overflow-x-auto pb-1">
<button
onClick={() => setCatFilter(null)}
@@ -123,7 +121,6 @@ export default function TransactionList({ onAdd }) {
))}
</div>
{/* Transaction groups */}
{Object.keys(grouped).length === 0 ? (
<div className="card p-12 text-center">
<span className="text-4xl block mb-3">🔍</span>

View File

@@ -13,13 +13,32 @@ const tabs = [
{ key: "categories", label: "Категории", icon: "🏷️" },
]
const MONTH_NAMES = [
"Январь", "Февраль", "Март", "Апрель", "Май", "Июнь",
"Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь",
]
export default function Finance() {
const now = new Date()
const [activeTab, setActiveTab] = useState("dashboard")
const [showAdd, setShowAdd] = useState(false)
const [refreshKey, setRefreshKey] = useState(0)
const [month, setMonth] = useState(now.getMonth() + 1)
const [year, setYear] = useState(now.getFullYear())
const refresh = () => setRefreshKey((k) => k + 1)
const prevMonth = () => {
if (month === 1) { setMonth(12); setYear(y => y - 1) }
else setMonth(m => m - 1)
}
const nextMonth = () => {
if (month === 12) { setMonth(1); setYear(y => y + 1) }
else setMonth(m => m + 1)
}
const isCurrentMonth = month === now.getMonth() + 1 && year === now.getFullYear()
return (
<div className="min-h-screen bg-surface-50 dark:bg-gray-950 gradient-mesh pb-24">
<header className="bg-white/70 dark:bg-gray-900/70 backdrop-blur-xl border-b border-gray-100/50 dark:border-gray-800 sticky top-0 z-10">
@@ -36,6 +55,31 @@ export default function Finance() {
+
</button>
</div>
{/* Month Switcher */}
<div className="max-w-lg mx-auto px-4 pb-3">
<div className="flex items-center justify-center gap-4">
<button
onClick={prevMonth}
className="w-8 h-8 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition text-sm font-bold"
>
</button>
<button
onClick={() => { setMonth(now.getMonth() + 1); setYear(now.getFullYear()) }}
className={"text-sm font-semibold min-w-[140px] text-center " + (isCurrentMonth ? "text-gray-900 dark:text-white" : "text-primary-600 dark:text-primary-400")}
>
{MONTH_NAMES[month - 1]} {year}
</button>
<button
onClick={nextMonth}
className="w-8 h-8 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 flex items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700 transition text-sm font-bold"
>
</button>
</div>
</div>
<div className="max-w-lg mx-auto px-4 pb-3 flex gap-1.5 overflow-x-auto scrollbar-hide">
{tabs.map((t) => (
<button
@@ -54,11 +98,11 @@ export default function Finance() {
</header>
<div className="max-w-lg mx-auto px-4 py-6">
{activeTab === "dashboard" && <FinanceDashboard key={refreshKey} />}
{activeTab === "dashboard" && <FinanceDashboard key={refreshKey + "-" + month + "-" + year} month={month} year={year} />}
{activeTab === "transactions" && (
<TransactionList key={refreshKey} onAdd={() => setShowAdd(true)} />
<TransactionList key={refreshKey + "-" + month + "-" + year} month={month} year={year} onAdd={() => setShowAdd(true)} />
)}
{activeTab === "analytics" && <FinanceAnalytics key={refreshKey} />}
{activeTab === "analytics" && <FinanceAnalytics key={refreshKey + "-" + month + "-" + year} month={month} year={year} />}
{activeTab === "categories" && <CategoriesManager refreshKey={refreshKey} />}
</div>