diff --git a/app/AccountType.php b/app/AccountType.php deleted file mode 100644 index 07d591d4..00000000 --- a/app/AccountType.php +++ /dev/null @@ -1,12 +0,0 @@ - + * @return array */ public static function getDefaultCategories(): array { @@ -33,316 +33,379 @@ class CreateDefaultCategories 'name' => 'Food', 'icon' => 'Utensils', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Cafes, restaurants, bars', 'icon' => 'Wine', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Groceries', 'icon' => 'ShoppingBasket', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Tobacco and alcohol', 'icon' => 'Cigarette', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Other groceries', 'icon' => 'ShoppingBasket', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Food delivery', 'icon' => 'Pizza', 'color' => 'red', + 'type' => 'expense', ], [ 'name' => 'Utility services', 'icon' => 'Home', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Electricity', 'icon' => 'Bolt', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Natural gas', 'icon' => 'Flame', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Rent and maintanence', 'icon' => 'Wrench', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Telephone, internet, TV, computer', 'icon' => 'Wifi', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Water', 'icon' => 'Droplets', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Other utility expenses', 'icon' => 'Receipt', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Household goods', 'icon' => 'Home', 'color' => 'orange', + 'type' => 'expense', ], [ 'name' => 'Transportation', 'icon' => 'Bus', 'color' => 'amber', + 'type' => 'expense', ], [ 'name' => 'Parking', 'icon' => 'ParkingMeter', 'color' => 'amber', + 'type' => 'expense', ], [ 'name' => 'Fuel', 'icon' => 'Fuel', 'color' => 'amber', + 'type' => 'expense', ], [ 'name' => 'Transportation expenses', 'icon' => 'Ticket', 'color' => 'amber', + 'type' => 'expense', ], [ 'name' => 'Vehicle purchase, maintenance', 'icon' => 'Car', 'color' => 'amber', + 'type' => 'expense', ], [ 'name' => 'Clothing and shoes', 'icon' => 'Shirt', 'color' => 'pink', + 'type' => 'expense', ], [ 'name' => 'Leisure activities, traveling', 'icon' => 'Plane', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Gifts', 'icon' => 'Gift', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Books, newspapers, magazines', 'icon' => 'BookOpen', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Accommodation, travel expenses', 'icon' => 'Hotel', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Sport and sports goods', 'icon' => 'Dumbbell', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Theatre, music, cinema', 'icon' => 'Clapperboard', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Hobbies and other leisure time activites', 'icon' => 'Puzzle', 'color' => 'violet', + 'type' => 'expense', ], [ 'name' => 'Education, health and beauty', 'icon' => 'GraduationCap', 'color' => 'rose', + 'type' => 'expense', ], [ 'name' => 'Education and courses', 'icon' => 'GraduationCap', 'color' => 'rose', + 'type' => 'expense', ], [ 'name' => 'Beauty, cosmetics', 'icon' => 'Sparkles', 'color' => 'rose', + 'type' => 'expense', ], [ 'name' => 'Health and pharmaceuticals', 'icon' => 'HeartPulse', 'color' => 'rose', + 'type' => 'expense', ], [ 'name' => 'Online transactions', 'icon' => 'Globe', 'color' => 'fuchsia', + 'type' => 'expense', ], [ 'name' => 'Online services', 'icon' => 'Server', 'color' => 'fuchsia', + 'type' => 'expense', ], [ 'name' => 'Insurance', 'icon' => 'ShieldCheck', 'color' => 'yellow', + 'type' => 'expense', ], [ 'name' => 'Investments', 'icon' => 'LineChart', 'color' => 'lime', + 'type' => 'expense', ], [ 'name' => 'Savings', 'icon' => 'PiggyBank', 'color' => 'lime', + 'type' => 'expense', ], [ 'name' => 'Other investments', 'icon' => 'TrendingUp', 'color' => 'lime', + 'type' => 'expense', ], [ 'name' => 'Financial services and commission', 'icon' => 'Landmark', 'color' => 'slate', + 'type' => 'expense', ], [ 'name' => 'Fines', 'icon' => 'Gavel', 'color' => 'slate', + 'type' => 'expense', ], [ 'name' => 'Mortgage', 'icon' => 'Building2', 'color' => 'slate', + 'type' => 'expense', ], [ 'name' => 'Credit card repayment', 'icon' => 'CreditCard', 'color' => 'slate', + 'type' => 'expense', ], [ 'name' => 'Cash withdrawal', 'icon' => 'Banknote', 'color' => 'neutral', + 'type' => 'expense', ], [ 'name' => 'Gambling', 'icon' => 'Dice5', 'color' => 'purple', + 'type' => 'expense', ], [ 'name' => 'Lottery', 'icon' => 'TicketPercent', 'color' => 'purple', + 'type' => 'expense', ], [ 'name' => 'Taxes and government fees', 'icon' => 'FileText', 'color' => 'stone', + 'type' => 'expense', ], [ 'name' => 'Invoices', 'icon' => 'FileInvoice', 'color' => 'stone', + 'type' => 'expense', ], [ 'name' => 'Personal transfers', 'icon' => 'ArrowLeftRight', 'color' => 'cyan', + 'type' => 'transfer', ], [ 'name' => 'Other personal transfers', 'icon' => 'ArrowLeftRight', 'color' => 'cyan', + 'type' => 'transfer', ], [ 'name' => 'Administrative violations', 'icon' => 'BadgeAlert', 'color' => 'stone', + 'type' => 'expense', ], [ 'name' => 'Other transfers', 'icon' => 'Split', 'color' => 'stone', + 'type' => 'transfer', ], [ 'name' => 'Other payments', 'icon' => 'Wallet', 'color' => 'stone', + 'type' => 'expense', ], [ 'name' => 'Salary', 'icon' => 'Coins', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Regular income', 'icon' => 'Coins', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Work on demand', 'icon' => 'Briefcase', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Income from rent', 'icon' => 'Building', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Unemployment benefit', 'icon' => 'HandCoins', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Tax return', 'icon' => 'RotateCcw', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Return debit', 'icon' => 'Undo2', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Own account', 'icon' => 'ArrowRightLeft', 'color' => 'blue', + 'type' => 'transfer', ], [ 'name' => 'From account of relatives', 'icon' => 'Users', 'color' => 'blue', + 'type' => 'transfer', ], [ 'name' => 'Returned payments', 'icon' => 'RotateCw', 'color' => 'green', + 'type' => 'income', ], [ 'name' => 'Credit cards', 'icon' => 'CreditCard', 'color' => 'green', + 'type' => 'expense', ], [ 'name' => 'Other incoming payments', 'icon' => 'DollarSign', 'color' => 'green', + 'type' => 'income', ], ]; } diff --git a/app/Console/Commands/SetupMainUser.php b/app/Console/Commands/SetupMainUser.php index 8c8082ea..53cb98f5 100644 --- a/app/Console/Commands/SetupMainUser.php +++ b/app/Console/Commands/SetupMainUser.php @@ -130,6 +130,7 @@ class SetupMainUser extends Command 'name' => $categoryData['name'], 'icon' => $categoryData['icon'], 'color' => $categoryData['color'], + 'type' => $categoryData['type'] ?? 'expense', 'created_at' => $categoryData['created_at'] ?? now(), 'updated_at' => $categoryData['updated_at'] ?? now(), ]); diff --git a/app/Enums/CategoryColor.php b/app/Enums/CategoryColor.php new file mode 100644 index 00000000..9fd24f0e --- /dev/null +++ b/app/Enums/CategoryColor.php @@ -0,0 +1,27 @@ +user() ->categories() ->orderBy('name') - ->get(['id', 'name', 'icon', 'color']); + ->get(['id', 'name', 'icon', 'color', 'type']); return Inertia::render('settings/categories', [ 'categories' => $categories, diff --git a/app/Http/Requests/Settings/StoreCategoryRequest.php b/app/Http/Requests/Settings/StoreCategoryRequest.php index dd4088fd..4311b19e 100644 --- a/app/Http/Requests/Settings/StoreCategoryRequest.php +++ b/app/Http/Requests/Settings/StoreCategoryRequest.php @@ -2,6 +2,8 @@ namespace App\Http\Requests\Settings; +use App\Enums\CategoryColor; +use App\Enums\CategoryType; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -29,22 +31,12 @@ class StoreCategoryRequest extends FormRequest 'color' => [ 'required', 'string', - Rule::in([ - 'amber', - 'blue', - 'cyan', - 'emerald', - 'gray', - 'green', - 'indigo', - 'orange', - 'pink', - 'purple', - 'red', - 'slate', - 'teal', - 'yellow', - ]), + Rule::enum(CategoryColor::class), + ], + 'type' => [ + 'required', + 'string', + Rule::enum(CategoryType::class), ], ]; } diff --git a/app/Http/Requests/Settings/UpdateCategoryRequest.php b/app/Http/Requests/Settings/UpdateCategoryRequest.php index 2b24c720..a89688bd 100644 --- a/app/Http/Requests/Settings/UpdateCategoryRequest.php +++ b/app/Http/Requests/Settings/UpdateCategoryRequest.php @@ -2,6 +2,8 @@ namespace App\Http\Requests\Settings; +use App\Enums\CategoryColor; +use App\Enums\CategoryType; use Illuminate\Contracts\Validation\ValidationRule; use Illuminate\Foundation\Http\FormRequest; use Illuminate\Validation\Rule; @@ -29,22 +31,12 @@ class UpdateCategoryRequest extends FormRequest 'color' => [ 'required', 'string', - Rule::in([ - 'amber', - 'blue', - 'cyan', - 'emerald', - 'gray', - 'green', - 'indigo', - 'orange', - 'pink', - 'purple', - 'red', - 'slate', - 'teal', - 'yellow', - ]), + Rule::enum(CategoryColor::class), + ], + 'type' => [ + 'required', + 'string', + Rule::enum(CategoryType::class), ], ]; } diff --git a/database/factories/CategoryFactory.php b/database/factories/CategoryFactory.php index 7f701e5c..95b8a35a 100644 --- a/database/factories/CategoryFactory.php +++ b/database/factories/CategoryFactory.php @@ -2,6 +2,7 @@ namespace Database\Factories; +use App\Enums\CategoryType; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; @@ -21,6 +22,7 @@ class CategoryFactory extends Factory 'name' => fake()->unique()->randomElement(['Food', 'Transport', 'Entertainment', 'Shopping', 'Healthcare', 'Utilities', 'Travel', 'Education', 'Bills', 'Investments', 'Savings', 'Insurance', 'Gifts', 'Personal Care', 'Sports', 'Hobbies', 'Subscriptions', 'Dining', 'Groceries', 'Clothing']), 'icon' => fake()->randomElement(['AlertCircle', 'AlertTriangle', 'ArrowDownCircle', 'ArrowLeftRight', 'ArrowUpCircle', 'Baby', 'Banknote', 'Briefcase', 'Building', 'Building2', 'Car', 'Clock', 'CreditCard', 'Dices', 'FileText', 'Gift', 'GraduationCap', 'HandHeart', 'Heart', 'HelpCircle', 'Home', 'Landmark', 'Mail', 'PiggyBank', 'Plane', 'Receipt', 'ReceiptText', 'Repeat', 'RotateCcw', 'Scale', 'Shield', 'ShieldCheck', 'ShoppingBag', 'TrendingUp', 'Undo2', 'Users', 'Users2', 'Utensils', 'Wallet']), 'color' => fake()->randomElement(['amber', 'blue', 'cyan', 'emerald', 'gray', 'green', 'indigo', 'orange', 'pink', 'purple', 'red', 'slate', 'teal', 'yellow']), + 'type' => fake()->randomElement(CategoryType::cases()), 'user_id' => User::factory(), ]; } diff --git a/database/migrations/2025_11_29_170353_add_type_to_categories_table.php b/database/migrations/2025_11_29_170353_add_type_to_categories_table.php index f054275e..78e3d179 100644 --- a/database/migrations/2025_11_29_170353_add_type_to_categories_table.php +++ b/database/migrations/2025_11_29_170353_add_type_to_categories_table.php @@ -12,7 +12,7 @@ return new class extends Migration public function up(): void { Schema::table('categories', function (Blueprint $table) { - $table->string('type')->default('expense')->after('color'); + $table->string('type')->after('color'); }); } diff --git a/resources/js/components/categories/create-category-dialog.tsx b/resources/js/components/categories/create-category-dialog.tsx index ddfa56f9..b1658592 100644 --- a/resources/js/components/categories/create-category-dialog.tsx +++ b/resources/js/components/categories/create-category-dialog.tsx @@ -1,4 +1,5 @@ import { store } from '@/actions/App/Http/Controllers/Settings/CategoryController'; +import { Alert, AlertDescription } from '@/components/ui/alert'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -22,10 +23,12 @@ import { categorySyncService } from '@/services/category-sync'; import { CATEGORY_COLORS, CATEGORY_ICONS, + CATEGORY_TYPES, getCategoryColorClasses, } from '@/types/category'; import { Form } from '@inertiajs/react'; import * as Icons from 'lucide-react'; +import { Info } from 'lucide-react'; import { useState } from 'react'; export function CreateCategoryDialog({ @@ -34,6 +37,7 @@ export function CreateCategoryDialog({ onSuccess?: () => void; }) { const [open, setOpen] = useState(false); + const [selectedType, setSelectedType] = useState(''); return ( @@ -139,6 +143,44 @@ export function CreateCategoryDialog({ )} +
+ + + {errors.type && ( +

+ {errors.type} +

+ )} + {selectedType === 'transfer' && ( + + + + Transactions in this category will + not be counted in top expenses or + income. Transfer categories are + mainly used for transactions between + accounts. + + + )} +
+
+
+ + + {errors.type && ( +

+ {errors.type} +

+ )} + {selectedType === 'transfer' && ( + + + + Transactions in this category will + not be counted in top expenses or + income. Transfer categories are + mainly used for transactions between + accounts. + + + )} +
+