diff --git a/copy/src/App.css b/copy/src/App.css deleted file mode 100644 index 307ab4f..0000000 --- a/copy/src/App.css +++ /dev/null @@ -1,325 +0,0 @@ -/* Base styles - Premium Glassmorphism Theme */ -:root { - /* - * Color Palette - Vibrant & Premium - * Primary: Indigo/Violet Gradients - * Secondary: Cyan/Teal - * Accent: Coral/Orange - */ - - /* Light Theme Base */ - --color-bg: #f8fafc; - --color-bg-page: #ffffeb; - /* Very subtle warm tint */ - --color-text: #0f172a; - --color-text-secondary: #475569; - --color-text-muted: #94a3b8; - - /* Primary Colors - Enterprise Yellow/Amber */ - --color-primary: #d97706; - /* Amber 600 */ - --color-primary-dark: #b45309; - /* Amber 700 */ - --color-primary-light: #fbbf24; - /* Amber 400 */ - --color-primary-lighter: rgba(217, 119, 6, 0.1); - --gradient-primary: linear-gradient(135deg, #d97706 0%, #f59e0b 100%); - --gradient-primary-hover: linear-gradient(135deg, #b45309 0%, #d97706 100%); - - /* Accent Colors - Navy/Slate Blue */ - --color-accent: #1e293b; - /* Slate 800 */ - --color-accent-hover: #0f172a; - /* Slate 900 */ - --color-accent-light: rgba(30, 41, 59, 0.1); - --gradient-accent: linear-gradient(135deg, #1e293b 0%, #334155 100%); - - /* Status Colors */ - --color-success: #059669; - /* Emerald 600 */ - --color-success-light: rgba(5, 150, 105, 0.1); - --color-warning: #f59e0b; - /* Amber 500 - matches primary but kept for semantic */ - --color-warning-light: rgba(245, 158, 11, 0.1); - --color-error: #dc2626; - /* Red 600 */ - --color-error-light: rgba(220, 38, 38, 0.1); - - /* Glassmorphism Variables */ - --glass-bg: rgba(255, 255, 255, 0.7); - --glass-border: rgba(0, 0, 0, 0.08); - /* Changed from white to dark for visibility on light bg */ - --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); - --glass-blur: blur(6px); - /* Reduced from 8px for performance */ - --glass-panel-bg: rgba(255, 255, 255, 0.85); - - /* Background Colors */ - --color-bg-tertiary: #f1f5f9; - /* Slate 100 */ - - /* Shadows & Depth */ - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); - --shadow-glow-primary: 0 0 15px rgba(217, 119, 6, 0.3); - - /* Spacing */ - --spacing-xs: 0.25rem; - --spacing-sm: 0.5rem; - --spacing-md: 1rem; - --spacing-lg: 1.5rem; - --spacing-xl: 2rem; - --spacing-2xl: 3rem; - - /* Border Radius */ - --radius-sm: 0.25rem; - --radius-md: 0.375rem; - --radius-lg: 0.5rem; - --radius-xl: 0.75rem; - --radius-2xl: 1rem; - --radius-full: 9999px; - - /* Typography Scale - Refined for Enterprise */ - --font-xs: 0.75rem; - /* 12px */ - --font-sm: 0.875rem; - /* 14px */ - --font-base: 1rem; - /* 16px */ - --font-lg: 1.125rem; - /* 18px */ - --font-xl: 1.25rem; - /* 20px */ - --font-2xl: 1.5rem; - /* 24px */ - --font-3xl: 2rem; - /* 32px */ - --font-3xl: 2rem; - /* 32px */ - --font-4xl: 2.5rem; - /* 40px */ - - /* Animation Durations */ - --duration-fast: 200ms; - --duration-normal: 300ms; - --duration-slow: 500ms; - - /* Easing */ - --ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1); - --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); -} - -/* Dark Theme */ -.dark { - --color-bg: #0f172a; - --color-bg-page: #020617; - --color-text: #f8fafc; - --color-text-secondary: #94a3b8; - --color-text-muted: #64748b; - --color-bg-tertiary: #1e293b; - /* Slate 800 */ - - --color-primary: #fbbf24; - /* Amber 400 */ - --color-primary-dark: #d97706; - /* Amber 600 */ - --color-primary-light: rgba(251, 191, 36, 0.2); - --gradient-primary: linear-gradient(135deg, #fbbf24 0%, #d97706 100%); - - --glass-bg: rgba(15, 23, 42, 0.6); - --glass-border: rgba(255, 255, 255, 0.1); - --glass-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.3); - --glass-panel-bg: rgba(30, 41, 59, 0.7); - - --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4); -} - -/* App Container */ -.app { - min-height: 100vh; - background-color: var(--color-bg-page); - color: var(--color-text); - color: var(--color-text); - font-family: 'Inter', sans-serif; - line-height: 1.6; - /* Improved readability */ - letter-spacing: -0.01em; - /* Tighter, more modern */ - /* Add subtle mesh gradient background */ - background-image: - radial-gradient(at 0% 0%, rgba(217, 119, 6, 0.05) 0px, transparent 50%), - radial-gradient(at 100% 0%, rgba(30, 41, 59, 0.05) 0px, transparent 50%), - radial-gradient(at 100% 100%, rgba(5, 150, 105, 0.05) 0px, transparent 50%), - radial-gradient(at 0% 100%, rgba(251, 191, 36, 0.05) 0px, transparent 50%); - background-attachment: fixed; - transition: background-color 0.3s ease; -} - -/* Glassmorphism Utilities */ -.glass-panel { - background: var(--glass-panel-bg); - -webkit-backdrop-filter: var(--glass-blur); - backdrop-filter: var(--glass-blur); - border: 1px solid var(--glass-border); - box-shadow: var(--glass-shadow); - border-radius: var(--radius-xl); -} - -.glass-card { - background: var(--glass-bg); - -webkit-backdrop-filter: blur(8px); - backdrop-filter: blur(8px); - border: 1px solid var(--glass-border); - box-shadow: var(--shadow-sm); - border-radius: var(--radius-lg); - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; -} - -.glass-card:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-lg), var(--shadow-glow-primary); - border-color: rgba(217, 119, 6, 0.4); -} - -/* Buttons */ -.btn-primary { - background: var(--gradient-primary); - color: white; - border: none; - padding: var(--spacing-sm) var(--spacing-xl); - border-radius: var(--radius-full); - font-weight: 600; - font-size: var(--font-sm); - cursor: pointer; - box-shadow: 0 4px 6px rgba(217, 119, 6, 0.25); - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.btn-primary:hover { - transform: translateY(-1px); - box-shadow: 0 8px 12px rgba(217, 119, 6, 0.35); - background: var(--gradient-primary-hover); -} - -.btn-primary:active { - transform: translateY(0); -} - -/* Inputs with floating-like feel */ -.input { - width: 100%; - padding: var(--spacing-md); - background: var(--glass-bg); - border: 1px solid var(--glass-border); - border-radius: var(--radius-lg); - color: var(--color-text); - font-size: var(--font-base); - transition: all 0.2s ease; -} - -.input:focus { - outline: none; - border-color: var(--color-primary); - box-shadow: 0 0 0 3px var(--color-primary-lighter); - /* Use lighter opacity variable */ - background: var(--color-bg); -} - -/* Animations */ -@keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(20px); - } - - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes slideUpFade { - 0% { - opacity: 0; - transform: translateY(10px); - } - - 100% { - opacity: 1; - transform: translateY(0); - } -} - - - -@keyframes shimmer { - 0% { - background-position: -200% 0; - } - - 100% { - background-position: 200% 0; - } -} - -.animate-fade-in { - animation: fadeIn 0.5s ease-out forwards; -} - -.animate-slide-up { - animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; -} - -/* Staggered animation delays */ -.delay-100 { - animation-delay: 100ms; -} - -.delay-200 { - animation-delay: 200ms; -} - -.delay-300 { - animation-delay: 300ms; -} - -/* Custom Scrollbar */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: rgba(156, 163, 175, 0.3); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: rgba(156, 163, 175, 0.5); -} - -/* Custom Selection Color */ -::selection { - background: var(--color-primary-light); - /* Amber 200 */ - color: var(--color-text); -} \ No newline at end of file diff --git a/copy/src/App.tsx b/copy/src/App.tsx deleted file mode 100644 index f09102b..0000000 --- a/copy/src/App.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { RouterProvider } from 'react-router-dom'; -import { router } from './router'; -import './App.css'; - -/** - * Main Application Component - * Local-first accounting application - */ -function App() { - return ; -} - -export default App; diff --git a/copy/src/assets/react.svg b/copy/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/copy/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/copy/src/components/account/AccountCard/AccountCard.css b/copy/src/components/account/AccountCard/AccountCard.css deleted file mode 100644 index dc46d9a..0000000 --- a/copy/src/components/account/AccountCard/AccountCard.css +++ /dev/null @@ -1,238 +0,0 @@ -/** - * AccountCard Component Styles - */ - -.account-card { - position: relative; - display: flex; - flex-direction: column; - padding: 1.25rem; - border-radius: var(--radius-xl, 16px); - background: var(--glass-panel-bg, rgba(255, 255, 255, 0.7)); - border: 1px solid var(--glass-border, rgba(255, 255, 255, 0.5)); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - overflow: hidden; - height: 100%; - min-height: 160px; - justify-content: space-between; - cursor: default; -} - -.account-card--clickable { - cursor: pointer; -} - -.account-card:hover { - transform: translateY(-4px); - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1); - border-color: rgba(255, 255, 255, 0.8); -} - -.account-card--selected { - box-shadow: 0 0 0 2px var(--color-primary); - transform: scale(1.02); -} - -/* Background Decoration for Glass Effect */ -.account-card__background-decoration { - position: absolute; - top: -50px; - right: -50px; - width: 140px; - height: 140px; - border-radius: 50%; - background: radial-gradient(circle, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0) 70%); - opacity: 0.1; - pointer-events: none; - transition: opacity 0.3s ease; -} - -.account-card:hover .account-card__background-decoration { - opacity: 0.2; -} - -/* Themes per account type */ -.account-card--cash::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(52, 211, 153, 0.1) 100%); - z-index: -1; -} - -.account-card--debit::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(96, 165, 250, 0.1) 100%); - z-index: -1; -} - -.account-card--credit::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(248, 113, 113, 0.1) 100%); - z-index: -1; -} - -.account-card--ewallet::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(245, 158, 11, 0.05) 0%, rgba(251, 191, 36, 0.1) 100%); - z-index: -1; -} - -.account-card--investment::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(167, 139, 250, 0.1) 100%); - z-index: -1; -} - -.account-card--loan::before { - content: ''; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(107, 114, 128, 0.05) 0%, rgba(156, 163, 175, 0.1) 100%); - z-index: -1; -} - -/* Header Section */ -.account-card__header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 1rem; -} - -.account-card__icon-wrapper { - width: 44px; - height: 44px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.9); - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); - font-size: 1.5rem; -} - -/* Actions Overlay - Only visible on hover */ -.account-card__actions-overlay { - display: flex; - gap: 0.5rem; - opacity: 0; - transform: translateX(10px); - transition: all 0.2s ease; -} - -.account-card:hover .account-card__actions-overlay { - opacity: 1; - transform: translateX(0); -} - -.account-card__action-btn { - width: 32px; - height: 32px; - border-radius: 50%; - border: none; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - background: rgba(255, 255, 255, 0.8); - color: var(--color-text-secondary); - transition: all 0.2s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.account-card__action-btn:hover { - transform: scale(1.1); - color: var(--color-primary); -} - -.account-card__action-btn--delete:hover { - color: var(--color-error); - background: #fee2e2; -} - -/* Body Section */ -.account-card__body { - display: flex; - flex-direction: column; - gap: 0.25rem; -} - -.account-card__balance-section { - display: flex; - align-items: baseline; - gap: 0.25rem; -} - -.account-card__currency-symbol { - font-size: 0.875rem; - color: var(--color-text-secondary); - font-weight: 600; -} - -.account-card__balance { - font-family: 'Outfit', sans-serif; - /* Ensure you have this font or fallback */ - font-size: 1.75rem; - font-weight: 700; - color: var(--color-text); - line-height: 1.2; - letter-spacing: -0.02em; -} - -.account-card__balance--negative { - color: var(--color-error); - /* e.g., #ef4444 */ -} - -/* Info Section */ -.account-card__info { - display: flex; - flex-direction: column; -} - -.account-card__name { - margin: 0; - font-size: 1rem; - font-weight: 600; - color: var(--color-text); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.account-card__meta { - display: flex; - align-items: center; - gap: 0.5rem; - margin-top: 0.25rem; -} - -.account-card__type { - font-size: 0.75rem; - color: var(--color-text-secondary); - background: rgba(0, 0, 0, 0.04); - padding: 2px 8px; - border-radius: 4px; -} - -.account-card__tag { - font-size: 0.65rem; - background: rgba(99, 102, 241, 0.1); - color: var(--color-primary); - padding: 2px 6px; - border-radius: 4px; - font-weight: 600; - text-transform: uppercase; -} \ No newline at end of file diff --git a/copy/src/components/account/AccountCard/AccountCard.tsx b/copy/src/components/account/AccountCard/AccountCard.tsx deleted file mode 100644 index 213516d..0000000 --- a/copy/src/components/account/AccountCard/AccountCard.tsx +++ /dev/null @@ -1,146 +0,0 @@ -/** - * AccountCard Component - * Displays a single account with balance, type, and icon - */ - -import React from 'react'; -import type { Account, AccountType } from '../../../types'; -import { formatCurrency } from '../../../utils/format'; -import { Icon } from '@iconify/react'; -import './AccountCard.css'; - -interface AccountCardProps { - account: Account; - onClick?: (account: Account) => void; - onEdit?: (account: Account) => void; - onDelete?: (account: Account) => void; - selected?: boolean; -} - -/** - * Account type labels in Chinese - */ -const ACCOUNT_TYPE_LABELS: Record = { - cash: '现金', - debit_card: '储蓄卡', - credit_card: '信用卡', - e_wallet: '电子钱包', - credit_line: '信用账户', - investment: '投资账户', -}; - -/** - * Default icons for account types - */ -const DEFAULT_ICONS: Record = { - cash: '💵', - debit_card: '💳', - credit_card: '💳', - e_wallet: '📱', - credit_line: '🏦', - investment: '📈', -}; - -export const AccountCard: React.FC = ({ - account, - onClick, - onEdit, - onDelete, - selected = false, -}) => { - const handleClick = () => { - onClick?.(account); - }; - - const handleEdit = (e: React.MouseEvent) => { - e.stopPropagation(); - onEdit?.(account); - }; - - const handleDelete = (e: React.MouseEvent) => { - e.stopPropagation(); - onDelete?.(account); - }; - - const icon = account.icon || DEFAULT_ICONS[account.type] || '💰'; - const isNegative = account.balance < 0; - const typeLabel = ACCOUNT_TYPE_LABELS[account.type] || account.type; - - // Determine card theme based on account type - const getThemeClass = (type: AccountType) => { - switch (type) { - case 'cash': return 'account-card--cash'; - case 'debit_card': return 'account-card--debit'; - case 'credit_card': return 'account-card--credit'; - case 'e_wallet': return 'account-card--ewallet'; - case 'investment': return 'account-card--investment'; - case 'credit_line': return 'account-card--loan'; - default: return 'account-card--default'; - } - }; - - return ( -
{ - if (onClick && (e.key === 'Enter' || e.key === ' ')) { - handleClick(); - } - }} - > -
- -
-
-
{icon}
-
-
- {onEdit && ( - - )} - {onDelete && ( - - )} -
-
- -
-
- {account.currency} - - {formatCurrency(account.balance, account.currency).replace(/[^0-9.,-]/g, '')} - -
- -
-

{account.name}

-
- {typeLabel} - {account.isCredit && 信用} -
-
-
-
- ); -}; - -export default AccountCard; diff --git a/copy/src/components/account/AccountCard/index.ts b/copy/src/components/account/AccountCard/index.ts deleted file mode 100644 index 62bbc02..0000000 --- a/copy/src/components/account/AccountCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AccountCard, default } from './AccountCard'; diff --git a/copy/src/components/account/AccountForm/AccountForm.css b/copy/src/components/account/AccountForm/AccountForm.css deleted file mode 100644 index 0c55041..0000000 --- a/copy/src/components/account/AccountForm/AccountForm.css +++ /dev/null @@ -1,248 +0,0 @@ -/** - * AccountForm Component Styles - */ - -.account-form { - display: flex; - flex-direction: column; - gap: 1.25rem; - padding: 1.5rem; - background-color: var(--color-bg); - border-radius: 16px; - max-width: 500px; - margin: 0 auto; -} - -.account-form__title { - margin: 0 0 0.5rem 0; - font-size: 1.25rem; - font-weight: 600; - color: var(--color-text); - text-align: center; -} - -/* Field styles */ -.account-form__field { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.account-form__field--half { - flex: 1; -} - -.account-form__row { - display: flex; - gap: 1rem; -} - -.account-form__label { - font-size: 0.875rem; - font-weight: 500; - color: var(--color-text); -} - -.account-form__required { - color: var(--color-error); -} - -.account-form__input { - padding: 0.75rem 1rem; - border: 1px solid var(--color-border); - border-radius: 8px; - font-size: 1rem; - background-color: var(--color-bg); - color: var(--color-text); - transition: border-color 0.2s ease; -} - -.account-form__input:focus { - outline: none; - border-color: var(--color-primary); -} - -.account-form__input--error { - border-color: var(--color-error); -} - -.account-form__input--number { - flex: 1; -} - -.account-form__input:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.account-form__error { - font-size: 0.75rem; - color: var(--color-error); -} - -.account-form__hint { - font-size: 0.75rem; - color: var(--color-text-secondary); -} - -/* Input group for currency + amount */ -.account-form__input-group { - display: flex; - gap: 0.5rem; -} - -.account-form__currency-select { - padding: 0.75rem; - border: 1px solid var(--color-border); - border-radius: 8px; - font-size: 0.875rem; - background-color: var(--color-bg); - color: var(--color-text); - cursor: pointer; - min-width: 80px; -} - -.account-form__currency-select:focus { - outline: none; - border-color: var(--color-primary); -} - -/* Account type grid */ -.account-form__type-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.5rem; -} - -.account-form__type-btn { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.25rem; - padding: 0.75rem 0.5rem; - border: 1px solid var(--color-border); - border-radius: 8px; - background-color: var(--color-bg); - cursor: pointer; - transition: all 0.2s ease; -} - -.account-form__type-btn:hover { - border-color: var(--color-primary); -} - -.account-form__type-btn--selected { - border-color: var(--color-primary); - background-color: rgba(24, 144, 255, 0.1); -} - -.account-form__type-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.account-form__type-icon { - font-size: 1.5rem; -} - -.account-form__type-label { - font-size: 0.75rem; - color: var(--color-text); -} - -/* Icon grid */ -.account-form__icon-grid { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; -} - -.account-form__icon-btn { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid var(--color-border); - border-radius: 8px; - background-color: var(--color-bg); - font-size: 1.25rem; - cursor: pointer; - transition: all 0.2s ease; -} - -.account-form__icon-btn:hover { - border-color: var(--color-primary); -} - -.account-form__icon-btn--selected { - border-color: var(--color-primary); - background-color: rgba(24, 144, 255, 0.1); -} - -.account-form__icon-btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Form actions */ -.account-form__actions { - display: flex; - gap: 1rem; - margin-top: 0.5rem; -} - -.account-form__btn { - flex: 1; - padding: 0.875rem 1.5rem; - border: none; - border-radius: 8px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; -} - -.account-form__btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.account-form__btn--cancel { - background-color: var(--color-bg-secondary); - color: var(--color-text); - border: 1px solid var(--color-border); -} - -.account-form__btn--cancel:hover:not(:disabled) { - background-color: var(--color-border); -} - -.account-form__btn--submit { - background-color: var(--color-primary); - color: #fff; -} - -.account-form__btn--submit:hover:not(:disabled) { - background-color: var(--color-primary-hover); -} - -/* Responsive styles */ -@media (max-width: 480px) { - .account-form { - padding: 1rem; - } - - .account-form__type-grid { - grid-template-columns: repeat(2, 1fr); - } - - .account-form__row { - flex-direction: column; - gap: 1rem; - } - - .account-form__actions { - flex-direction: column-reverse; - } -} diff --git a/copy/src/components/account/AccountForm/AccountForm.tsx b/copy/src/components/account/AccountForm/AccountForm.tsx deleted file mode 100644 index 96d9dd2..0000000 --- a/copy/src/components/account/AccountForm/AccountForm.tsx +++ /dev/null @@ -1,328 +0,0 @@ -/** - * AccountForm Component - * Form for creating and editing accounts - */ - -import React, { useState, useEffect } from 'react'; -import type { Account, AccountType, CurrencyCode, AccountFormInput } from '../../../types'; -import './AccountForm.css'; - -interface AccountFormProps { - account?: Account; - onSubmit: (data: AccountFormInput) => void; - onCancel: () => void; - loading?: boolean; -} - -/** - * Account type options - */ -const ACCOUNT_TYPES: { value: AccountType; label: string; icon: string }[] = [ - { value: 'cash', label: '现金', icon: '💵' }, - { value: 'debit_card', label: '储蓄卡', icon: '💳' }, - { value: 'credit_card', label: '信用卡', icon: '💳' }, - { value: 'e_wallet', label: '电子钱包', icon: '📱' }, - { value: 'credit_line', label: '信用账户', icon: '🏦' }, - { value: 'investment', label: '投资账户', icon: '📈' }, -]; - -/** - * Currency options - */ -const CURRENCIES: { value: CurrencyCode; label: string }[] = [ - { value: 'CNY', label: '人民币 (CNY)' }, - { value: 'USD', label: '美元 (USD)' }, - { value: 'EUR', label: '欧元 (EUR)' }, - { value: 'JPY', label: '日元 (JPY)' }, - { value: 'GBP', label: '英镑 (GBP)' }, - { value: 'HKD', label: '港币 (HKD)' }, -]; - -/** - * Icon options - */ -const ICONS = ['💵', '💳', '📱', '🏦', '📈', '💰', '🪙', '💎', '🏧', '💴', '💶', '💷']; - -/** - * Credit account types that support negative balance - */ -const CREDIT_TYPES: AccountType[] = ['credit_card', 'credit_line']; - -export const AccountForm: React.FC = ({ - account, - onSubmit, - onCancel, - loading = false, -}) => { - const isEditing = !!account; - - const [formData, setFormData] = useState({ - name: '', - type: 'cash', - balance: 0, - currency: 'CNY', - icon: '💵', - billingDate: undefined, - paymentDate: undefined, - }); - - const [errors, setErrors] = useState>>({}); - - // Initialize form with account data when editing - useEffect(() => { - if (account) { - setFormData({ - name: account.name, - type: account.type, - balance: account.balance, - currency: account.currency, - icon: account.icon, - billingDate: account.billingDate, - paymentDate: account.paymentDate, - }); - } - }, [account]); - - const isCreditType = CREDIT_TYPES.includes(formData.type); - - const handleChange = (e: React.ChangeEvent) => { - const { name, value } = e.target; - - setFormData((prev) => ({ - ...prev, - [name]: - name === 'balance' || name === 'billingDate' || name === 'paymentDate' - ? value === '' - ? undefined - : Number(value) - : value, - })); - - // Clear error when field is modified - if (errors[name as keyof AccountFormInput]) { - setErrors((prev) => ({ ...prev, [name]: undefined })); - } - }; - - const handleTypeChange = (type: AccountType) => { - const typeOption = ACCOUNT_TYPES.find((t) => t.value === type); - setFormData((prev) => ({ - ...prev, - type, - icon: typeOption?.icon || prev.icon, - // Clear credit-specific fields if not a credit type - billingDate: CREDIT_TYPES.includes(type) ? prev.billingDate : undefined, - paymentDate: CREDIT_TYPES.includes(type) ? prev.paymentDate : undefined, - })); - }; - - const handleIconSelect = (icon: string) => { - setFormData((prev) => ({ ...prev, icon })); - }; - - const validate = (): boolean => { - const newErrors: Partial> = {}; - - if (!formData.name.trim()) { - newErrors.name = '请输入账户名称'; - } - - if (formData.balance === undefined || isNaN(formData.balance)) { - newErrors.balance = '请输入有效金额'; - } - - // Non-credit accounts cannot have negative balance - if (!isCreditType && formData.balance < 0) { - newErrors.balance = '非信用账户余额不能为负'; - } - - // Validate billing date for credit cards - if (isCreditType && formData.billingDate !== undefined) { - if (formData.billingDate < 1 || formData.billingDate > 31) { - newErrors.billingDate = '账单日应在1-31之间'; - } - } - - // Validate payment date for credit cards - if (isCreditType && formData.paymentDate !== undefined) { - if (formData.paymentDate < 1 || formData.paymentDate > 31) { - newErrors.paymentDate = '还款日应在1-31之间'; - } - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (validate()) { - onSubmit(formData); - } - }; - - return ( -
-

{isEditing ? '编辑账户' : '新建账户'}

- - {/* Account Name */} -
- - - {errors.name && {errors.name}} -
- - {/* Account Type */} -
- -
- {ACCOUNT_TYPES.map((type) => ( - - ))} -
-
- - {/* Initial Balance */} -
- -
- - -
- {errors.balance && {errors.balance}} - {isCreditType && 信用账户支持负余额(欠款)} -
- - {/* Credit Card Specific Fields */} - {isCreditType && ( -
-
- - - {errors.billingDate && ( - {errors.billingDate} - )} -
-
- - - {errors.paymentDate && ( - {errors.paymentDate} - )} -
-
- )} - - {/* Icon Selection */} -
- -
- {ICONS.map((icon) => ( - - ))} -
-
- - {/* Form Actions */} -
- - -
-
- ); -}; - -export default AccountForm; diff --git a/copy/src/components/account/AccountForm/index.ts b/copy/src/components/account/AccountForm/index.ts deleted file mode 100644 index f224c9a..0000000 --- a/copy/src/components/account/AccountForm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AccountForm, default } from './AccountForm'; diff --git a/copy/src/components/account/AccountList/AccountList.css b/copy/src/components/account/AccountList/AccountList.css deleted file mode 100644 index d9291d5..0000000 --- a/copy/src/components/account/AccountList/AccountList.css +++ /dev/null @@ -1,238 +0,0 @@ -/** - * AccountList Component - Glassmorphism Style - */ - -.account-list { - display: flex; - flex-direction: column; - gap: var(--spacing-lg); - width: 100%; -} - -/* Loading state */ -.account-list--loading { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: calc(var(--spacing-xl) * 2); - color: var(--color-text-secondary); - gap: var(--spacing-md); - background: var(--glass-bg); - backdrop-filter: var(--glass-blur); - -webkit-backdrop-filter: var(--glass-blur); - border: 1px solid var(--glass-border); - border-radius: var(--radius-lg); -} - -.account-list__spinner { - width: 40px; - height: 40px; - border: 3px solid var(--glass-border); - border-top-color: var(--color-primary); - border-radius: 50%; - animation: spin 0.8s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* Empty state */ -.account-list--empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: calc(var(--spacing-xl) * 2); - text-align: center; - background: var(--glass-bg); - backdrop-filter: var(--glass-blur); - -webkit-backdrop-filter: var(--glass-blur); - border: 1px dashed var(--glass-border); - border-radius: var(--radius-lg); -} - -.account-list__empty-icon { - font-size: 3.5rem; - margin-bottom: var(--spacing-md); - opacity: 0.6; -} - -.account-list__empty-message { - color: var(--color-text-secondary); - margin: 0; - font-size: 1rem; -} - -/* Summary section */ -.account-list__summary { - display: flex; - justify-content: space-between; - align-items: center; - padding: var(--spacing-lg) var(--spacing-xl); - background: linear-gradient(135deg, var(--color-primary), #d97706); - /* Enterprise Amber */ - border-radius: var(--radius-xl); - color: white; - box-shadow: 0 8px 30px rgba(245, 158, 11, 0.3); - position: relative; - overflow: hidden; - margin-bottom: var(--spacing-md); -} - -.account-list__summary::before { - content: ''; - position: absolute; - top: -50%; - right: -20%; - width: 200px; - height: 200px; - background: rgba(255, 255, 255, 0.15); - border-radius: 50%; -} - -.account-list__summary::after { - content: ''; - position: absolute; - bottom: -30%; - left: -10%; - width: 150px; - height: 150px; - background: rgba(255, 255, 255, 0.1); - border-radius: 50%; -} - -.account-list__summary-label { - font-size: 0.875rem; - font-weight: 600; - opacity: 0.95; - position: relative; - z-index: 1; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.account-list__summary-value { - font-family: 'Outfit', sans-serif; - font-size: 1.75rem; - font-weight: 800; - position: relative; - z-index: 1; - letter-spacing: -0.02em; -} - -.account-list__summary-value--negative { - color: #fecaca; -} - -/* Group section */ -.account-list__group { - display: flex; - flex-direction: column; - gap: var(--spacing-md); - animation: slideUpFade 0.5s ease-out forwards; -} - -.account-list__group-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0 var(--spacing-xs); - margin-bottom: -4px; -} - -.account-list__group-title { - margin: 0; - font-size: 0.875rem; - font-weight: 700; - color: var(--color-text-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; - display: flex; - align-items: center; - gap: 0.5rem; -} - -.account-list__group-title::before { - content: ''; - display: block; - width: 4px; - height: 16px; - background: var(--color-primary); - border-radius: 2px; -} - -.account-list__group-total { - font-family: 'Outfit', sans-serif; - font-size: 1rem; - font-weight: 700; - color: var(--color-text); - background: var(--glass-bg-heavy); - padding: 0.25rem 0.75rem; - border-radius: var(--radius-full); -} - -.account-list__group-total--negative { - color: var(--color-error); - background: rgba(239, 68, 68, 0.1); -} - -/* Grid Layout for Items */ -/* Grid Layout for Items */ -.account-list__group-items { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - /* Slightly reduced min-width for better fit */ - gap: var(--spacing-lg); -} - -/* Staggered animation delays */ -.account-list__group:nth-child(1) { - animation-delay: 0.1s; -} - -.account-list__group:nth-child(2) { - animation-delay: 0.2s; -} - -.account-list__group:nth-child(3) { - animation-delay: 0.3s; -} - -.account-list__group:nth-child(4) { - animation-delay: 0.4s; -} - -/* Mobile */ -@media (max-width: 480px) { - .account-list__summary { - padding: var(--spacing-md) var(--spacing-lg); - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-xs); - } - - .account-list__summary-value { - font-size: 1.5rem; - } - - .account-list__group-items { - grid-template-columns: 1fr; - /* Stack on mobile */ - } -} - -/* Reduced Motion */ -@media (prefers-reduced-motion: reduce) { - .account-list__spinner { - animation: none; - } - - .account-list__group { - animation: none; - opacity: 1; - } -} \ No newline at end of file diff --git a/copy/src/components/account/AccountList/AccountList.tsx b/copy/src/components/account/AccountList/AccountList.tsx deleted file mode 100644 index 7e755d5..0000000 --- a/copy/src/components/account/AccountList/AccountList.tsx +++ /dev/null @@ -1,129 +0,0 @@ -/** - * AccountList Component - * Displays list of all accounts grouped by type - */ - -import React from 'react'; -import type { Account, AccountType } from '../../../types'; -import { AccountCard } from '../AccountCard'; -import { formatCurrency } from '../../../utils/format'; -import { groupAccountsByType, calculateTotalBalance } from '../../../services/accountService'; -import './AccountList.css'; - -interface AccountListProps { - accounts: Account[]; - onAccountClick?: (account: Account) => void; - onAccountEdit?: (account: Account) => void; - onAccountDelete?: (account: Account) => void; - selectedAccountId?: number; - loading?: boolean; - emptyMessage?: string; -} - -/** - * Account type labels in Chinese - */ -const ACCOUNT_TYPE_LABELS: Record = { - cash: '现金账户', - debit_card: '储蓄卡', - credit_card: '信用卡', - e_wallet: '电子钱包', - credit_line: '信用账户', - investment: '投资账户', -}; - -/** - * Account type display order - */ -const ACCOUNT_TYPE_ORDER: AccountType[] = [ - 'cash', - 'debit_card', - 'e_wallet', - 'credit_card', - 'credit_line', - 'investment', -]; - -export const AccountList: React.FC = ({ - accounts, - onAccountClick, - onAccountEdit, - onAccountDelete, - selectedAccountId, - loading = false, - emptyMessage = '暂无账户,点击上方按钮添加', -}) => { - if (loading) { - return ( -
-
- 加载中... -
- ); - } - - if (accounts.length === 0) { - return ( -
- 💰 -

{emptyMessage}

-
- ); - } - - const groupedAccounts = groupAccountsByType(accounts); - const totalBalance = calculateTotalBalance(accounts); - - // Sort groups by predefined order - const sortedGroups = ACCOUNT_TYPE_ORDER.filter( - (type) => groupedAccounts[type] && groupedAccounts[type].length > 0 - ); - - return ( -
- {/* Total Balance Summary */} -
- 总余额 - - {formatCurrency(totalBalance, 'CNY')} - -
- - {/* Grouped Accounts */} - {sortedGroups.map((type) => { - const typeAccounts = groupedAccounts[type]; - const groupTotal = calculateTotalBalance(typeAccounts); - const typeLabel = ACCOUNT_TYPE_LABELS[type as AccountType] || type; - - return ( -
-
-

{typeLabel}

- - {formatCurrency(groupTotal, 'CNY')} - -
-
- {typeAccounts.map((account) => ( - - ))} -
-
- ); - })} -
- ); -}; - -export default AccountList; diff --git a/copy/src/components/account/AccountList/index.ts b/copy/src/components/account/AccountList/index.ts deleted file mode 100644 index 9507f41..0000000 --- a/copy/src/components/account/AccountList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AccountList, default } from './AccountList'; diff --git a/copy/src/components/account/TransferForm/TransferForm.css b/copy/src/components/account/TransferForm/TransferForm.css deleted file mode 100644 index be877ed..0000000 --- a/copy/src/components/account/TransferForm/TransferForm.css +++ /dev/null @@ -1,279 +0,0 @@ -/** - * TransferForm Component Styles - */ - -.transfer-form { - display: flex; - flex-direction: column; - gap: 1.25rem; - padding: 1.5rem; - background-color: var(--color-bg); - border-radius: 16px; - max-width: 500px; - margin: 0 auto; -} - -.transfer-form__title { - margin: 0 0 0.5rem 0; - font-size: 1.25rem; - font-weight: 600; - color: var(--color-text); - text-align: center; -} - -/* Field styles */ -.transfer-form__field { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.transfer-form__label { - font-size: 0.875rem; - font-weight: 500; - color: var(--color-text); -} - -.transfer-form__required { - color: var(--color-error); -} - -.transfer-form__select { - padding: 0.75rem 1rem; - border: 1px solid var(--color-border); - border-radius: 8px; - font-size: 1rem; - background-color: var(--color-bg); - color: var(--color-text); - cursor: pointer; - transition: border-color 0.2s ease; -} - -.transfer-form__select:focus { - outline: none; - border-color: var(--color-primary); -} - -.transfer-form__select--error { - border-color: var(--color-error); -} - -.transfer-form__select:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.transfer-form__error { - font-size: 0.75rem; - color: var(--color-error); -} - -.transfer-form__balance { - font-size: 0.75rem; - color: var(--color-text-secondary); -} - -/* Swap button */ -.transfer-form__swap { - display: flex; - justify-content: center; - margin: -0.5rem 0; -} - -.transfer-form__swap-btn { - width: 40px; - height: 40px; - display: flex; - align-items: center; - justify-content: center; - border: 1px solid var(--color-border); - border-radius: 50%; - background-color: var(--color-bg); - font-size: 1.25rem; - cursor: pointer; - transition: all 0.2s ease; - color: var(--color-text); -} - -.transfer-form__swap-btn:hover:not(:disabled) { - border-color: var(--color-primary); - background-color: var(--color-bg-secondary); -} - -.transfer-form__swap-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -/* Amount input */ -.transfer-form__amount-input { - display: flex; - align-items: center; - border: 1px solid var(--color-border); - border-radius: 8px; - overflow: hidden; - transition: border-color 0.2s ease; -} - -.transfer-form__amount-input:focus-within { - border-color: var(--color-primary); -} - -.transfer-form__currency-symbol { - padding: 0.75rem 1rem; - background-color: var(--color-bg-secondary); - color: var(--color-text-secondary); - font-size: 1rem; - font-weight: 500; - border-right: 1px solid var(--color-border); -} - -.transfer-form__input { - flex: 1; - padding: 0.75rem 1rem; - border: none; - font-size: 1.25rem; - font-weight: 500; - background-color: var(--color-bg); - color: var(--color-text); -} - -.transfer-form__input:focus { - outline: none; -} - -.transfer-form__input--error { - color: var(--color-error); -} - -.transfer-form__input:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Textarea */ -.transfer-form__textarea { - padding: 0.75rem 1rem; - border: 1px solid var(--color-border); - border-radius: 8px; - font-size: 1rem; - background-color: var(--color-bg); - color: var(--color-text); - resize: vertical; - font-family: inherit; - transition: border-color 0.2s ease; -} - -.transfer-form__textarea:focus { - outline: none; - border-color: var(--color-primary); -} - -.transfer-form__textarea:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -/* Transfer preview */ -.transfer-form__preview { - display: flex; - align-items: center; - justify-content: space-between; - padding: 1rem; - background-color: var(--color-bg-secondary); - border-radius: 8px; - gap: 0.5rem; -} - -.transfer-form__preview-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 0.25rem; - flex: 1; -} - -.transfer-form__preview-label { - font-size: 0.75rem; - color: var(--color-text-secondary); -} - -.transfer-form__preview-value { - font-size: 1rem; - font-weight: 600; -} - -.transfer-form__preview-value--from { - color: var(--color-error); -} - -.transfer-form__preview-value--to { - color: var(--color-success); -} - -.transfer-form__preview-arrow { - font-size: 1.25rem; - color: var(--color-text-secondary); -} - -/* Form actions */ -.transfer-form__actions { - display: flex; - gap: 1rem; - margin-top: 0.5rem; -} - -.transfer-form__btn { - flex: 1; - padding: 0.875rem 1.5rem; - border: none; - border-radius: 8px; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; -} - -.transfer-form__btn:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.transfer-form__btn--cancel { - background-color: var(--color-bg-secondary); - color: var(--color-text); - border: 1px solid var(--color-border); -} - -.transfer-form__btn--cancel:hover:not(:disabled) { - background-color: var(--color-border); -} - -.transfer-form__btn--submit { - background-color: var(--color-primary); - color: #fff; -} - -.transfer-form__btn--submit:hover:not(:disabled) { - background-color: var(--color-primary-hover); -} - -/* Responsive styles */ -@media (max-width: 480px) { - .transfer-form { - padding: 1rem; - } - - .transfer-form__preview { - flex-direction: column; - gap: 0.75rem; - } - - .transfer-form__preview-arrow { - transform: rotate(90deg); - } - - .transfer-form__actions { - flex-direction: column-reverse; - } -} diff --git a/copy/src/components/account/TransferForm/TransferForm.tsx b/copy/src/components/account/TransferForm/TransferForm.tsx deleted file mode 100644 index d3b6ead..0000000 --- a/copy/src/components/account/TransferForm/TransferForm.tsx +++ /dev/null @@ -1,277 +0,0 @@ -/** - * TransferForm Component - * Form for transferring money between accounts - */ - -import React, { useState, useEffect } from 'react'; -import type { Account, TransferFormInput } from '../../../types'; -import { formatCurrency } from '../../../utils/format'; -import './TransferForm.css'; - -interface TransferFormProps { - accounts: Account[]; - onSubmit: (data: TransferFormInput) => void; - onCancel: () => void; - loading?: boolean; - initialFromAccountId?: number; -} - -export const TransferForm: React.FC = ({ - accounts, - onSubmit, - onCancel, - loading = false, - initialFromAccountId, -}) => { - const [formData, setFormData] = useState({ - fromAccountId: initialFromAccountId || 0, - toAccountId: 0, - amount: 0, - note: '', - }); - - const [errors, setErrors] = useState>>({}); - - // Set initial from account - useEffect(() => { - if (initialFromAccountId) { - setFormData((prev) => ({ ...prev, fromAccountId: initialFromAccountId })); - } else if (accounts.length > 0 && formData.fromAccountId === 0) { - setFormData((prev) => ({ ...prev, fromAccountId: accounts[0].id })); - } - }, [accounts, initialFromAccountId]); - - const fromAccount = accounts.find((a) => a.id === formData.fromAccountId); - const toAccount = accounts.find((a) => a.id === formData.toAccountId); - - // Filter available target accounts (exclude source account) - const availableToAccounts = accounts.filter((a) => a.id !== formData.fromAccountId); - - const handleChange = ( - e: React.ChangeEvent - ) => { - const { name, value } = e.target; - - setFormData((prev) => ({ - ...prev, - [name]: - name === 'amount' || name === 'fromAccountId' || name === 'toAccountId' - ? Number(value) - : value, - })); - - // Clear error when field is modified - if (errors[name as keyof TransferFormInput]) { - setErrors((prev) => ({ ...prev, [name]: undefined })); - } - - // Reset toAccountId if it equals the new fromAccountId - if (name === 'fromAccountId' && Number(value) === formData.toAccountId) { - setFormData((prev) => ({ ...prev, toAccountId: 0 })); - } - }; - - const swapAccounts = () => { - if (formData.toAccountId !== 0) { - setFormData((prev) => ({ - ...prev, - fromAccountId: prev.toAccountId, - toAccountId: prev.fromAccountId, - })); - } - }; - - const validate = (): boolean => { - const newErrors: Partial> = {}; - - if (!formData.fromAccountId) { - newErrors.fromAccountId = '请选择转出账户'; - } - - if (!formData.toAccountId) { - newErrors.toAccountId = '请选择转入账户'; - } - - if (formData.fromAccountId === formData.toAccountId) { - newErrors.toAccountId = '转入账户不能与转出账户相同'; - } - - if (!formData.amount || formData.amount <= 0) { - newErrors.amount = '请输入有效的转账金额'; - } - - // Check if source account has sufficient balance (for non-credit accounts) - if (fromAccount && !fromAccount.isCredit && formData.amount > fromAccount.balance) { - newErrors.amount = `余额不足,当前余额: ${formatCurrency(fromAccount.balance, fromAccount.currency)}`; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (validate()) { - onSubmit(formData); - } - }; - - return ( -
-

账户转账

- - {/* From Account */} -
- - - {errors.fromAccountId && ( - {errors.fromAccountId} - )} - {fromAccount && ( - - 可用余额: {formatCurrency(fromAccount.balance, fromAccount.currency)} - - )} -
- - {/* Swap Button */} -
- -
- - {/* To Account */} -
- - - {errors.toAccountId && {errors.toAccountId}} - {toAccount && ( - - 当前余额: {formatCurrency(toAccount.balance, toAccount.currency)} - - )} -
- - {/* Amount */} -
- -
- - {fromAccount?.currency === 'CNY' ? '¥' : fromAccount?.currency || '¥'} - - -
- {errors.amount && {errors.amount}} -
- - {/* Note */} -
- -