From 9ce912baa2c37dc84d180920c0d3290fb9c8b86b Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Wed, 10 Dec 2025 13:32:44 +0100 Subject: [PATCH 1/6] Initial work on SettingLayout #174 --- resources/js/layouts/settings/Layout.vue | 58 ++++++++++++++---------- resources/js/pages/Achievements.vue | 4 +- resources/js/pages/settings/Profile.vue | 4 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/resources/js/layouts/settings/Layout.vue b/resources/js/layouts/settings/Layout.vue index d939872..41df118 100644 --- a/resources/js/layouts/settings/Layout.vue +++ b/resources/js/layouts/settings/Layout.vue @@ -2,13 +2,15 @@ import Heading from '@/components/Heading.vue'; import { Button } from '@/components/ui/crm-button'; import { Separator } from '@/components/ui/separator'; -import { toUrl, urlIsActive } from '@/lib/utils'; +import { cn, toUrl, urlIsActive } from '@/lib/utils'; import { edit as editAppearance } from '@/routes/appearance'; import { edit as editPassword } from '@/routes/password'; import { edit as editProfile } from '@/routes/profile'; import { show } from '@/routes/two-factor'; import { type NavItem } from '@/types'; import { Link } from '@inertiajs/vue3'; +import AppLayout from '@/layouts/AppLayout.vue' +import AppHeader from '@/components/AppHeader.vue' const sidebarNavItems: NavItem[] = [ { @@ -33,33 +35,39 @@ const currentPath = typeof window !== undefined ? window.location.pathname : ''; diff --git a/resources/js/pages/Achievements.vue b/resources/js/pages/Achievements.vue index b69da46..1a6da9b 100644 --- a/resources/js/pages/Achievements.vue +++ b/resources/js/pages/Achievements.vue @@ -1,4 +1,5 @@ @@ -7,8 +8,7 @@ import AppLayout from '@/layouts/AppLayout.vue';
-

Kategorie

-

3 von 10 freigeschaltet

+
diff --git a/resources/js/pages/settings/Profile.vue b/resources/js/pages/settings/Profile.vue index 5227a83..2cc81c1 100644 --- a/resources/js/pages/settings/Profile.vue +++ b/resources/js/pages/settings/Profile.vue @@ -24,7 +24,7 @@ const user = page.props.auth.user; From 41788d6033f6d85d568f846642d724bedf4d2147 Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Tue, 20 Jan 2026 12:21:31 +0100 Subject: [PATCH 2/6] Fix: division by zero errors --- app/Http/Controllers/InvoiceController.php | 10 +++++----- routes/api.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php index af65c72..6a6d6f9 100644 --- a/app/Http/Controllers/InvoiceController.php +++ b/app/Http/Controllers/InvoiceController.php @@ -170,15 +170,15 @@ public function salesStatistics() 'year' => $currentYear, 'totalRevenue' => $totalRevenue, 'paid' => $paid, - 'paidPercent' => round(($paid / $totalRevenue) * 100, 2), + 'paidPercent' => ($totalRevenue == 0) ? 0 : round(($paid / $totalRevenue) * 100, 2), 'draft' => $draft, - 'draftPercent' => round(($draft / $totalRevenue) * 100, 2), + 'draftPercent' => ($totalRevenue == 0) ? 0 : round(($draft / $totalRevenue) * 100, 2), 'issued' => $issued, - 'issuedPercent' => round(($issued / $totalRevenue) * 100, 2), + 'issuedPercent' => ($totalRevenue == 0) ? 0 : round(($issued / $totalRevenue) * 100, 2), 'due' => $due, - 'duePercent' => round(($due / $totalRevenue) * 100, 2), + 'duePercent' => ($totalRevenue == 0) ? 0 : round(($due / $totalRevenue) * 100, 2), 'reminded' => $reminded, - 'remindedPercent' => round(($reminded / $totalRevenue) * 100, 2), + 'remindedPercent' => ($totalRevenue == 0) ? 0 : round(($reminded / $totalRevenue) * 100, 2), ]; // Daten in camelCase umwandeln diff --git a/routes/api.php b/routes/api.php index 63b4d19..f39b68f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -47,7 +47,7 @@ Route::get('/invoices/{id}/remind', [InvoiceController::class, 'remind']); Route::get('/lineitems/{invoiceId}', [LineItemController::class, 'index']); -Route::post('/lineitems/import/{invoiceId}', [LineItemController::class, 'importFromCsv']); +Route::post('/lineitems/import', [LineItemController::class, 'importFromCsv']); Route::get('/offers/{id}/confirm', function ($id) { // $offer = offerController::single($id); From be719e66a59dab15bce2c180a36376670afef044 Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Tue, 20 Jan 2026 15:25:06 +0100 Subject: [PATCH 3/6] [Feature] Import LineItems from from CSV and products --- app/Http/Controllers/LineItemController.php | 40 ++-- .../js/components/documents/LineItemTable.vue | 184 ++++++++++++++++-- .../js/components/ui/command/Command.vue | 86 ++++++++ .../components/ui/command/CommandDialog.vue | 21 ++ .../js/components/ui/command/CommandEmpty.vue | 23 +++ .../js/components/ui/command/CommandGroup.vue | 44 +++++ .../js/components/ui/command/CommandInput.vue | 35 ++++ .../js/components/ui/command/CommandItem.vue | 75 +++++++ .../js/components/ui/command/CommandList.vue | 21 ++ .../ui/command/CommandSeparator.vue | 20 ++ .../components/ui/command/CommandShortcut.vue | 14 ++ resources/js/components/ui/command/index.ts | 25 +++ resources/js/components/ui/dialog/Dialog.vue | 6 +- .../js/components/ui/dialog/DialogClose.vue | 3 +- .../js/components/ui/dialog/DialogContent.vue | 32 +-- .../ui/dialog/DialogDescription.vue | 16 +- .../js/components/ui/dialog/DialogFooter.vue | 6 +- .../js/components/ui/dialog/DialogHeader.vue | 6 +- .../js/components/ui/dialog/DialogOverlay.vue | 16 +- .../ui/dialog/DialogScrollContent.vue | 26 +-- .../js/components/ui/dialog/DialogTitle.vue | 16 +- .../js/components/ui/dialog/DialogTrigger.vue | 3 +- resources/js/components/ui/dialog/index.ts | 20 +- 23 files changed, 618 insertions(+), 120 deletions(-) create mode 100644 resources/js/components/ui/command/Command.vue create mode 100644 resources/js/components/ui/command/CommandDialog.vue create mode 100644 resources/js/components/ui/command/CommandEmpty.vue create mode 100644 resources/js/components/ui/command/CommandGroup.vue create mode 100644 resources/js/components/ui/command/CommandInput.vue create mode 100644 resources/js/components/ui/command/CommandItem.vue create mode 100644 resources/js/components/ui/command/CommandList.vue create mode 100644 resources/js/components/ui/command/CommandSeparator.vue create mode 100644 resources/js/components/ui/command/CommandShortcut.vue create mode 100644 resources/js/components/ui/command/index.ts diff --git a/app/Http/Controllers/LineItemController.php b/app/Http/Controllers/LineItemController.php index e1ee58f..6f93efb 100644 --- a/app/Http/Controllers/LineItemController.php +++ b/app/Http/Controllers/LineItemController.php @@ -57,10 +57,9 @@ public function index($invoiceId) * ;"Hosting";"Annual hosting";1.500,50;"lump sum";1.200,00 * * @param Request $request The HTTP request with the CSV file - * @param int $invoiceId The ID of the invoice for which the items will be imported * @return \Illuminate\Http\JsonResponse A JSON response with the imported items or an error message */ - public function importFromCsv(Request $request, $invoiceId) + public function importFromCsv(Request $request) { $request->validate([ 'csv' => 'required|file|mimes:csv,txt' @@ -133,16 +132,22 @@ public function importFromCsv(Request $request, $invoiceId) } $lineItems[] = [ - 'invoice_id' => $invoiceId, + 'id' => 0, + 'invoice_id' => 0, 'position' => $position, 'is_section' => false, 'title' => $itemData['title'], 'description' => $itemData['description'] ?? '', 'quantity' => (float)$quantity, 'unit_id' => $unit->id, + 'unit' => [ + 'id' => $unit->id, + 'name' => $unit->name, + 'symbol' => $unit->symbol, + 'created_at' => $unit->created_at, + 'updated_at' => $unit->updated_at, + ], 'price' => (float)$price, - 'created_at' => now(), - 'updated_at' => now() ]; } @@ -150,28 +155,9 @@ public function importFromCsv(Request $request, $invoiceId) return response()->json(['message' => 'No valid items found in the CSV file'], 400); } - // Delete existing items for this invoice - LineItem::where('invoice_id', $invoiceId)->delete(); - - // Insert new items - LineItem::insert($lineItems); - - // Return the newly created items - $items = LineItem::with('unit') - ->where('invoice_id', $invoiceId) - ->orderBy('position', 'asc') - ->get(); - - return $items->map(function ($item) { - $itemArray = $item->toArray(); - - if ($item->unit) { - $itemArray['unit_name'] = $item->unit->name; - $itemArray['unit_symbol'] = $item->unit->symbol; - } - - return ApiDataTransformer::snakeToCamel($itemArray); - }); + return response()->json( + ApiDataTransformer::snakeToCamel($lineItems) + ); } /** diff --git a/resources/js/components/documents/LineItemTable.vue b/resources/js/components/documents/LineItemTable.vue index 76be6a7..a9c8e38 100644 --- a/resources/js/components/documents/LineItemTable.vue +++ b/resources/js/components/documents/LineItemTable.vue @@ -2,20 +2,28 @@ + + diff --git a/resources/js/components/ui/command/CommandDialog.vue b/resources/js/components/ui/command/CommandDialog.vue new file mode 100644 index 0000000..bccd25c --- /dev/null +++ b/resources/js/components/ui/command/CommandDialog.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/components/ui/command/CommandEmpty.vue b/resources/js/components/ui/command/CommandEmpty.vue new file mode 100644 index 0000000..d4d156f --- /dev/null +++ b/resources/js/components/ui/command/CommandEmpty.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/js/components/ui/command/CommandGroup.vue b/resources/js/components/ui/command/CommandGroup.vue new file mode 100644 index 0000000..99df3a4 --- /dev/null +++ b/resources/js/components/ui/command/CommandGroup.vue @@ -0,0 +1,44 @@ + + + diff --git a/resources/js/components/ui/command/CommandInput.vue b/resources/js/components/ui/command/CommandInput.vue new file mode 100644 index 0000000..4f7e84a --- /dev/null +++ b/resources/js/components/ui/command/CommandInput.vue @@ -0,0 +1,35 @@ + + + diff --git a/resources/js/components/ui/command/CommandItem.vue b/resources/js/components/ui/command/CommandItem.vue new file mode 100644 index 0000000..c6088e2 --- /dev/null +++ b/resources/js/components/ui/command/CommandItem.vue @@ -0,0 +1,75 @@ + + + diff --git a/resources/js/components/ui/command/CommandList.vue b/resources/js/components/ui/command/CommandList.vue new file mode 100644 index 0000000..d94b969 --- /dev/null +++ b/resources/js/components/ui/command/CommandList.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/components/ui/command/CommandSeparator.vue b/resources/js/components/ui/command/CommandSeparator.vue new file mode 100644 index 0000000..799319b --- /dev/null +++ b/resources/js/components/ui/command/CommandSeparator.vue @@ -0,0 +1,20 @@ + + + diff --git a/resources/js/components/ui/command/CommandShortcut.vue b/resources/js/components/ui/command/CommandShortcut.vue new file mode 100644 index 0000000..6d95d73 --- /dev/null +++ b/resources/js/components/ui/command/CommandShortcut.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/components/ui/command/index.ts b/resources/js/components/ui/command/index.ts new file mode 100644 index 0000000..af18933 --- /dev/null +++ b/resources/js/components/ui/command/index.ts @@ -0,0 +1,25 @@ +import type { Ref } from "vue" +import { createContext } from "reka-ui" + +export { default as Command } from "./Command.vue" +export { default as CommandDialog } from "./CommandDialog.vue" +export { default as CommandEmpty } from "./CommandEmpty.vue" +export { default as CommandGroup } from "./CommandGroup.vue" +export { default as CommandInput } from "./CommandInput.vue" +export { default as CommandItem } from "./CommandItem.vue" +export { default as CommandList } from "./CommandList.vue" +export { default as CommandSeparator } from "./CommandSeparator.vue" +export { default as CommandShortcut } from "./CommandShortcut.vue" + +export const [useCommand, provideCommandContext] = createContext<{ + allItems: Ref> + allGroups: Ref>> + filterState: { + search: string + filtered: { count: number, items: Map, groups: Set } + } +}>("Command") + +export const [useCommandGroup, provideCommandGroupContext] = createContext<{ + id?: string +}>("CommandGroup") diff --git a/resources/js/components/ui/dialog/Dialog.vue b/resources/js/components/ui/dialog/Dialog.vue index bb8bf72..ade5260 100644 --- a/resources/js/components/ui/dialog/Dialog.vue +++ b/resources/js/components/ui/dialog/Dialog.vue @@ -1,5 +1,6 @@ diff --git a/resources/js/components/ui/dialog/DialogContent.vue b/resources/js/components/ui/dialog/DialogContent.vue index 1226459..7f86b47 100644 --- a/resources/js/components/ui/dialog/DialogContent.vue +++ b/resources/js/components/ui/dialog/DialogContent.vue @@ -1,25 +1,27 @@ @@ -29,7 +31,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits) diff --git a/resources/js/components/ui/dialog/DialogDescription.vue b/resources/js/components/ui/dialog/DialogDescription.vue index dc5be12..f52e655 100644 --- a/resources/js/components/ui/dialog/DialogDescription.vue +++ b/resources/js/components/ui/dialog/DialogDescription.vue @@ -1,15 +1,13 @@ diff --git a/resources/js/components/ui/dialog/DialogFooter.vue b/resources/js/components/ui/dialog/DialogFooter.vue index c3b4b55..0a936e6 100644 --- a/resources/js/components/ui/dialog/DialogFooter.vue +++ b/resources/js/components/ui/dialog/DialogFooter.vue @@ -1,8 +1,8 @@