Update default categories
This commit is contained in:
parent
b866e6152e
commit
2c696aa88a
|
|
@ -11,19 +11,77 @@ class CreateDefaultCategories
|
|||
*/
|
||||
public function handle(User $user): void
|
||||
{
|
||||
$defaultCategories = [
|
||||
['name' => 'Groceries', 'icon' => 'ShoppingCart', 'color' => 'green'],
|
||||
['name' => 'Transportation', 'icon' => 'Car', 'color' => 'blue'],
|
||||
['name' => 'Entertainment', 'icon' => 'Film', 'color' => 'purple'],
|
||||
['name' => 'Dining Out', 'icon' => 'Utensils', 'color' => 'orange'],
|
||||
['name' => 'Healthcare', 'icon' => 'Heart', 'color' => 'red'],
|
||||
['name' => 'Utilities', 'icon' => 'Zap', 'color' => 'yellow'],
|
||||
['name' => 'Shopping', 'icon' => 'ShoppingBag', 'color' => 'pink'],
|
||||
['name' => 'Travel', 'icon' => 'Plane', 'color' => 'sky'],
|
||||
];
|
||||
$defaultCategories = self::getDefaultCategories();
|
||||
|
||||
foreach ($defaultCategories as $category) {
|
||||
$user->categories()->create($category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default categories configuration.
|
||||
*
|
||||
* @return array<int, array{name: string, icon: string, color: string}>
|
||||
*/
|
||||
public static function getDefaultCategories(): array
|
||||
{
|
||||
return
|
||||
['name' => 'Administrative violations', 'icon' => 'AlertTriangle', 'color' => 'red'],
|
||||
['name' => 'Alimony and child support', 'icon' => 'Users', 'color' => 'teal'],
|
||||
['name' => 'Association membership fees', 'icon' => 'Users2', 'color' => 'cyan'],
|
||||
['name' => 'Bailiffs', 'icon' => 'Scale', 'color' => 'amber'],
|
||||
['name' => 'Bills', 'icon' => 'FileText', 'color' => 'red'],
|
||||
['name' => 'Cash deposit', 'icon' => 'Banknote', 'color' => 'green'],
|
||||
['name' => 'Cash withdrawal', 'icon' => 'Banknote', 'color' => 'emerald'],
|
||||
['name' => 'Cashback rewards', 'icon' => 'Gift', 'color' => 'pink'],
|
||||
['name' => 'Cheque', 'icon' => 'Receipt', 'color' => 'gray'],
|
||||
['name' => 'Cheque deposit', 'icon' => 'Receipt', 'color' => 'green'],
|
||||
['name' => 'Children', 'icon' => 'Baby', 'color' => 'yellow'],
|
||||
['name' => 'Clothing and shoes', 'icon' => 'ShoppingBag', 'color' => 'pink'],
|
||||
['name' => 'Currency exchange', 'icon' => 'Repeat', 'color' => 'green'],
|
||||
['name' => 'Debt collection', 'icon' => 'FileText', 'color' => 'orange'],
|
||||
['name' => 'Dishonors', 'icon' => 'AlertCircle', 'color' => 'red'],
|
||||
['name' => 'Dividends Income', 'icon' => 'TrendingUp', 'color' => 'emerald'],
|
||||
['name' => 'Education, health and beauty', 'icon' => 'Heart', 'color' => 'red'],
|
||||
['name' => 'Enquiries', 'icon' => 'HelpCircle', 'color' => 'blue'],
|
||||
['name' => 'Entertainment', 'icon' => 'Heart', 'color' => 'purple'],
|
||||
['name' => 'Financial services and commission', 'icon' => 'Building2', 'color' => 'slate'],
|
||||
['name' => 'Food', 'icon' => 'Utensils', 'color' => 'orange'],
|
||||
['name' => 'Gambling', 'icon' => 'Dices', 'color' => 'red'],
|
||||
['name' => 'Groceries', 'icon' => 'ShoppingBag', 'color' => 'green'],
|
||||
['name' => 'Healthcare', 'icon' => 'Heart', 'color' => 'red'],
|
||||
['name' => 'Household goods', 'icon' => 'Home', 'color' => 'green'],
|
||||
['name' => 'Income from rent', 'icon' => 'Building', 'color' => 'indigo'],
|
||||
['name' => 'Insurance', 'icon' => 'Shield', 'color' => 'cyan'],
|
||||
['name' => 'Insurance indemnity', 'icon' => 'ShieldCheck', 'color' => 'cyan'],
|
||||
['name' => 'Interest received', 'icon' => 'TrendingUp', 'color' => 'emerald'],
|
||||
['name' => 'Invoices', 'icon' => 'FileText', 'color' => 'indigo'],
|
||||
['name' => 'Leisure activities, traveling', 'icon' => 'Plane', 'color' => 'purple'],
|
||||
['name' => 'Loans', 'icon' => 'Landmark', 'color' => 'gray'],
|
||||
['name' => 'Online transactions', 'icon' => 'CreditCard', 'color' => 'indigo'],
|
||||
['name' => 'Other incoming payments', 'icon' => 'ArrowUpCircle', 'color' => 'green'],
|
||||
['name' => 'Other incoming payments from employer', 'icon' => 'Briefcase', 'color' => 'blue'],
|
||||
['name' => 'Other outgoing payments', 'icon' => 'ArrowDownCircle', 'color' => 'gray'],
|
||||
['name' => 'Pension', 'icon' => 'Wallet', 'color' => 'teal'],
|
||||
['name' => 'Personal transfers', 'icon' => 'ArrowLeftRight', 'color' => 'purple'],
|
||||
['name' => 'Postal services', 'icon' => 'Mail', 'color' => 'blue'],
|
||||
['name' => 'Regular income', 'icon' => 'TrendingUp', 'color' => 'emerald'],
|
||||
['name' => 'Restaurants', 'icon' => 'Utensils', 'color' => 'orange'],
|
||||
['name' => 'Return debit', 'icon' => 'Undo2', 'color' => 'amber'],
|
||||
['name' => 'Returned payments', 'icon' => 'RotateCcw', 'color' => 'orange'],
|
||||
['name' => 'Salary', 'icon' => 'Wallet', 'color' => 'green'],
|
||||
['name' => 'Savings and investments', 'icon' => 'PiggyBank', 'color' => 'green'],
|
||||
['name' => 'Scholarships', 'icon' => 'GraduationCap', 'color' => 'purple'],
|
||||
['name' => 'Shopping', 'icon' => 'ShoppingBag', 'color' => 'pink'],
|
||||
['name' => 'Social security benefits', 'icon' => 'HandHeart', 'color' => 'cyan'],
|
||||
['name' => 'State aid payments', 'icon' => 'Landmark', 'color' => 'blue'],
|
||||
['name' => 'Tax return', 'icon' => 'ReceiptText', 'color' => 'green'],
|
||||
['name' => 'Taxes and government fees', 'icon' => 'Receipt', 'color' => 'slate'],
|
||||
['name' => 'Transportation', 'icon' => 'Car', 'color' => 'blue'],
|
||||
['name' => 'Travel', 'icon' => 'Plane', 'color' => 'blue'],
|
||||
['name' => 'Utilities', 'icon' => 'Home', 'color' => 'yellow'],
|
||||
['name' => 'Utility services', 'icon' => 'Home', 'color' => 'blue'],
|
||||
['name' => 'Work on demand', 'icon' => 'Clock', 'color' => 'orange'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Actions\CreateDefaultCategories;
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ResetUserCategories extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'categories:reset {user : The ID or email of the user}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Delete all categories for a user and create the default ones';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
$userIdentifier = $this->argument('user');
|
||||
|
||||
$user = is_numeric($userIdentifier)
|
||||
? User::find($userIdentifier)
|
||||
: User::where('email', $userIdentifier)->first();
|
||||
|
||||
if (! $user) {
|
||||
$this->error("User not found: {$userIdentifier}");
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$this->info("Resetting categories for user: {$user->name} ({$user->email})");
|
||||
|
||||
$categoriesCount = $user->categories()->count();
|
||||
|
||||
if ($categoriesCount > 0) {
|
||||
$user->categories()->delete();
|
||||
$this->info("Deleted {$categoriesCount} existing categories.");
|
||||
} else {
|
||||
$this->info('No existing categories found.');
|
||||
}
|
||||
|
||||
$createDefaultCategories = new CreateDefaultCategories;
|
||||
$createDefaultCategories->handle($user);
|
||||
|
||||
$newCategoriesCount = $user->categories()->count();
|
||||
$this->info("Created {$newCategoriesCount} default categories.");
|
||||
|
||||
$this->newLine();
|
||||
$this->info('✓ Categories reset successfully!');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,23 +30,20 @@ class StoreCategoryRequest extends FormRequest
|
|||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'cyan',
|
||||
'emerald',
|
||||
'gray',
|
||||
'green',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'orange',
|
||||
'pink',
|
||||
'rose',
|
||||
'purple',
|
||||
'red',
|
||||
'slate',
|
||||
'teal',
|
||||
'yellow',
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -30,23 +30,20 @@ class UpdateCategoryRequest extends FormRequest
|
|||
'required',
|
||||
'string',
|
||||
Rule::in([
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'cyan',
|
||||
'emerald',
|
||||
'gray',
|
||||
'green',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'orange',
|
||||
'pink',
|
||||
'rose',
|
||||
'purple',
|
||||
'red',
|
||||
'slate',
|
||||
'teal',
|
||||
'yellow',
|
||||
]),
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ class CategoryFactory extends Factory
|
|||
{
|
||||
return [
|
||||
'name' => fake()->randomElement(['Food', 'Transport', 'Entertainment', 'Shopping', 'Healthcare', 'Utilities', 'Travel', 'Education']),
|
||||
'icon' => fake()->randomElement(['🍔', '🚗', '🎮', '🛒', '💊', '💡', '✈️', '📚']),
|
||||
'color' => fake()->hexColor(),
|
||||
'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']),
|
||||
'user_id' => User::factory(),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Actions\CreateDefaultCategories;
|
||||
use App\Models\Category;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
|
@ -19,16 +20,7 @@ class CategorySeeder extends Seeder
|
|||
return;
|
||||
}
|
||||
|
||||
$categories = [
|
||||
['name' => 'Food & Dining', 'icon' => '🍔', 'color' => '#FF6B6B'],
|
||||
['name' => 'Transportation', 'icon' => '🚗', 'color' => '#4ECDC4'],
|
||||
['name' => 'Entertainment', 'icon' => '🎮', 'color' => '#95E1D3'],
|
||||
['name' => 'Shopping', 'icon' => '🛒', 'color' => '#F38181'],
|
||||
['name' => 'Healthcare', 'icon' => '💊', 'color' => '#AA96DA'],
|
||||
['name' => 'Utilities', 'icon' => '💡', 'color' => '#FCBAD3'],
|
||||
['name' => 'Travel', 'icon' => '✈️', 'color' => '#FFFFD2'],
|
||||
['name' => 'Education', 'icon' => '📚', 'color' => '#A8D8EA'],
|
||||
];
|
||||
$categories = CreateDefaultCategories::getDefaultCategories();
|
||||
|
||||
foreach ($users as $user) {
|
||||
foreach ($categories as $category) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { categorySyncService } from '@/services/category-sync';
|
||||
import {
|
||||
CATEGORY_COLORS,
|
||||
CATEGORY_ICONS,
|
||||
|
|
@ -48,7 +49,8 @@ export function CreateCategoryDialog({
|
|||
</DialogHeader>
|
||||
<Form
|
||||
{...store.form()}
|
||||
onSuccess={() => {
|
||||
onSuccess={async () => {
|
||||
await categorySyncService.sync();
|
||||
setOpen(false);
|
||||
onSuccess?.();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { categorySyncService } from '@/services/category-sync';
|
||||
import { type Category } from '@/types/category';
|
||||
import { Form } from '@inertiajs/react';
|
||||
|
||||
|
|
@ -36,7 +37,8 @@ export function DeleteCategoryDialog({
|
|||
</DialogHeader>
|
||||
<Form
|
||||
{...destroy.form.delete(category.id)}
|
||||
onSuccess={() => {
|
||||
onSuccess={async () => {
|
||||
await categorySyncService.delete(category.id);
|
||||
onOpenChange(false);
|
||||
onSuccess?.();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { categorySyncService } from '@/services/category-sync';
|
||||
import {
|
||||
CATEGORY_COLORS,
|
||||
CATEGORY_ICONS,
|
||||
|
|
@ -50,7 +51,8 @@ export function EditCategoryDialog({
|
|||
</DialogHeader>
|
||||
<Form
|
||||
{...update.form.patch(category.id)}
|
||||
onSuccess={() => {
|
||||
onSuccess={async () => {
|
||||
await categorySyncService.sync();
|
||||
onOpenChange(false);
|
||||
onSuccess?.();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
flexRender,
|
||||
getCoreRowModel,
|
||||
getFilteredRowModel,
|
||||
getPaginationRowModel,
|
||||
getSortedRowModel,
|
||||
SortingState,
|
||||
useReactTable,
|
||||
|
|
@ -140,7 +139,13 @@ export default function Categories() {
|
|||
header: 'Color',
|
||||
cell: ({ row }) => {
|
||||
const color = row.getValue('color') as Category['color'];
|
||||
if (!color) {
|
||||
return null;
|
||||
}
|
||||
const colorClasses = getCategoryColorClasses(color);
|
||||
if (!colorClasses) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Badge
|
||||
className={`${colorClasses.bg} ${colorClasses.text} text-[10px] tracking-widest`}
|
||||
|
|
@ -163,7 +168,6 @@ export default function Categories() {
|
|||
onSortingChange: setSorting,
|
||||
onColumnFiltersChange: setColumnFilters,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
getFilteredRowModel: getFilteredRowModel(),
|
||||
onColumnVisibilityChange: setColumnVisibility,
|
||||
|
|
@ -273,29 +277,11 @@ export default function Categories() {
|
|||
</Table>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end space-x-2">
|
||||
<div className="flex-1 text-sm text-muted-foreground">
|
||||
<div className="flex items-center justify-end">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{table.getFilteredRowModel().rows.length}{' '}
|
||||
category(ies) total.
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.previousPage()}
|
||||
disabled={!table.getCanPreviousPage()}
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => table.nextPage()}
|
||||
disabled={!table.getCanNextPage()}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,62 @@
|
|||
export const CATEGORY_ICONS = [
|
||||
'ShoppingCart',
|
||||
'Home',
|
||||
'Car',
|
||||
'Utensils',
|
||||
'Film',
|
||||
'Heart',
|
||||
'Zap',
|
||||
'ShoppingBag',
|
||||
'Plane',
|
||||
'Coffee',
|
||||
'Smartphone',
|
||||
'Book',
|
||||
'DollarSign',
|
||||
'Gift',
|
||||
'AlertCircle',
|
||||
'AlertTriangle',
|
||||
'ArrowDownCircle',
|
||||
'ArrowLeftRight',
|
||||
'ArrowUpCircle',
|
||||
'Baby',
|
||||
'Banknote',
|
||||
'Briefcase',
|
||||
'GamepadIcon',
|
||||
'Music',
|
||||
'Shirt',
|
||||
'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',
|
||||
] as const;
|
||||
|
||||
export type CategoryIcon = (typeof CATEGORY_ICONS)[number];
|
||||
|
||||
export const CATEGORY_COLORS = [
|
||||
'red',
|
||||
'orange',
|
||||
'amber',
|
||||
'yellow',
|
||||
'lime',
|
||||
'green',
|
||||
'emerald',
|
||||
'teal',
|
||||
'cyan',
|
||||
'sky',
|
||||
'blue',
|
||||
'cyan',
|
||||
'emerald',
|
||||
'gray',
|
||||
'green',
|
||||
'indigo',
|
||||
'violet',
|
||||
'purple',
|
||||
'fuchsia',
|
||||
'orange',
|
||||
'pink',
|
||||
'rose',
|
||||
'purple',
|
||||
'red',
|
||||
'slate',
|
||||
'teal',
|
||||
'yellow',
|
||||
] as const;
|
||||
|
||||
export type CategoryColor = (typeof CATEGORY_COLORS)[number];
|
||||
|
|
@ -55,73 +73,61 @@ export function getCategoryColorClasses(color: CategoryColor): {
|
|||
text: string;
|
||||
} {
|
||||
const colorMap: Record<CategoryColor, { bg: string; text: string }> = {
|
||||
red: {
|
||||
bg: 'bg-red-100 dark:bg-red-700',
|
||||
text: 'text-red-700 dark:text-red-100',
|
||||
},
|
||||
orange: {
|
||||
bg: 'bg-orange-100 dark:bg-orange-700',
|
||||
text: 'text-orange-700 dark:text-orange-100',
|
||||
},
|
||||
amber: {
|
||||
bg: 'bg-amber-100 dark:bg-amber-700',
|
||||
text: 'text-amber-700 dark:text-amber-100',
|
||||
},
|
||||
yellow: {
|
||||
bg: 'bg-yellow-100 dark:bg-yellow-700',
|
||||
text: 'text-yellow-700 dark:text-yellow-100',
|
||||
},
|
||||
lime: {
|
||||
bg: 'bg-lime-100 dark:bg-lime-700',
|
||||
text: 'text-lime-700 dark:text-lime-100',
|
||||
},
|
||||
green: {
|
||||
bg: 'bg-green-100 dark:bg-green-700',
|
||||
text: 'text-green-700 dark:text-green-100',
|
||||
},
|
||||
emerald: {
|
||||
bg: 'bg-emerald-100 dark:bg-emerald-700',
|
||||
text: 'text-emerald-700 dark:text-emerald-100',
|
||||
},
|
||||
teal: {
|
||||
bg: 'bg-teal-100 dark:bg-teal-700',
|
||||
text: 'text-teal-700 dark:text-teal-100',
|
||||
},
|
||||
cyan: {
|
||||
bg: 'bg-cyan-100 dark:bg-cyan-700',
|
||||
text: 'text-cyan-700 dark:text-cyan-100',
|
||||
},
|
||||
sky: {
|
||||
bg: 'bg-sky-100 dark:bg-sky-700',
|
||||
text: 'text-sky-700 dark:text-sky-100',
|
||||
},
|
||||
blue: {
|
||||
bg: 'bg-blue-100 dark:bg-blue-700',
|
||||
text: 'text-blue-700 dark:text-blue-100',
|
||||
},
|
||||
cyan: {
|
||||
bg: 'bg-cyan-100 dark:bg-cyan-700',
|
||||
text: 'text-cyan-700 dark:text-cyan-100',
|
||||
},
|
||||
emerald: {
|
||||
bg: 'bg-emerald-100 dark:bg-emerald-700',
|
||||
text: 'text-emerald-700 dark:text-emerald-100',
|
||||
},
|
||||
gray: {
|
||||
bg: 'bg-gray-100 dark:bg-gray-700',
|
||||
text: 'text-gray-700 dark:text-gray-100',
|
||||
},
|
||||
green: {
|
||||
bg: 'bg-green-100 dark:bg-green-700',
|
||||
text: 'text-green-700 dark:text-green-100',
|
||||
},
|
||||
indigo: {
|
||||
bg: 'bg-indigo-100 dark:bg-indigo-700',
|
||||
text: 'text-indigo-700 dark:text-indigo-100',
|
||||
},
|
||||
violet: {
|
||||
bg: 'bg-violet-100 dark:bg-violet-700',
|
||||
text: 'text-violet-700 dark:text-violet-100',
|
||||
},
|
||||
purple: {
|
||||
bg: 'bg-purple-100 dark:bg-purple-700',
|
||||
text: 'text-purple-700 dark:text-purple-100',
|
||||
},
|
||||
fuchsia: {
|
||||
bg: 'bg-fuchsia-100 dark:bg-fuchsia-700',
|
||||
text: 'text-fuchsia-700 dark:text-fuchsia-100',
|
||||
orange: {
|
||||
bg: 'bg-orange-100 dark:bg-orange-700',
|
||||
text: 'text-orange-700 dark:text-orange-100',
|
||||
},
|
||||
pink: {
|
||||
bg: 'bg-pink-100 dark:bg-pink-700',
|
||||
text: 'text-pink-700 dark:text-pink-100',
|
||||
},
|
||||
rose: {
|
||||
bg: 'bg-rose-100 dark:bg-rose-700',
|
||||
text: 'text-rose-700 dark:text-rose-100',
|
||||
purple: {
|
||||
bg: 'bg-purple-100 dark:bg-purple-700',
|
||||
text: 'text-purple-700 dark:text-purple-100',
|
||||
},
|
||||
red: {
|
||||
bg: 'bg-red-100 dark:bg-red-700',
|
||||
text: 'text-red-700 dark:text-red-100',
|
||||
},
|
||||
slate: {
|
||||
bg: 'bg-slate-100 dark:bg-slate-700',
|
||||
text: 'text-slate-700 dark:text-slate-100',
|
||||
},
|
||||
teal: {
|
||||
bg: 'bg-teal-100 dark:bg-teal-700',
|
||||
text: 'text-teal-700 dark:text-teal-100',
|
||||
},
|
||||
yellow: {
|
||||
bg: 'bg-yellow-100 dark:bg-yellow-700',
|
||||
text: 'text-yellow-700 dark:text-yellow-100',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Category;
|
||||
use App\Models\User;
|
||||
|
||||
test('command resets categories for a user by ID', function () {
|
||||
$user = User::factory()->create();
|
||||
Category::factory()->count(5)->create(['user_id' => $user->id]);
|
||||
|
||||
expect($user->categories()->count())->toBe(5);
|
||||
|
||||
$this->artisan('categories:reset', ['user' => $user->id])
|
||||
->expectsOutput("Resetting categories for user: {$user->name} ({$user->email})")
|
||||
->expectsOutput('Deleted 5 existing categories.')
|
||||
->expectsOutput('Created 49 default categories.')
|
||||
->expectsOutput('✓ Categories reset successfully!')
|
||||
->assertSuccessful();
|
||||
|
||||
expect($user->fresh()->categories()->count())->toBe(49);
|
||||
|
||||
$categoryNames = $user->categories->pluck('name')->toArray();
|
||||
expect($categoryNames)->toContain('Food', 'Transportation', 'Salary', 'Insurance');
|
||||
});
|
||||
|
||||
test('command resets categories for a user by email', function () {
|
||||
$user = User::factory()->create(['email' => 'test@example.com']);
|
||||
Category::factory()->count(3)->create(['user_id' => $user->id]);
|
||||
|
||||
expect($user->categories()->count())->toBe(3);
|
||||
|
||||
$this->artisan('categories:reset', ['user' => 'test@example.com'])
|
||||
->expectsOutput("Resetting categories for user: {$user->name} (test@example.com)")
|
||||
->expectsOutput('Deleted 3 existing categories.')
|
||||
->expectsOutput('Created 49 default categories.')
|
||||
->expectsOutput('✓ Categories reset successfully!')
|
||||
->assertSuccessful();
|
||||
|
||||
expect($user->fresh()->categories()->count())->toBe(49);
|
||||
});
|
||||
|
||||
test('command works when user has no existing categories', function () {
|
||||
$user = User::factory()->create();
|
||||
|
||||
expect($user->categories()->count())->toBe(0);
|
||||
|
||||
$this->artisan('categories:reset', ['user' => $user->id])
|
||||
->expectsOutput("Resetting categories for user: {$user->name} ({$user->email})")
|
||||
->expectsOutput('No existing categories found.')
|
||||
->expectsOutput('Created 49 default categories.')
|
||||
->expectsOutput('✓ Categories reset successfully!')
|
||||
->assertSuccessful();
|
||||
|
||||
expect($user->fresh()->categories()->count())->toBe(49);
|
||||
});
|
||||
|
||||
test('command fails when user is not found by ID', function () {
|
||||
$this->artisan('categories:reset', ['user' => 99999])
|
||||
->expectsOutput('User not found: 99999')
|
||||
->assertFailed();
|
||||
});
|
||||
|
||||
test('command fails when user is not found by email', function () {
|
||||
$this->artisan('categories:reset', ['user' => 'nonexistent@example.com'])
|
||||
->expectsOutput('User not found: nonexistent@example.com')
|
||||
->assertFailed();
|
||||
});
|
||||
|
|
@ -38,7 +38,7 @@ test('authenticated users can create a category', function () {
|
|||
|
||||
$categoryData = [
|
||||
'name' => 'Shopping',
|
||||
'icon' => 'ShoppingCart',
|
||||
'icon' => 'ShoppingBag',
|
||||
'color' => 'blue',
|
||||
];
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ test('authenticated users can create a category', function () {
|
|||
$this->assertDatabaseHas('categories', [
|
||||
'user_id' => $user->id,
|
||||
'name' => 'Shopping',
|
||||
'icon' => 'ShoppingCart',
|
||||
'icon' => 'ShoppingBag',
|
||||
'color' => 'blue',
|
||||
]);
|
||||
});
|
||||
|
|
@ -58,7 +58,7 @@ test('category name is required', function () {
|
|||
$user = User::factory()->create();
|
||||
|
||||
$response = $this->actingAs($user)->post(route('categories.store'), [
|
||||
'icon' => 'ShoppingCart',
|
||||
'icon' => 'ShoppingBag',
|
||||
'color' => 'blue',
|
||||
]);
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ test('category color is required', function () {
|
|||
|
||||
$response = $this->actingAs($user)->post(route('categories.store'), [
|
||||
'name' => 'Shopping',
|
||||
'icon' => 'ShoppingCart',
|
||||
'icon' => 'ShoppingBag',
|
||||
]);
|
||||
|
||||
$response->assertSessionHasErrors(['color']);
|
||||
|
|
@ -92,7 +92,7 @@ test('category color must be valid', function () {
|
|||
|
||||
$response = $this->actingAs($user)->post(route('categories.store'), [
|
||||
'name' => 'Shopping',
|
||||
'icon' => 'ShoppingCart',
|
||||
'icon' => 'ShoppingBag',
|
||||
'color' => 'invalid-color',
|
||||
]);
|
||||
|
||||
|
|
@ -183,8 +183,8 @@ test('default categories are created when user registers', function () {
|
|||
$service = new \App\Actions\CreateDefaultCategories;
|
||||
$service->handle($user);
|
||||
|
||||
expect($user->categories()->count())->toBe(8);
|
||||
expect($user->categories()->count())->toBe(49);
|
||||
|
||||
$categoryNames = $user->categories->pluck('name')->toArray();
|
||||
expect($categoryNames)->toContain('Groceries', 'Transportation', 'Entertainment');
|
||||
expect($categoryNames)->toContain('Food', 'Transportation', 'Salary', 'Insurance');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ it('can delete a transaction', function () {
|
|||
|
||||
$response->assertSuccessful();
|
||||
|
||||
$this->assertDatabaseMissing('transactions', [
|
||||
$this->assertSoftDeleted('transactions', [
|
||||
'id' => $transaction->id,
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue