diff --git a/frontend/.env b/frontend/.env
index a8cf54a..b8ca0df 100644
--- a/frontend/.env
+++ b/frontend/.env
@@ -1 +1 @@
-VITE_API_BASE_URL=http://localhost:8080
+VITE_API_BASE_URL=
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index cff8d8e..94e1ce8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@tailwindcss/postcss": "^4.1.18",
"axios": "^1.13.2",
+ "lucide-react": "^0.561.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1",
@@ -3436,6 +3437,15 @@
"yallist": "^3.0.2"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.561.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
+ "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 8bc7062..80ee3b6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"@tailwindcss/postcss": "^4.1.18",
"axios": "^1.13.2",
+ "lucide-react": "^0.561.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1",
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts
index 31f1ae2..8a60fb5 100644
--- a/frontend/src/api/client.ts
+++ b/frontend/src/api/client.ts
@@ -13,7 +13,7 @@ import type {
VerifyFamilyPasswordResponse,
} from '../types';
-const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000';
+const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '';
const apiClient = axios.create({
baseURL: API_BASE_URL,
diff --git a/frontend/src/index.css b/frontend/src/index.css
index b5c61c9..56ffd94 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,3 +1,51 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@import "tailwindcss";
+
+.gradient-bg {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+}
+
+.gradient-bg-light {
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+}
+
+.card-hover {
+ transition: all 0.3s;
+}
+
+.card-hover:hover {
+ box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+ transform: translateY(-0.25rem);
+}
+
+.glass-effect {
+ background: rgb(255 255 255 / 0.8);
+ backdrop-filter: blur(12px);
+}
+
+.animate-fadeIn {
+ animation: fadeIn 0.2s ease-in-out;
+}
+
+.animate-scaleIn {
+ animation: scaleIn 0.2s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
diff --git a/frontend/src/pages/AdminPanel.tsx b/frontend/src/pages/AdminPanel.tsx
index 324604c..b7832cc 100644
--- a/frontend/src/pages/AdminPanel.tsx
+++ b/frontend/src/pages/AdminPanel.tsx
@@ -2,6 +2,18 @@ import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { authApi, familyApi } from '../api/client';
import { useStore } from '../store/useStore';
+import {
+ Shield,
+ Home,
+ LogOut,
+ Users,
+ Plus,
+ Trash2,
+ Lock,
+ User,
+ ArrowLeft,
+ X,
+} from 'lucide-react';
export default function AdminPanel() {
const navigate = useNavigate();
@@ -95,131 +107,203 @@ export default function AdminPanel() {
if (!isAuthenticated) {
return (
-
-
-
- Вход в админ панель
-
-
- {loginError && (
-
- {loginError}
+
+
+
+
- )}
+
+ Админ панель
+
+
+ Войдите для управления системой
+
-
-
-
-
+
);
}
return (
-
+
-
-
+
+
+
+
+
Админ панель
-
-
-
-
-
- Создать новую семью
-
-
-
-
setNewFamilyName(e.target.value)}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- />
-
setNewFamilyPassword(e.target.value)}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- />
+
+ Управление семьями
+
+
+
-
-
- Список семей
-
+
+
+
+
+ Создать новую семью
+
+
+
+
+
+
+ setNewFamilyName(e.target.value)}
+ className="w-full px-5 py-4 border-2 border-gray-300 rounded-2xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all font-medium text-center"
+ />
+
+
+
+ setNewFamilyPassword(e.target.value)}
+ className="w-full px-5 py-4 border-2 border-gray-300 rounded-2xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all font-medium text-center"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Список семей
+
+
{families.length === 0 ? (
-
- Семьи не найдены
-
+
+
+
+
+
+ Семьи не найдены
+
+
+ Создайте первую семью
+
+
) : (
-
+
{families.map((family) => (
-
- {family.name}
-
+
+
+
+
+
+ {family.name}
+
+
@@ -227,13 +311,6 @@ export default function AdminPanel() {
)}
-
-
);
diff --git a/frontend/src/pages/FamilyView.tsx b/frontend/src/pages/FamilyView.tsx
index 3fe3913..c687f1d 100644
--- a/frontend/src/pages/FamilyView.tsx
+++ b/frontend/src/pages/FamilyView.tsx
@@ -3,6 +3,18 @@ import { useParams, useNavigate } from 'react-router-dom';
import { categoryApi, expenseApi } from '../api/client';
import { useStore } from '../store/useStore';
import type { Category } from '../types';
+import {
+ ArrowLeft,
+ Wallet,
+ TrendingDown,
+ Plus,
+ Trash2,
+ RotateCcw,
+ Loader2,
+ X,
+ DollarSign,
+ Tag,
+} from 'lucide-react';
export default function FamilyView() {
const { familyId } = useParams<{ familyId: string }>();
@@ -140,160 +152,255 @@ export default function FamilyView() {
if (loading) {
return (
-
-
Загрузка...
+
);
}
+ const getProgressColor = (remaining: number, limit: number) => {
+ const percentage = (remaining / limit) * 100;
+ if (percentage > 50) return 'bg-green-500';
+ if (percentage > 25) return 'bg-yellow-500';
+ return 'bg-red-500';
+ };
+
+ const getProgressPercentage = (remaining: number, limit: number) => {
+ return Math.max(0, Math.min(100, (remaining / limit) * 100));
+ };
+
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
{selectedFamily?.name || 'Семья'}
+
+ Управление категориями и расходами
+
{error && (
-
- {error}
+
)}
-
- {categories.map((category) => (
-
-
-
- {category.name}
-
-
- Остаток:
- {remainingLimits.get(category.id)?.toFixed(2) || '0.00'} ₽
-
- {' / '}
- {category.limit_amount.toString()} ₽
-
-
+
+ {categories.map((category) => {
+ const remaining = remainingLimits.get(category.id) || 0;
+ const limit = parseFloat(category.limit_amount.toString());
+ const percentage = getProgressPercentage(remaining, limit);
-
- {showAddExpense === category.id ? (
-
-
setExpenseAmount(e.target.value)}
- className="w-full mb-2 px-3 py-2 border border-gray-300 rounded"
+ return (
+
+
+
+
+
+
+
+ {category.name}
+
+
+
+ {showAddExpense !== category.id && (
+
+ )}
+
+
+
+
+ Остаток:
+
+ {remaining.toFixed(2)} ₽
+
+
+
+ Лимит:
+ {limit.toFixed(2)} ₽
+
+
+
+
-
setExpenseDescription(e.target.value)}
- className="w-full mb-2 px-3 py-2 border border-gray-300 rounded"
- />
-
-
-
+
+
+ {percentage.toFixed(0)}% осталось
+
+
+
+ {showAddExpense === category.id && (
+
+
+ Добавить расход
+
+
+
+
+ setExpenseAmount(e.target.value)}
+ className="w-full px-4 py-3 border-2 border-gray-300 rounded-2xl focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all text-center font-semibold text-lg"
+ />
+
+
+
+ setExpenseDescription(e.target.value)}
+ className="w-full px-4 py-3 border-2 border-gray-300 rounded-2xl focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all"
+ />
+
+
+
+
+
- ) : (
-
)}
-
- ))}
+ );
+ })}
-
-
- Управление категориями
-
+
+
+
+
+
+
+ Управление категориями
+
+
{showAddCategory ? (
-
-
setNewCategoryName(e.target.value)}
- className="w-full mb-2 px-4 py-2 border border-gray-300 rounded-lg"
- />
-
setNewCategoryLimit(e.target.value)}
- className="w-full mb-2 px-4 py-2 border border-gray-300 rounded-lg"
- />
-
-
-
+
+
+ Новая категория
+
+
+
setNewCategoryName(e.target.value)}
+ className="w-full px-5 py-4 border-2 border-gray-300 rounded-2xl focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all font-medium"
+ />
+
setNewCategoryLimit(e.target.value)}
+ className="w-full px-5 py-4 border-2 border-gray-300 rounded-2xl focus:border-purple-500 focus:ring-2 focus:ring-purple-200 transition-all font-medium text-center"
+ />
+
+
+
+
) : (
)}
-
+
{categories.map((category) => (
-
-
{category.name}
+
+
+
+
+
+
+ {category.name}
+
+
diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx
index c3a83bb..1292a6f 100644
--- a/frontend/src/pages/Home.tsx
+++ b/frontend/src/pages/Home.tsx
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { familyApi, categoryApi } from '../api/client';
import { useStore } from '../store/useStore';
import type { Family } from '../types';
+import { Users, Settings, Lock, Loader2, X } from 'lucide-react';
export default function Home() {
const navigate = useNavigate();
@@ -88,95 +89,134 @@ export default function Home() {
if (loading) {
return (
-
-
Загрузка...
+
);
}
return (
-
-
-
-
+
+
+
+
Семейный бюджет
{error && (
-
- {error}
+
)}
-
-
- Выберите семью
-
-
- {families.length === 0 ? (
-
- Семьи не найдены. Создайте семью в админ панели.
-
- ) : (
-
- {families.map((family) => (
-
- ))}
+
+
+
+
- )}
+
+ Выберите семью
+
+
+ {families.length === 0 ? (
+
+
+
+
+
+ Семьи не найдены
+
+
+ Создайте семью в админ панели
+
+
+ ) : (
+
+ {families.map((family) => (
+
+ ))}
+
+ )}
+
{showPasswordDialog && selectedFamilyForAuth && (
-
-
-
- Введите пароль для семьи
-
-
- {selectedFamilyForAuth.name}
-
+
+
+
+
+
+
+
+ Защищённая семья
+
+
+ {selectedFamilyForAuth.name}
+
+
{passwordError && (
-
- {passwordError}
+
)}
setFamilyPassword(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleVerifyPassword()}
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent mb-4"
+ className="w-full px-5 py-4 border-2 border-gray-300 rounded-2xl focus:ring-2 focus:ring-purple-500 focus:border-purple-500 transition-all mb-6 text-base text-center font-medium"
autoFocus
/>
-
+
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index 37ae4d9..2869179 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -6,8 +6,16 @@ export default defineConfig({
server: {
port: 5173,
proxy: {
- '/api': {
- target: 'http://localhost:3000',
+ '/families': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ },
+ '/login': {
+ target: 'http://localhost:8080',
+ changeOrigin: true,
+ },
+ '/logout': {
+ target: 'http://localhost:8080',
changeOrigin: true,
}
}