This commit is contained in:
arrelin
2026-03-10 12:16:00 +03:00
parent 0f72d62d3e
commit 6f679a5066
6 changed files with 92 additions and 50 deletions

View File

@@ -1,7 +1,6 @@
use family_budget::*; use family_budget::*;
use sea_orm::DbErr; use sea_orm::DbErr;
use sea_orm_migration::prelude::*; use sea_orm_migration::prelude::*;
//TODO: НЕУДОБНОЕ РАСПОЛОЖЕНИЕ ДОБАВИТЬ РАСХОД + ИСТОРИЯ, ВОЗВРАЩАЕТ В НАЧАЛО ПОСЛЕ ДОБАВЛЕНИЯ РАСХОДА + ЗАКРЫВАЕТ ДОБАВИТЬ РАСХОД, ИСТОРИЯ НЕ ОБНОВЛЯЕТСЯ
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), DbErr> { async fn main() -> Result<(), DbErr> {
let db = establish_connection().await?; let db = establish_connection().await?;

View File

@@ -4,6 +4,9 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#667eea" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<title>Family budget</title> <title>Family budget</title>
</head> </head>
<body> <body>

View File

@@ -16,9 +16,20 @@ function AppContent() {
const { user, isAuthenticated, isLoading, setUser, setIsLoading } = useStore(); const { user, isAuthenticated, isLoading, setUser, setIsLoading } = useStore();
const location = useLocation(); const location = useLocation();
const themeColors: Record<string, string> = {
light: '#667eea',
dark: '#000000',
sunset: '#f97316',
ocean: '#3b82f6',
forest: '#22c55e',
purple: '#8b5cf6',
};
useEffect(() => { useEffect(() => {
const storedTheme = localStorage.getItem('theme') || 'light'; const storedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', storedTheme); document.documentElement.setAttribute('data-theme', storedTheme);
const metaTheme = document.querySelector('meta[name="theme-color"]');
if (metaTheme) metaTheme.setAttribute('content', themeColors[storedTheme] ?? '#667eea');
const storedLocale = localStorage.getItem('locale'); const storedLocale = localStorage.getItem('locale');
if (storedLocale && storedLocale !== i18n.language) { if (storedLocale && storedLocale !== i18n.language) {

View File

@@ -1,5 +1,12 @@
@import "tailwindcss"; @import "tailwindcss";
html,
body {
background: linear-gradient(135deg, var(--gradient-start) 0%, var(--gradient-end) 100%);
background-attachment: fixed;
overscroll-behavior-y: none;
}
:root, :root,
[data-theme="light"] { [data-theme="light"] {
--gradient-start: #667eea; --gradient-start: #667eea;

View File

@@ -164,7 +164,17 @@ export default function FamilyView() {
setExpenseAmount(''); setExpenseAmount('');
setExpenseDescription(''); setExpenseDescription('');
setShowAddExpense(null); setShowAddExpense(null);
loadCategories();
const limitResponse = await expenseApi.getRemainingLimit(parseInt(familyId), categoryId);
const limitValue = typeof limitResponse.data.remaining_limit === 'string'
? parseFloat(limitResponse.data.remaining_limit)
: limitResponse.data.remaining_limit;
setRemainingLimits(prev => new Map(prev).set(categoryId, limitValue));
if (showHistory === categoryId) {
const historyResponse = await expenseApi.getHistory(parseInt(familyId), categoryId, false);
setHistoryData(historyResponse.data);
}
} catch (err) { } catch (err) {
alert(t('expense.addError')); alert(t('expense.addError'));
console.error(err); console.error(err);
@@ -452,6 +462,55 @@ export default function FamilyView() {
</button> </button>
</div> </div>
{showAddExpense === category.id && (
<div className="glass-effect p-6 rounded-2xl border-2 border-gray-200 mt-4">
<h3 className="font-semibold text-gray-800 mb-4 text-center">
{t('expense.addTitle')}
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('expense.amount')}
</label>
<input
type="number"
placeholder="0.00"
value={expenseAmount}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('expense.description')}
</label>
<input
type="text"
placeholder={t('expense.descriptionPlaceholder')}
value={expenseDescription}
onChange={(e) => 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"
/>
</div>
<div className="flex gap-3">
<button
onClick={() => handleAddExpense(category.id)}
className="flex-1 flex items-center justify-center gap-2 px-5 py-3 btn-success text-white rounded-2xl hover:shadow-xl transition-all font-semibold"
>
<Plus className="w-5 h-5" />
{t('common.add')}
</button>
<button
onClick={() => setShowAddExpense(null)}
className="px-5 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-2xl transition-all font-medium"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
</div>
)}
{showHistory === category.id && historyData && ( {showHistory === category.id && historyData && (
<div className="mt-4 glass-effect p-4 rounded-2xl border-2 border-blue-200"> <div className="mt-4 glass-effect p-4 rounded-2xl border-2 border-blue-200">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
@@ -585,54 +644,6 @@ export default function FamilyView() {
</div> </div>
)} )}
{showAddExpense === category.id && (
<div className="glass-effect p-6 rounded-2xl border-2 border-gray-200 mt-4">
<h3 className="font-semibold text-gray-800 mb-4 text-center">
{t('expense.addTitle')}
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('expense.amount')}
</label>
<input
type="number"
placeholder="0.00"
value={expenseAmount}
onChange={(e) => 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"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
{t('expense.description')}
</label>
<input
type="text"
placeholder={t('expense.descriptionPlaceholder')}
value={expenseDescription}
onChange={(e) => 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"
/>
</div>
<div className="flex gap-3">
<button
onClick={() => handleAddExpense(category.id)}
className="flex-1 flex items-center justify-center gap-2 px-5 py-3 btn-success text-white rounded-2xl hover:shadow-xl transition-all font-semibold"
>
<Plus className="w-5 h-5" />
{t('common.add')}
</button>
<button
onClick={() => setShowAddExpense(null)}
className="px-5 py-3 bg-gray-200 hover:bg-gray-300 text-gray-700 rounded-2xl transition-all font-medium"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
</div>
)}
</div> </div>
); );
})} })}

View File

@@ -97,9 +97,20 @@ export default function Profile() {
} }
}; };
const themeColors: Record<string, string> = {
light: '#667eea',
dark: '#000000',
sunset: '#f97316',
ocean: '#3b82f6',
forest: '#22c55e',
purple: '#8b5cf6',
};
const handleThemeChange = (theme: Theme) => { const handleThemeChange = (theme: Theme) => {
setPreferences({ theme }); setPreferences({ theme });
document.documentElement.setAttribute('data-theme', theme); document.documentElement.setAttribute('data-theme', theme);
const metaTheme = document.querySelector('meta[name="theme-color"]');
if (metaTheme) metaTheme.setAttribute('content', themeColors[theme] ?? '#667eea');
}; };
const handleLocaleChange = (locale: 'ru' | 'en') => { const handleLocaleChange = (locale: 'ru' | 'en') => {