From be719e66a59dab15bce2c180a36376670afef044 Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Tue, 20 Jan 2026 15:25:06 +0100 Subject: [PATCH] [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 @@