diff --git a/app/Events/CustomColumnsUpdated.php b/app/Events/CustomColumnsUpdated.php new file mode 100644 index 00000000..88567c9c --- /dev/null +++ b/app/Events/CustomColumnsUpdated.php @@ -0,0 +1,26 @@ +contact = $contact; + $this->columns = $columns; + } +} diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php index fb612aca..a41185bb 100644 --- a/app/Http/Controllers/ContactsController.php +++ b/app/Http/Controllers/ContactsController.php @@ -3,6 +3,8 @@ namespace App\Http\Controllers; use App\Models\Contact; +use App\Models\Organization; +use App\Models\ContactCustomColumns; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; @@ -10,6 +12,7 @@ use Illuminate\Validation\Rule; use Inertia\Inertia; use Inertia\Response; +use App\Events\CustomColumnsUpdated; class ContactsController extends Controller { @@ -17,19 +20,22 @@ public function index(): Response { return Inertia::render('Contacts/Index', [ 'filters' => Request::all('search', 'trashed'), + 'organizations' => Organization::all(), + 'additionalColumns' => Auth::user()->account->contactCustomColumns()->get(), 'contacts' => Auth::user()->account->contacts() ->with('organization') ->orderByName() ->filter(Request::only('search', 'trashed')) ->paginate(10) ->withQueryString() - ->through(fn ($contact) => [ + ->through(fn($contact) => [ 'id' => $contact->id, 'name' => $contact->name, 'phone' => $contact->phone, 'city' => $contact->city, 'deleted_at' => $contact->deleted_at, 'organization' => $contact->organization ? $contact->organization->only('name') : null, + 'additional_data' => $contact->additional_data, ]), ]); } @@ -90,6 +96,9 @@ public function edit(Contact $contact): Response ->get() ->map ->only('id', 'name'), + + 'customColumns' => Auth::user()->account->contactCustomColumns()->get(), + 'customData' => $contact->contactsCustomData()->get()->map->only('column_id', 'value'), ]); } @@ -101,7 +110,7 @@ public function update(Contact $contact): RedirectResponse 'last_name' => ['required', 'max:50'], 'organization_id' => [ 'nullable', - Rule::exists('organizations', 'id')->where(fn ($query) => $query->where('account_id', Auth::user()->account_id)), + Rule::exists('organizations', 'id')->where(fn($query) => $query->where('account_id', Auth::user()->account_id)), ], 'email' => ['nullable', 'max:50', 'email'], 'phone' => ['nullable', 'max:50'], @@ -129,4 +138,71 @@ public function restore(Contact $contact): RedirectResponse return Redirect::back()->with('success', 'Contact restored.'); } + + public function importCsv(): RedirectResponse + { + $data = Request::input('data'); + + $filteredData = array_filter($data, function ($row) { + if (empty($row['name'])) { + return false; + } + + foreach ($row as $key => $value) { + if ($key !== 'name' && ($value === null || $value === 'N/A')) { + $row[$key] = null; + } + } + + return true; + }); + + foreach ($filteredData as $row) { + auth()->user()->account->contacts()->create([ + 'first_name' => explode(' ', $row['name'])[0], + 'last_name' => explode(' ', $row['name'])[1], + 'phone' => $row['phone'] ?? null, + 'city' => $row['city'] ?? null, + 'organization_id' => $row['organization_id'] ?? null, + + ]); + } + + return Redirect::route('contacts')->with('success', 'Contacts imported.'); + } + + public function addColumn() + { + $column = Request::validate([ + 'name' => ['required', 'max:50'], + 'type' => ['required', 'in:string,number,date'], + ]); + + if (Auth::user()->account->contactCustomColumns()->where('name', $column['name'])->exists()) { + return Redirect::back()->with('error', 'Column already exists.'); + } else { + + Auth::user()->account->contactCustomColumns()->create($column); + + return Redirect::back()->with('success', 'Column added.'); + } + } + + public function updateCustomColumns(Contact $contact): RedirectResponse + { + $columns = Request::input('columns'); + + foreach ($columns as $column) { + if (isset($column['value']) && $column['value'] !== null) { + $contact->contactsCustomData()->updateOrCreate( + ['column_id' => $column['id']], + ['value' => $column['value']] + ); + } + } + + event(new CustomColumnsUpdated($contact, $columns)); + + return Redirect::back()->with('success', 'Contact updated.'); + } } diff --git a/app/Http/Controllers/InvoicesController.php b/app/Http/Controllers/InvoicesController.php new file mode 100644 index 00000000..0d4124c3 --- /dev/null +++ b/app/Http/Controllers/InvoicesController.php @@ -0,0 +1,118 @@ +has('search') && $search = $request->input('search')) { + $query->where('number', 'like', "%{$search}%"); + } + + $invoices = $query->paginate(10); + + $invoices->appends(['search' => $request->input('search')]); + + return Inertia::render('Invoices/Index', [ + 'invoices' => $invoices, + ]); + } + + public function create(): Response + { + return Inertia::render('Invoices/Create', [ + 'organizations' => Auth::user()->account->organizations() + ->has('contacts') + ->get() + ->map + ->only('id', 'name'), + 'products' => Product::all(['id', 'name', 'price', 'quantity']), + ]); + } + + public function getContacts($organizationId) + { + $contacts = Contact::where('organization_id', $organizationId)->get(['id', 'first_name', 'last_name', 'phone', 'email']); + Log::info($contacts); + return response()->json($contacts); + } + + public function store(Request $request) + { + $request->validate([ + 'number' => 'required', + 'amount' => 'required', + 'organization_id' => 'required|exists:organizations,id', + 'contact_id' => 'required|exists:contacts,id', + 'added_products' => 'required|array', + 'added_products.*.id' => 'required|exists:products,id', + 'added_products.*.quantity' => 'required|integer|min:1', + ]); + + $organization = Organization::findOrFail($request->organization_id); + $contact = Contact::findOrFail($request->contact_id); + + $invoice = Invoice::create([ + 'number' => $request->number, + 'amount' => $request->amount, + 'organization_id' => $request->organization_id, + 'contact_id' => $request->contact_id, + 'organization_name' => $organization->name, + 'contact_first_name' => $contact->first_name, + 'contact_last_name' => $contact->last_name, + ]); + + foreach ($request->added_products as $productData) { + $product = Product::findOrFail($productData['id']); + $product->decrement('quantity', $productData['quantity0']); + + $item = InvoiceItem::create([ + 'invoice_id' => $invoice->id, + 'name' => $product->name, + 'price' => $product->price, + 'quantity' => $productData['quantity0'], + ]); + } + + return redirect()->route('invoices')->with('success', 'Invoice created successfully.'); + } + + public function view(Invoice $invoice): Response + { + $invoice = Invoice::with('items')->findOrFail($invoice->id); + $contact = Contact::findOrFail($invoice->contact_id); + Log::info($contact); + return Inertia::render('Invoices/View', [ + 'invoice' => $invoice, + 'contact' => $contact, + ]); + } + + public function download(Invoice $invoice) + { + $localinvoice = Invoice::with('items')->findOrFail($invoice->id); + $localcontact = Contact::findOrFail($invoice->contact_id); + + return response()->json([ + 'localinvoice' => $localinvoice, + 'localcontact' => $localcontact, + ]); + } +} diff --git a/app/Http/Controllers/OrganizationsController.php b/app/Http/Controllers/OrganizationsController.php index db3402e8..fb60267d 100644 --- a/app/Http/Controllers/OrganizationsController.php +++ b/app/Http/Controllers/OrganizationsController.php @@ -3,31 +3,49 @@ namespace App\Http\Controllers; use App\Models\Organization; +use App\Models\Settings; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; use Illuminate\Support\Facades\Request; use Inertia\Inertia; use Inertia\Response; +use Illuminate\Http\Request as HttpRequest; class OrganizationsController extends Controller { public function index(): Response { + $userId = Auth::user()->id; + $path = "org/column/{$userId}"; + $settings = Settings::where('path', $path)->first(); + $visibleColumns = $settings ? json_decode($settings->value, true) : []; + + $allColumns = Organization::allColumns(); + + $columnsToSelect = array_intersect($allColumns, $visibleColumns); + + if (!in_array('id', $columnsToSelect)) { + $columnsToSelect[] = 'id'; + } + + if (empty($visibleColumns) || empty($columnsToSelect)) { + $columnsToSelect = Organization::defaultColumns(); + } + return Inertia::render('Organizations/Index', [ + 'visibleColumns' => $visibleColumns, 'filters' => Request::all('search', 'trashed'), 'organizations' => Auth::user()->account->organizations() + ->select($columnsToSelect) ->orderBy('name') ->filter(Request::only('search', 'trashed')) - ->paginate(10) + ->paginate(25) ->withQueryString() - ->through(fn ($organization) => [ - 'id' => $organization->id, - 'name' => $organization->name, - 'phone' => $organization->phone, - 'city' => $organization->city, - 'deleted_at' => $organization->deleted_at, - ]), + ->through( + fn($organization) => + $organization->only($columnsToSelect) + ), ]); } @@ -104,4 +122,49 @@ public function restore(Organization $organization): RedirectResponse return Redirect::back()->with('success', 'Organization restored.'); } + + public function saveColumnVisibility(HttpRequest $request): RedirectResponse + { + $userId = Auth::user()->id; + $path = "org/column/{$userId}"; + + $settings = Settings::updateOrCreate( + ['path' => $path], + ['value' => json_encode($request->input('columns'))] + ); + + return Redirect::back(); + } + + public function importCsv(): RedirectResponse + { + $data = Request::input('data'); + + $filteredData = array_filter($data, function ($row) { + if (empty($row['name'])) { + return false; + } + foreach ($row as $key => $value) { + if ($key !== 'name' && ($value === null || $value === 'N/A')) { + $row[$key] = null; + } + } + return true; + }); + + foreach ($filteredData as $row) { + auth()->user()->account->organizations()->create([ + 'name' => $row['name'], + 'email' => $row['email'] ?? null, + 'phone' => $row['phone'] ?? null, + 'address' => $row['address'] ?? null, + 'city' => $row['city'] ?? null, + 'region' => $row['region'] ?? null, + 'country' => $row['country'] ?? null, + 'postal_code' => $row['postal_code'] ?? null, + ]); + } + + return Redirect::route('organizations')->with('success', 'Organizations imported.'); + } } diff --git a/app/Http/Controllers/ProductsController.php b/app/Http/Controllers/ProductsController.php new file mode 100644 index 00000000..31ba90f6 --- /dev/null +++ b/app/Http/Controllers/ProductsController.php @@ -0,0 +1,74 @@ +has('search') && $search = $request->input('search')) { + $query->where('name', 'like', "%{$search}%"); + } + + $products = $query->paginate(10); + + // Manually add query string to pagination links + $products->appends(['search' => $request->input('search')]); + + return Inertia::render('Products/Index', [ + 'products' => $products, + ]); + } + + + public function create(): Response + { + return Inertia::render('Products/Create'); + } + + public function store(Request $request): RedirectResponse + { + $validateData = $request->validate([ + 'name' => ['required', 'max:50'], + 'description' => ['required', 'max:255'], + 'price' => ['required', 'numeric'], + 'quantity' => ['required', 'numeric'], + ]); + + Product::create($validateData); + + return Redirect::route('products')->with('success', 'Product created.'); + } + + public function edit(Product $product): Response + { + return Inertia::render('Products/Edit', [ + 'product' => $product, + ]); + } + + public function update(Product $product): RedirectResponse + { + $validateData = request()->validate([ + 'name' => ['required', 'max:50'], + 'description' => ['required', 'max:255'], + 'price' => ['required', 'numeric'], + 'quantity' => ['required', 'numeric'], + ]); + + $product->update($validateData); + + return Redirect::back()->with('success', 'Product updated.'); + } +} diff --git a/app/Http/Controllers/ReportsController.php b/app/Http/Controllers/ReportsController.php index 57a978a4..32148da5 100644 --- a/app/Http/Controllers/ReportsController.php +++ b/app/Http/Controllers/ReportsController.php @@ -4,11 +4,35 @@ use Inertia\Inertia; use Inertia\Response; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Request; class ReportsController extends Controller { public function index(): Response { - return Inertia::render('Reports/Index'); + $organizations = Auth::user()->account->organizations() + ->orderBy('name') + ->paginate(9) + ->withQueryString() + ->through(function ($organization) { + $contact = $organization->contacts()->first(); + $ownerName = $contact + ? ($contact->first_name . ' ' . $contact->last_name) + : "N/A"; + + return [ + 'id' => $organization->id, + 'name' => implode(' ', array_slice(explode(' ', $organization->name), 0, 2)), + 'phone' => $organization->phone, + 'city' => $organization->city, + 'deleted_at' => $organization->deleted_at, + 'owner' => $ownerName, + ]; + }); + + return Inertia::render('Reports/Index', [ + 'organizations' => $organizations, + ]); } } diff --git a/app/Listeners/UpdateAdditionalData.php b/app/Listeners/UpdateAdditionalData.php new file mode 100644 index 00000000..b5762fd4 --- /dev/null +++ b/app/Listeners/UpdateAdditionalData.php @@ -0,0 +1,29 @@ +contact; + $columns = $event->columns; + $additionalData = json_decode($contact->additional_data, true) ?: []; + + foreach ($columns as $column) { + if (isset($column['value']) && $column['value'] !== null) { + $columnName = ContactCustomColumns::where('id', $column['id'])->value('name'); + $additionalData[$columnName] = $column['value']; + } + } + + $contact->update([ + 'additional_data' => json_encode($additionalData), + ]); + } +} diff --git a/app/Models/Account.php b/app/Models/Account.php index c3fdce6f..885dbad5 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -21,4 +21,9 @@ public function contacts(): HasMany { return $this->hasMany(Contact::class); } + + public function contactCustomColumns(): HasMany + { + return $this->hasMany(ContactCustomColumns::class); + } } diff --git a/app/Models/Contact.php b/app/Models/Contact.php index 98411794..ffb75579 100644 --- a/app/Models/Contact.php +++ b/app/Models/Contact.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Database\Eloquent\Relations\HasMany; class Contact extends Model { @@ -24,7 +25,7 @@ public function organization(): BelongsTo public function getNameAttribute() { - return $this->first_name.' '.$this->last_name; + return $this->first_name . ' ' . $this->last_name; } public function scopeOrderByName($query) @@ -36,11 +37,11 @@ public function scopeFilter($query, array $filters) { $query->when($filters['search'] ?? null, function ($query, $search) { $query->where(function ($query) use ($search) { - $query->where('first_name', 'like', '%'.$search.'%') - ->orWhere('last_name', 'like', '%'.$search.'%') - ->orWhere('email', 'like', '%'.$search.'%') + $query->where('first_name', 'like', '%' . $search . '%') + ->orWhere('last_name', 'like', '%' . $search . '%') + ->orWhere('email', 'like', '%' . $search . '%') ->orWhereHas('organization', function ($query) use ($search) { - $query->where('name', 'like', '%'.$search.'%'); + $query->where('name', 'like', '%' . $search . '%'); }); }); })->when($filters['trashed'] ?? null, function ($query, $trashed) { @@ -51,4 +52,9 @@ public function scopeFilter($query, array $filters) } }); } + + public function contactsCustomData(): HasMany + { + return $this->hasMany(ContactsCustomData::class); + } } diff --git a/app/Models/ContactCustomColumns.php b/app/Models/ContactCustomColumns.php new file mode 100644 index 00000000..d2b216c3 --- /dev/null +++ b/app/Models/ContactCustomColumns.php @@ -0,0 +1,20 @@ +hasMany(ContactsCustomData::class); + } +} diff --git a/app/Models/ContactsCustomData.php b/app/Models/ContactsCustomData.php new file mode 100644 index 00000000..549b3613 --- /dev/null +++ b/app/Models/ContactsCustomData.php @@ -0,0 +1,13 @@ +where($field ?? 'id', $value)->with('items')->firstOrFail(); + } + public function items() + { + return $this->hasMany(InvoiceItem::class); + } +} diff --git a/app/Models/InvoiceItem.php b/app/Models/InvoiceItem.php new file mode 100644 index 00000000..9b840d5f --- /dev/null +++ b/app/Models/InvoiceItem.php @@ -0,0 +1,13 @@ +where($field ?? 'id', $value)->withTrashed()->firstOrFail(); } + public static function allColumns(): array + { + return [ + 'name', + 'phone', + 'city', + 'email', + 'address', + 'region', + 'country', + 'postal_code', + 'deleted_at' + ]; + } + + public static function defaultColumns(): array + { + return [ + 'name', + 'phone', + 'city', + 'email' + ]; + } + public function contacts(): HasMany { return $this->hasMany(Contact::class); @@ -25,7 +50,7 @@ public function contacts(): HasMany public function scopeFilter($query, array $filters) { $query->when($filters['search'] ?? null, function ($query, $search) { - $query->where('name', 'like', '%'.$search.'%'); + $query->where('name', 'like', '%' . $search . '%'); })->when($filters['trashed'] ?? null, function ($query, $trashed) { if ($trashed === 'with') { $query->withTrashed(); diff --git a/app/Models/Product.php b/app/Models/Product.php new file mode 100644 index 00000000..e0077388 --- /dev/null +++ b/app/Models/Product.php @@ -0,0 +1,18 @@ +where($field ?? 'id', $value)->firstOrFail(); + } +} diff --git a/app/Models/Settings.php b/app/Models/Settings.php new file mode 100644 index 00000000..f916cbda --- /dev/null +++ b/app/Models/Settings.php @@ -0,0 +1,14 @@ + [ + \App\Listeners\UpdateAdditionalData::class, + ], + ]; + public function register(): void + { + // + } + + /** + * Bootstrap services. + */ + public function boot(): void + { + // + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index 38b258d1..86f5297a 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -2,4 +2,5 @@ return [ App\Providers\AppServiceProvider::class, + App\Providers\EventServiceProvider::class, ]; diff --git a/database/migrations/2024_08_07_045450_create_products_table.php b/database/migrations/2024_08_07_045450_create_products_table.php new file mode 100644 index 00000000..110f7f03 --- /dev/null +++ b/database/migrations/2024_08_07_045450_create_products_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('name'); + $table->text('description'); + $table->decimal('price', 8, 2); + $table->integer('quantity'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('products'); + } +}; diff --git a/database/migrations/2024_08_07_071222_create_invoices_table.php b/database/migrations/2024_08_07_071222_create_invoices_table.php new file mode 100644 index 00000000..92477952 --- /dev/null +++ b/database/migrations/2024_08_07_071222_create_invoices_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('number'); + $table->decimal('amount', 15, 2); + $table->integer('organization_id'); + $table->integer('contact_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('invoices'); + } +}; diff --git a/database/migrations/2024_08_07_102758_add_ogranization_and_contact_to_invoices_table.php b/database/migrations/2024_08_07_102758_add_ogranization_and_contact_to_invoices_table.php new file mode 100644 index 00000000..bbd9888c --- /dev/null +++ b/database/migrations/2024_08_07_102758_add_ogranization_and_contact_to_invoices_table.php @@ -0,0 +1,30 @@ +string('organization_name')->after('amount'); + $table->string('contact_first_name')->after('organization_name'); + $table->string('contact_last_name')->after('contact_first_name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('invoices', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2024_08_07_115238_create_test_table.php b/database/migrations/2024_08_07_115238_create_test_table.php new file mode 100644 index 00000000..9a66efec --- /dev/null +++ b/database/migrations/2024_08_07_115238_create_test_table.php @@ -0,0 +1,30 @@ +id(); + $table->integer('organization_id')->unsigned(); + $table->string('name'); + $table->foreign('organization_id')->references('id')->on('organizations'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('test'); + } +}; diff --git a/database/migrations/2024_08_08_051355_create_invoice_items_table.php b/database/migrations/2024_08_08_051355_create_invoice_items_table.php new file mode 100644 index 00000000..01452fd0 --- /dev/null +++ b/database/migrations/2024_08_08_051355_create_invoice_items_table.php @@ -0,0 +1,31 @@ +id(); + $table->foreignId('invoice_id')->constrained('invoices')->onDelete('cascade'); + $table->string('name'); + $table->decimal('price', 8, 2); + $table->integer('quantity'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('invoice_items'); + } +}; diff --git a/database/migrations/2024_08_12_090302_create_settings_table.php b/database/migrations/2024_08_12_090302_create_settings_table.php new file mode 100644 index 00000000..30cd88c8 --- /dev/null +++ b/database/migrations/2024_08_12_090302_create_settings_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('path')->unique(); + $table->mediumText('value')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('settings'); + } +}; diff --git a/database/migrations/2024_08_19_074449_add_additional_data_to_contacts_table.php b/database/migrations/2024_08_19_074449_add_additional_data_to_contacts_table.php new file mode 100644 index 00000000..b7ac49c0 --- /dev/null +++ b/database/migrations/2024_08_19_074449_add_additional_data_to_contacts_table.php @@ -0,0 +1,28 @@ +json('additional_data')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('contacts', function (Blueprint $table) { + // + }); + } +}; diff --git a/database/migrations/2024_08_19_074659_create_contact_custom_columns_table.php b/database/migrations/2024_08_19_074659_create_contact_custom_columns_table.php new file mode 100644 index 00000000..814644ad --- /dev/null +++ b/database/migrations/2024_08_19_074659_create_contact_custom_columns_table.php @@ -0,0 +1,31 @@ +id(); + $table->integer('account_id')->unsigned(); + $table->foreign('account_id')->references('id')->on('accounts'); + $table->string('name'); + $table->string('type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contact_custom_columns'); + } +}; diff --git a/database/migrations/2024_08_19_075119_create_contacts_custom_data_table.php b/database/migrations/2024_08_19_075119_create_contacts_custom_data_table.php new file mode 100644 index 00000000..8f5b8cf5 --- /dev/null +++ b/database/migrations/2024_08_19_075119_create_contacts_custom_data_table.php @@ -0,0 +1,31 @@ +id(); + $table->integer('contact_id')->unsigned(); + $table->foreign('contact_id')->references('id')->on('contacts'); + $table->foreignId('column_id')->constrained('contact_custom_columns')->onDelete('cascade'); + $table->string('value'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('contacts_custom_data'); + } +}; diff --git a/package-lock.json b/package-lock.json index 490a24aa..739d4dd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,18 @@ "packages": { "": { "name": "pingcrm", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/vue-fontawesome": "^3.0.8", + "axios": "^1.7.3", + "html2pdf.js": "^0.10.2", + "papaparse": "^5.4.1", + "vue-draggable-next": "^2.2.1", + "vuedraggable": "^4.1.0", + "ziggy-js": "^2.3.0" + }, "devDependencies": { "@inertiajs/vue3": "^1.0.15", "@popperjs/core": "^2.11.0", @@ -50,7 +62,6 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.1.tgz", "integrity": "sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==", - "dev": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -58,6 +69,17 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -482,6 +504,56 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz", + "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/vue-fontawesome": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.8.tgz", + "integrity": "sha512-yyHHAj4G8pQIDfaIsMvQpwKMboIZtcHTUvPqXjOHyldh1O1vZfH4W03VDPv5RvI9P6DLTzJQlmVgj9wCf7c2Fw==", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "vue": ">= 3.0.0 < 4" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -620,8 +692,7 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -889,6 +960,12 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "optional": true + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -912,7 +989,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz", "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==", - "dev": true, "dependencies": { "@babel/parser": "^7.23.9", "@vue/shared": "3.4.21", @@ -925,7 +1001,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz", "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==", - "dev": true, "dependencies": { "@vue/compiler-core": "3.4.21", "@vue/shared": "3.4.21" @@ -935,7 +1010,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz", "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==", - "dev": true, "dependencies": { "@babel/parser": "^7.23.9", "@vue/compiler-core": "3.4.21", @@ -952,7 +1026,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz", "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==", - "dev": true, "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/shared": "3.4.21" @@ -962,7 +1035,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz", "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==", - "dev": true, "dependencies": { "@vue/shared": "3.4.21" } @@ -971,7 +1043,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz", "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==", - "dev": true, "dependencies": { "@vue/reactivity": "3.4.21", "@vue/shared": "3.4.21" @@ -981,7 +1052,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz", "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==", - "dev": true, "dependencies": { "@vue/runtime-core": "3.4.21", "@vue/shared": "3.4.21", @@ -992,7 +1062,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz", "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==", - "dev": true, "dependencies": { "@vue/compiler-ssr": "3.4.21", "@vue/shared": "3.4.21" @@ -1004,8 +1073,7 @@ "node_modules/@vue/shared": { "version": "3.4.21", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz", - "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==", - "dev": true + "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==" }, "node_modules/acorn": { "version": "8.11.3", @@ -1102,8 +1170,18 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } }, "node_modules/autoprefixer": { "version": "10.4.19", @@ -1143,10 +1221,9 @@ } }, "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dev": true, + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1159,6 +1236,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1232,6 +1317,17 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/btoa": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", + "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", + "bin": { + "btoa": "bin/btoa.js" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -1289,6 +1385,31 @@ } ] }, + "node_modules/canvg": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz", + "integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/canvg/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "optional": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1363,7 +1484,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1386,6 +1506,17 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/core-js": { + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.0.tgz", + "integrity": "sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==", + "hasInstallScript": true, + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1400,6 +1531,14 @@ "node": ">= 8" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1415,8 +1554,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/debug": { "version": "4.3.4", @@ -1471,7 +1609,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -1500,6 +1637,12 @@ "node": ">=6.0.0" } }, + "node_modules/dompurify": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.6.tgz", + "integrity": "sha512-zUTaUBO8pY4+iJMPE1B9XlO2tXVYIcEA4SNGtvDELzTSCQO7RzH+j7S180BmhmJId78lqGU2z19vgVx2Sxs/PQ==", + "optional": true + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1522,7 +1665,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, "engines": { "node": ">=0.12" }, @@ -1551,6 +1693,11 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -1793,8 +1940,7 @@ "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/esutils": { "version": "2.0.3", @@ -1860,6 +2006,11 @@ "reusify": "^1.0.4" } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1925,7 +2076,6 @@ "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, "funding": [ { "type": "individual", @@ -1961,7 +2111,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -2154,6 +2303,28 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2pdf.js": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.10.2.tgz", + "integrity": "sha512-WyHVeMb18Bp7vYTmBv1GVsThH//K7SRfHdSdhHPkl4JvyQarNQXnailkYn0QUbRRmnN5rdbbmSIGEsPZtzPy2Q==", + "dependencies": { + "es6-promise": "^4.2.5", + "html2canvas": "^1.0.0", + "jspdf": "^2.3.1" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2340,6 +2511,23 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jspdf": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.1.tgz", + "integrity": "sha512-hXObxz7ZqoyhxET78+XR34Xu2qFGrJJ2I2bE5w4SM8eFaFEkW2xcGRVUss360fYelwRSid/jT078kbNvmoW0QA==", + "dependencies": { + "@babel/runtime": "^7.14.0", + "atob": "^2.1.2", + "btoa": "^1.2.1", + "fflate": "^0.4.8" + }, + "optionalDependencies": { + "canvg": "^3.0.6", + "core-js": "^3.6.0", + "dompurify": "^2.2.0", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2451,7 +2639,6 @@ "version": "0.30.8", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, @@ -2485,7 +2672,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -2494,7 +2680,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -2544,7 +2729,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, "funding": [ { "type": "github", @@ -2689,6 +2873,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2759,11 +2948,16 @@ "node": "14 || >=16.14" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "optional": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2799,7 +2993,6 @@ "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2984,8 +3177,7 @@ "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { "version": "2.3.1", @@ -3031,6 +3223,15 @@ } ] }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -3052,6 +3253,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -3088,6 +3294,15 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3243,15 +3458,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sortablejs": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.2.tgz", + "integrity": "sha512-FJF5jgdfvoKn1MAKSdGs33bIqLi3LmsgVTliuX6iITj834F+JRQZN90Z93yql8h0K2t0RwDPBmxwlbZfDcxNZA==", + "peer": true + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -3446,6 +3675,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/tailwind-classes-sorter": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tailwind-classes-sorter/-/tailwind-classes-sorter-0.2.5.tgz", @@ -3509,6 +3747,14 @@ "postcss": "^8.0.0" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3624,6 +3870,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -3702,7 +3956,6 @@ "version": "3.4.21", "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz", "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==", - "dev": true, "dependencies": { "@vue/compiler-dom": "3.4.21", "@vue/compiler-sfc": "3.4.21", @@ -3719,6 +3972,15 @@ } } }, + "node_modules/vue-draggable-next": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vue-draggable-next/-/vue-draggable-next-2.2.1.tgz", + "integrity": "sha512-EAMS1IRHF0kZO0o5PMOinsQsXIqsrKT1hKmbICxG3UEtn7zLFkLxlAtajcCcUTisNvQ6TtCB5COjD9a1raNADw==", + "peerDependencies": { + "sortablejs": "^1.14.0", + "vue": "^3.2.2" + } + }, "node_modules/vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", @@ -3743,6 +4005,22 @@ "eslint": ">=6.0.0" } }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, + "node_modules/vuedraggable/node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3887,6 +4165,25 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/ziggy-js": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ziggy-js/-/ziggy-js-2.3.0.tgz", + "integrity": "sha512-g2m/WgC/7KkqZzYFe87LmSVqVbZ6fGkx0RCKR+0mrUvn4xpou3h4s0IIxl7TYsyBspEFebGspdf01JFo1gaCZA==", + "dependencies": { + "qs": "~6.9.7" + } + }, + "node_modules/ziggy-js/node_modules/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } } } } diff --git a/package.json b/package.json index 69da4712..10f1661c 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,17 @@ "uuid": "^8.3.2", "vite": "^5.2.7", "vue": "^3.2.27" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/vue-fontawesome": "^3.0.8", + "axios": "^1.7.3", + "html2pdf.js": "^0.10.2", + "papaparse": "^5.4.1", + "vue-draggable-next": "^2.2.1", + "vuedraggable": "^4.1.0", + "ziggy-js": "^2.3.0" } } diff --git a/resources/css/buttons.css b/resources/css/buttons.css index 9527d728..b52b1c1f 100644 --- a/resources/css/buttons.css +++ b/resources/css/buttons.css @@ -2,6 +2,22 @@ @apply px-6 py-3 rounded bg-indigo-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-orange-400 focus:bg-orange-400; } +.btn-indigo-light { + @apply px-6 py-3 rounded bg-indigo-400 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-orange-300 focus:bg-orange-300; + } + +.btn-green { + @apply px-6 py-3 rounded bg-green-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-green-400 focus:bg-green-400; + } + + .btn-red { + @apply px-6 py-3 rounded bg-red-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-red-400 focus:bg-red-400; + } + + .btn-yellow { + @apply px-6 py-3 rounded bg-yellow-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-yellow-400 focus:bg-yellow-400; + } + .btn-spinner, .btn-spinner:after { border-radius: 50%; diff --git a/resources/js/Pages/Contacts/Edit.vue b/resources/js/Pages/Contacts/Edit.vue index fb1019d2..a02f8146 100644 --- a/resources/js/Pages/Contacts/Edit.vue +++ b/resources/js/Pages/Contacts/Edit.vue @@ -1,36 +1,53 @@