diff --git a/app/Enums/FinanceTypeEnum.php b/app/Enums/FinanceTypeEnum.php new file mode 100644 index 0000000..3593595 --- /dev/null +++ b/app/Enums/FinanceTypeEnum.php @@ -0,0 +1,27 @@ + __('Income'), + FinanceTypeEnum::EXPENSE => __('Expense'), + }; + } +} diff --git a/app/Http/Controllers/ActiveAssetController.php b/app/Http/Controllers/ActiveAssetController.php new file mode 100644 index 0000000..64de480 --- /dev/null +++ b/app/Http/Controllers/ActiveAssetController.php @@ -0,0 +1,283 @@ +query('page'); + $search = $request->query('search'); + + $assets = Asset::with(['category', 'brand', 'latestHistory']) + ->when($search, function (Builder $query, ?string $search) { + $query->where('name', 'LIKE', "%{$search}%") + ->orWhere('code', 'LIKE', "%{$search}%") + ->orWhere('type', 'LIKE', "%{$search}%") + ->orWhere('purchase_date', 'LIKE', "%{$search}%") + ->orWhereHas('category', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }) + ->orWhereHas('brand', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }) + ->orWhereHas('latestHistory', function (Builder $query) use ($search) { + $query->where('date_from', 'LIKE', "%{$search}%"); + }); + }) + ->latest() + ->paginate() + ->withQueryString(); + + if ($page > $assets->lastPage() && $page > 1) { + abort(404); + } + + return view('active-assets.index', compact('assets', 'search')); + } + + /** + * Show the form for creating a new resource. + */ + public function create(): View + { + $categories = Category::select(['name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + $brands = Brand::orderBy('name') + ->pluck('name') + ->toArray(); + + $distributors = Distributor::orderBy('name') + ->pluck('name') + ->toArray(); + + return view('active-assets.create', compact('categories', 'brands', 'distributors')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreAssetRequest $request): RedirectResponse + { + DB::transaction(function () use ($request) { + $category = Category::firstOrCreate([ + 'name' => $request->input('category'), + ]); + + $brand = Brand::firstOrCreate([ + 'name' => $request->input('brand'), + ]); + + $distributor = Distributor::firstOrCreate([ + 'name' => $request->input('distributor'), + ]); + + $asset = new Asset; + $asset->name = $request->input('name'); + $asset->code = $request->input('code'); + $asset->category_id = $category->getAttribute('id'); + $asset->brand_id = $brand->getAttribute('id'); + $asset->type = $request->input('type'); + $asset->manufacturer = $request->input('manufacturer'); + $asset->serial_number = $request->input('serial_number'); + $asset->production_year = $request->input('production_year'); + $asset->description = $request->input('description'); + $asset->purchase_date = $request->input('purchase_date'); + $asset->distributor_id = $distributor->getAttribute('id'); + $asset->invoice_number = $request->input('invoice_number'); + $asset->qty = $request->input('qty'); + $asset->unit_price = $request->input('unit_price'); + $asset->notes = $request->input('notes'); + + if ($request->hasFile('photo')) { + $asset->photo = Storage::disk('public')->put( + 'photos', + $request->file('photo'), + ); + } + + $asset->qr_code = 'qr-codes/'.str()->random(40).'.png'; + $asset->save(); + + Storage::disk('public')->put( + $asset->qr_code, + QrCode::format('png') + ->margin(1) + ->size(512) + ->generate("http://localhost:8080/{$asset->id}"), + ); + + if ($request->hasFile('attachments')) { + $attachments = $request->file('attachments'); + + foreach ($attachments as $filename) { + $attachment = new AssetAttachment; + $attachment->filename = Storage::disk('public')->putFileAs( + 'attachments', + $filename, + time().'_'.$filename->getClientOriginalName(), + ); + $attachment->asset_id = $asset->getAttribute('id'); + $attachment->save(); + } + } + }); + + return redirect()->route('active-assets.index') + ->with('message', 'The asset has been created.'); + } + + /** + * Display the specified resource. + */ + public function show(Asset $asset): View + { + return view('active-assets.show', compact('asset')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Asset $asset): View + { + $categories = Category::select(['name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + $brands = Brand::orderBy('name') + ->pluck('name') + ->toArray(); + + $distributors = Distributor::orderBy('name') + ->pluck('name') + ->toArray(); + + return view('active-assets.edit', compact('asset', 'categories', 'brands', 'distributors')); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateAssetRequest $request, Asset $asset): RedirectResponse + { + DB::transaction(function () use ($request, $asset) { + $category = Category::firstOrCreate([ + 'name' => $request->input('category'), + ]); + + $brand = Brand::firstOrCreate([ + 'name' => $request->input('brand'), + ]); + + $distributor = Distributor::firstOrCreate([ + 'name' => $request->input('distributor'), + ]); + + $asset->name = $request->input('name'); + $asset->code = $request->input('code'); + $asset->category_id = $category->getAttribute('id'); + $asset->brand_id = $brand->getAttribute('id'); + $asset->type = $request->input('type'); + $asset->manufacturer = $request->input('manufacturer'); + $asset->serial_number = $request->input('serial_number'); + $asset->production_year = $request->input('production_year'); + $asset->description = $request->input('description'); + $asset->purchase_date = $request->input('purchase_date'); + $asset->distributor_id = $distributor->getAttribute('id'); + $asset->invoice_number = $request->input('invoice_number'); + $asset->qty = $request->input('qty'); + $asset->unit_price = $request->input('unit_price'); + $asset->notes = $request->input('notes'); + + if ($request->hasFile('photo')) { + if ($asset->photo && Storage::disk('public')->exists($asset->photo)) { + Storage::disk('public')->delete($asset->photo); + } + + $asset->photo = Storage::disk('public')->put( + 'photos', + $request->file('photo'), + ); + } + + $asset->save(); + + if ($request->hasFile('attachments')) { + foreach ($asset->assetAttachments as $attachment) { + if ($attachment->filename && Storage::disk('public')->exists($attachment->filename)) { + Storage::disk('public')->delete($attachment->filename); + } + + $attachment->delete(); + } + + $attachments = $request->file('attachments'); + + foreach ($attachments as $filename) { + $attachment = new AssetAttachment; + $attachment->filename = Storage::disk('public')->putFileAs( + 'attachments', + $filename, + time().'_'.$filename->getClientOriginalName(), + ); + $attachment->asset_id = $asset->getAttribute('id'); + $attachment->save(); + } + } + }); + + return redirect()->route('active-assets.index') + ->with('message', 'The asset has been updated.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Asset $asset): RedirectResponse + { + DB::transaction(function () use ($asset) { + foreach ($asset->assetAttachments as $attachment) { + if ($attachment->filename && Storage::disk('public')->exists($attachment->filename)) { + Storage::disk('public')->delete($attachment->filename); + } + + $attachment->delete(); + } + + if ($asset->photo && Storage::disk('public')->exists($asset->photo)) { + Storage::disk('public')->delete($asset->photo); + } + + if ($asset->qr_code && Storage::disk('public')->exists($asset->qr_code)) { + Storage::disk('public')->delete($asset->qr_code); + } + + $asset->delete(); + }); + + return redirect()->route('active-assets.index') + ->with('message', 'The asset has been deleted.'); + } +} diff --git a/app/Http/Controllers/AssetFinanceController.php b/app/Http/Controllers/AssetFinanceController.php new file mode 100644 index 0000000..92eb4cb --- /dev/null +++ b/app/Http/Controllers/AssetFinanceController.php @@ -0,0 +1,121 @@ +query('page'); + $search = $request->query('search'); + + $finances = AssetFinance::with(['asset']) + ->when($search, function (Builder $query, ?string $search) { + $query->where('type', 'LIKE', "%{$search}%") + ->orWhere('date', 'LIKE', "%{$search}%") + ->orWhere('amount', 'LIKE', "%{$search}%") + ->orWhereHas('asset', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }); + }) + ->latest() + ->paginate() + ->withQueryString(); + + if ($page > $finances->lastPage() && $page > 1) { + abort(404); + } + + return view('asset-finances.index', compact('finances', 'search')); + } + + /** + * Show the form for creating a new resource. + */ + public function create(): View + { + $assets = Asset::select(['id', 'name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + return view('asset-finances.create', compact('assets')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreAssetFinanceRequest $request): RedirectResponse + { + $finance = new AssetFinance; + $finance->asset_id = $request->input('asset_id'); + $finance->type = $request->input('type'); + $finance->date = $request->input('date'); + $finance->amount = $request->input('amount'); + $finance->notes = $request->input('notes'); + $finance->save(); + + return redirect()->route('asset-finances.index') + ->with('message', 'The finance has been created.'); + } + + /** + * Display the specified resource. + */ + public function show(AssetFinance $finance): View + { + return view('asset-finances.show', compact('finance')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(AssetFinance $finance): View + { + $assets = Asset::select(['id', 'name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + return view('asset-finances.edit', compact('finance', 'assets')); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateAssetFinanceRequest $request, AssetFinance $finance): RedirectResponse + { + $finance->asset_id = $request->input('asset_id'); + $finance->type = $request->input('type'); + $finance->date = $request->input('date'); + $finance->amount = $request->input('amount'); + $finance->notes = $request->input('notes'); + $finance->save(); + + return redirect()->route('asset-finances.index') + ->with('message', 'The finance has been updated.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(AssetFinance $finance): RedirectResponse + { + $finance->delete(); + + return redirect()->route('asset-finances.index') + ->with('message', 'The finance has been deleted.'); + } +} diff --git a/app/Http/Controllers/AssetHistoryController.php b/app/Http/Controllers/AssetHistoryController.php new file mode 100644 index 0000000..eb09fb1 --- /dev/null +++ b/app/Http/Controllers/AssetHistoryController.php @@ -0,0 +1,173 @@ +query('page'); + $search = $request->query('search'); + + $histories = AssetHistory::with(['asset', 'responsiblePerson', 'location']) + ->when($search, function (Builder $query, ?string $search) { + $query->where('date_from', 'LIKE', "%{$search}%") + ->orWhere('qty', 'LIKE', "%{$search}%") + ->orWhere('condition_percentage', 'LIKE', "%{$search}%") + ->orWhere('completeness_percentage', 'LIKE', "%{$search}%") + ->orWhereHas('asset', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }) + ->orWhereHas('responsiblePerson', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }) + ->orWhereHas('location', function (Builder $query) use ($search) { + $query->where('name', 'LIKE', "%{$search}%"); + }); + }) + ->latest() + ->paginate() + ->withQueryString(); + + if ($page > $histories->lastPage() && $page > 1) { + abort(404); + } + + return view('asset-histories.index', compact('histories', 'search')); + } + + /** + * Show the form for creating a new resource. + */ + public function create(): View + { + $assets = Asset::select(['id', 'name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + $persons = ResponsiblePerson::orderBy('name') + ->pluck('name') + ->toArray(); + + $locations = Location::orderBy('name') + ->pluck('name') + ->toArray(); + + return view('asset-histories.create', compact('assets', 'persons', 'locations')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreAssetHistoryRequest $request): RedirectResponse + { + DB::transaction(function () use ($request) { + $person = ResponsiblePerson::firstOrCreate([ + 'name' => $request->input('responsible_person'), + ]); + + $location = Location::firstOrCreate([ + 'name' => $request->input('location'), + ]); + + $history = new AssetHistory; + $history->asset_id = $request->input('asset_id'); + $history->date_from = $request->input('date_from'); + $history->responsible_person_id = $person->getAttribute('id'); + $history->location_id = $location->getAttribute('id'); + $history->qty = $request->input('qty'); + $history->condition_percentage = $request->input('condition_percentage'); + $history->completeness_percentage = $request->input('completeness_percentage'); + $history->notes = $request->input('notes'); + $history->save(); + }); + + return redirect()->route('asset-histories.index') + ->with('message', 'The history has been created.'); + } + + /** + * Display the specified resource. + */ + public function show(AssetHistory $history): View + { + return view('asset-histories.show', compact('history')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(AssetHistory $history): View + { + $assets = Asset::select(['id', 'name']) + ->where('name', '!=', 'None') + ->orderBy('name') + ->get(); + + $persons = ResponsiblePerson::orderBy('name') + ->pluck('name') + ->toArray(); + + $locations = Location::orderBy('name') + ->pluck('name') + ->toArray(); + + return view('asset-histories.edit', compact('history', 'assets', 'persons', 'locations')); + } + + /** + * Update the specified resource in storage. + */ + public function update(UpdateAssetHistoryRequest $request, AssetHistory $history): RedirectResponse + { + DB::transaction(function () use ($request, $history) { + $person = ResponsiblePerson::firstOrCreate([ + 'name' => $request->input('responsible_person'), + ]); + + $location = Location::firstOrCreate([ + 'name' => $request->input('location'), + ]); + + $history->asset_id = $request->input('asset_id'); + $history->date_from = $request->input('date_from'); + $history->responsible_person_id = $person->getAttribute('id'); + $history->location_id = $location->getAttribute('id'); + $history->qty = $request->input('qty'); + $history->condition_percentage = $request->input('condition_percentage'); + $history->completeness_percentage = $request->input('completeness_percentage'); + $history->notes = $request->input('notes'); + $history->save(); + }); + + return redirect()->route('asset-histories.index') + ->with('message', 'The history has been updated.'); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(AssetHistory $history): RedirectResponse + { + $history->delete(); + + return redirect()->route('asset-histories.index') + ->with('message', 'The history has been deleted.'); + } +} diff --git a/app/Http/Requests/StoreAssetFinanceRequest.php b/app/Http/Requests/StoreAssetFinanceRequest.php new file mode 100644 index 0000000..a1c97fa --- /dev/null +++ b/app/Http/Requests/StoreAssetFinanceRequest.php @@ -0,0 +1,69 @@ +|string> + */ + public function rules(): array + { + return [ + 'asset_id' => [ + 'required', + 'uuid', + 'exists:assets,id', + ], + 'type' => [ + 'required', + 'string', + Rule::Enum(FinanceTypeEnum::class), + ], + 'date' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'amount' => [ + 'required', + 'numeric', + 'min:1', + 'max:1000000000', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'asset_id' => 'asset name', + ]; + } +} diff --git a/app/Http/Requests/StoreAssetHistoryRequest.php b/app/Http/Requests/StoreAssetHistoryRequest.php new file mode 100644 index 0000000..8f629c4 --- /dev/null +++ b/app/Http/Requests/StoreAssetHistoryRequest.php @@ -0,0 +1,86 @@ +|string> + */ + public function rules(): array + { + return [ + 'asset_id' => [ + 'required', + 'uuid', + 'exists:assets,id', + ], + 'date_from' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'responsible_person' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'location' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'qty' => [ + 'required', + 'integer', + 'min:1', + 'max:1000', + ], + 'condition_percentage' => [ + 'required', + 'integer', + 'min:1', + 'max:100', + ], + 'completeness_percentage' => [ + 'required', + 'integer', + 'min:1', + 'max:100', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'asset_id' => 'asset name', + ]; + } +} diff --git a/app/Http/Requests/StoreAssetRequest.php b/app/Http/Requests/StoreAssetRequest.php new file mode 100644 index 0000000..c529e7e --- /dev/null +++ b/app/Http/Requests/StoreAssetRequest.php @@ -0,0 +1,134 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'code' => [ + 'required', + 'string', + 'min:3', + 'max:100', + 'unique:assets,code', + ], + 'category' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'photo' => [ + 'nullable', + 'image', + 'max:1024', + 'mimes:jpg,jpeg,png', + ], + 'brand' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'type' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'manufacturer' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'serial_number' => [ + 'required', + 'string', + 'min:3', + 'max:100', + 'unique:assets,serial_number', + ], + 'production_year' => [ + 'required', + 'integer', + 'digits:4', + ], + 'description' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + 'purchase_date' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'distributor' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'invoice_number' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'qty' => [ + 'required', + 'integer', + 'min:1', + 'max:1000', + ], + 'unit_price' => [ + 'required', + 'numeric', + 'min:1', + 'max:1000000000', + ], + 'attachments' => [ + 'nullable', + 'array', + 'max:5', + ], + 'attachments.*' => [ + 'nullable', + 'file', + 'max:1024', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } +} diff --git a/app/Http/Requests/UpdateAssetFinanceRequest.php b/app/Http/Requests/UpdateAssetFinanceRequest.php new file mode 100644 index 0000000..3a3af8f --- /dev/null +++ b/app/Http/Requests/UpdateAssetFinanceRequest.php @@ -0,0 +1,69 @@ +|string> + */ + public function rules(): array + { + return [ + 'asset_id' => [ + 'required', + 'uuid', + 'exists:assets,id', + ], + 'type' => [ + 'required', + 'string', + Rule::Enum(FinanceTypeEnum::class), + ], + 'date' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'amount' => [ + 'required', + 'numeric', + 'min:1', + 'max:1000000000', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'asset_id' => 'asset name', + ]; + } +} diff --git a/app/Http/Requests/UpdateAssetHistoryRequest.php b/app/Http/Requests/UpdateAssetHistoryRequest.php new file mode 100644 index 0000000..93bf89b --- /dev/null +++ b/app/Http/Requests/UpdateAssetHistoryRequest.php @@ -0,0 +1,86 @@ +|string> + */ + public function rules(): array + { + return [ + 'asset_id' => [ + 'required', + 'uuid', + 'exists:assets,id', + ], + 'date_from' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'responsible_person' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'location' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'qty' => [ + 'required', + 'integer', + 'min:1', + 'max:1000', + ], + 'condition_percentage' => [ + 'required', + 'integer', + 'min:1', + 'max:100', + ], + 'completeness_percentage' => [ + 'required', + 'integer', + 'min:1', + 'max:100', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'asset_id' => 'asset name', + ]; + } +} diff --git a/app/Http/Requests/UpdateAssetRequest.php b/app/Http/Requests/UpdateAssetRequest.php new file mode 100644 index 0000000..7984d18 --- /dev/null +++ b/app/Http/Requests/UpdateAssetRequest.php @@ -0,0 +1,135 @@ +|string> + */ + public function rules(): array + { + return [ + 'name' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'code' => [ + 'required', + 'string', + 'min:3', + 'max:100', + Rule::unique('assets', 'code')->ignore($this->asset), + ], + 'category' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'photo' => [ + 'nullable', + 'image', + 'max:1024', + 'mimes:jpg,jpeg,png', + ], + 'brand' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'type' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'manufacturer' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'serial_number' => [ + 'required', + 'string', + 'min:3', + 'max:100', + Rule::unique('assets', 'serial_number')->ignore($this->asset), + ], + 'production_year' => [ + 'required', + 'integer', + 'digits:4', + ], + 'description' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + 'purchase_date' => [ + 'required', + 'date', + 'date_format:Y-m-d', + 'before_or_equal:today', + ], + 'distributor' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'invoice_number' => [ + 'required', + 'string', + 'min:3', + 'max:100', + ], + 'qty' => [ + 'required', + 'integer', + 'min:1', + 'max:1000', + ], + 'unit_price' => [ + 'required', + 'numeric', + 'min:1', + 'max:1000000000', + ], + 'attachments' => [ + 'nullable', + 'array', + 'max:5', + ], + 'attachments.*' => [ + 'nullable', + 'file', + 'max:1024', + ], + 'notes' => [ + 'nullable', + 'string', + 'min:3', + 'max:255', + ], + ]; + } +} diff --git a/app/Models/Asset.php b/app/Models/Asset.php new file mode 100644 index 0000000..6a1d833 --- /dev/null +++ b/app/Models/Asset.php @@ -0,0 +1,162 @@ + + */ + protected $fillable = [ + 'name', + 'code', + 'category_id', + 'brand_id', + 'type', + 'manufacturer', + 'serial_number', + 'production_year', + 'description', + 'purchase_date', + 'distributor_id', + 'invoice_number', + 'qty', + 'unit_price', + 'photo', + 'notes', + ]; + + /** + * Interact with the asset's qrcode url. + */ + protected function qrCodeUrl(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attrs) { + return Storage::url($attrs['qr_code']); + }, + ); + } + + /** + * Interact with the asset's photo url. + */ + protected function photoUrl(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attrs) { + if ($attrs['photo'] && Storage::disk('public')->exists($attrs['photo'])) { + return Storage::url($attrs['photo']); + } + + return Vite::asset('resources/images/photo.png'); + } + ); + } + + /** + * Get the category that owns the asset. + */ + public function category(): BelongsTo + { + return $this->belongsTo(Category::class, 'category_id', 'id'); + } + + /** + * Get the brand that owns the asset. + */ + public function brand(): BelongsTo + { + return $this->belongsTo(Brand::class, 'brand_id', 'id'); + } + + /** + * Get the distributor that owns the asset. + */ + public function distributor(): BelongsTo + { + return $this->belongsTo(Distributor::class, 'distributor_id', 'id'); + } + + /** + * Get the asset attachments for the asset. + */ + public function assetAttachments(): HasMany + { + return $this->hasMany(AssetAttachment::class, 'asset_id', 'id'); + } + + /** + * Get the asset histories for the asset. + */ + public function assetHistories(): HasMany + { + return $this->hasMany(AssetHistory::class, 'asset_id', 'id'); + } + + /** + * Get the histories most recent asset. + */ + public function latestHistory(): HasOne + { + return $this->hasOne(AssetHistory::class)->latestOfMany(); + } + + /** + * Get the asset finances for the asset. + */ + public function assetFinances(): HasMany + { + return $this->hasMany(AssetFinance::class, 'asset_id', 'id'); + } +} diff --git a/app/Models/AssetAttachment.php b/app/Models/AssetAttachment.php new file mode 100644 index 0000000..f73e70a --- /dev/null +++ b/app/Models/AssetAttachment.php @@ -0,0 +1,80 @@ + + */ + protected $fillable = [ + 'filename', + 'asset_id', + ]; + + /** + * Interact with the asset attachment's filename url. + */ + protected function filenameUrl(): Attribute + { + return Attribute::make( + get: function (mixed $value, array $attrs) { + return Storage::url($attrs['filename']); + } + ); + } + + /** + * Get the asset that owns the attachment. + */ + public function asset(): BelongsTo + { + return $this->belongsTo(Asset::class, 'asset_id', 'id'); + } +} diff --git a/app/Models/AssetFinance.php b/app/Models/AssetFinance.php new file mode 100644 index 0000000..7e9deb4 --- /dev/null +++ b/app/Models/AssetFinance.php @@ -0,0 +1,79 @@ + + */ + protected $fillable = [ + 'asset_id', + 'type', + 'date', + 'amount', + 'notes', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'type' => FinanceTypeEnum::class, + ]; + + /** + * Get the asset that owns the asset finance. + */ + public function asset(): BelongsTo + { + return $this->belongsTo(Asset::class, 'asset_id', 'id'); + } +} diff --git a/app/Models/AssetHistory.php b/app/Models/AssetHistory.php new file mode 100644 index 0000000..7c29058 --- /dev/null +++ b/app/Models/AssetHistory.php @@ -0,0 +1,88 @@ + + */ + protected $fillable = [ + 'asset_id', + 'date_from', + 'responsible_person', + 'location', + 'qty', + 'condition_percentage', + 'completeness_percentage', + 'notes', + ]; + + /** + * Get the responsible person that owns the asset history. + */ + public function responsiblePerson(): BelongsTo + { + return $this->belongsTo(ResponsiblePerson::class, 'responsible_person_id', 'id'); + } + + /** + * Get the location that owns the asset history. + */ + public function location(): BelongsTo + { + return $this->belongsTo(Location::class, 'location_id', 'id'); + } + + /** + * Get the asset that owns the asset history. + */ + public function asset(): BelongsTo + { + return $this->belongsTo(Asset::class, 'asset_id', 'id'); + } +} diff --git a/app/Models/Location.php b/app/Models/Location.php index 87c993b..991e97e 100644 --- a/app/Models/Location.php +++ b/app/Models/Location.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Concerns\HasUuids; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\HasMany; class Location extends Model { @@ -54,4 +55,12 @@ class Location extends Model 'name', 'description', ]; + + /** + * Get the asset histories for the location. + */ + public function assetHistories(): HasMany + { + return $this->hasMany(AssetHistory::class, 'location_id', 'id'); + } } diff --git a/app/Models/ResponsiblePerson.php b/app/Models/ResponsiblePerson.php index dac9f60..8cb12a3 100644 --- a/app/Models/ResponsiblePerson.php +++ b/app/Models/ResponsiblePerson.php @@ -59,4 +59,12 @@ class ResponsiblePerson extends Model 'email', 'description', ]; + + /** + * Get the asset histories for the responsible person. + */ + public function assetHistories(): HasMany + { + return $this->hasMany(AssetHistory::class, 'responsible_person_id', 'id'); + } } diff --git a/app/Observers/AssetObserver.php b/app/Observers/AssetObserver.php new file mode 100644 index 0000000..851fbad --- /dev/null +++ b/app/Observers/AssetObserver.php @@ -0,0 +1,16 @@ +total_price = $asset->qty * $asset->unit_price; + } +} diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 2d65aac..651e0b4 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -2,6 +2,8 @@ namespace App\Providers; +use App\Models\Asset; +use App\Observers\AssetObserver; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; @@ -25,7 +27,7 @@ class EventServiceProvider extends ServiceProvider */ public function boot(): void { - // + Asset::observe(AssetObserver::class); } /** diff --git a/composer.json b/composer.json index 5b40f87..f0b8b7f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "guzzlehttp/guzzle": "^7.2", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2", - "laravel/tinker": "^2.8" + "laravel/tinker": "^2.8", + "simplesoftwareio/simple-qrcode": "~1" }, "require-dev": { "fakerphp/faker": "^1.9.1", diff --git a/composer.lock b/composer.lock index 3c0925e..569d6be 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,58 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bfe12996eeecb6fdc8713a9fd9d431f8", + "content-hash": "f2e794b028bb56bdc0dc4248ee9e2f1b", "packages": [ + { + "name": "bacon/bacon-qr-code", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "448ee9929aece0e86f0e2b926e636f9b53d03ce1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/448ee9929aece0e86f0e2b926e636f9b53d03ce1", + "reference": "448ee9929aece0e86f0e2b926e636f9b53d03ce1", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8" + }, + "suggest": { + "ext-gd": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-0": { + "BaconQrCode": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "http://www.dasprids.de", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/master" + }, + "time": "2016-11-26T13:57:10+00:00" + }, { "name": "brick/math", "version": "0.12.1", @@ -1383,16 +1433,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.4", + "version": "v1.3.5", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81" + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/61b87392d986dc49ad5ef64e75b1ff5fee24ef81", - "reference": "61b87392d986dc49ad5ef64e75b1ff5fee24ef81", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", "shasum": "" }, "require": { @@ -1440,20 +1490,20 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-08-02T07:48:17+00:00" + "time": "2024-09-23T13:33:08+00:00" }, { "name": "laravel/tinker", - "version": "v2.9.0", + "version": "v2.10.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", - "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "url": "https://api.github.com/repos/laravel/tinker/zipball/ba4d51eb56de7711b3a37d63aa0643e99a339ae5", + "reference": "ba4d51eb56de7711b3a37d63aa0643e99a339ae5", "shasum": "" }, "require": { @@ -1504,9 +1554,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.9.0" + "source": "https://github.com/laravel/tinker/tree/v2.10.0" }, - "time": "2024-01-04T16:10:04+00:00" + "time": "2024-09-23T13:32:56+00:00" }, { "name": "league/commonmark", @@ -1698,16 +1748,16 @@ }, { "name": "league/flysystem", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c" + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", - "reference": "e611adab2b1ae2e3072fa72d62c62f52c2bf1f0c", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", + "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", "shasum": "" }, "require": { @@ -1775,22 +1825,22 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" }, - "time": "2024-05-22T10:09:12+00:00" + "time": "2024-09-29T11:59:11+00:00" }, { "name": "league/flysystem-local", - "version": "3.28.0", + "version": "3.29.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40" + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/13f22ea8be526ea58c2ddff9e158ef7c296e4f40", - "reference": "13f22ea8be526ea58c2ddff9e158ef7c296e4f40", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/e0e8d52ce4b2ed154148453d321e97c8e931bd27", + "reference": "e0e8d52ce4b2ed154148453d321e97c8e931bd27", "shasum": "" }, "require": { @@ -1824,9 +1874,9 @@ "local" ], "support": { - "source": "https://github.com/thephpleague/flysystem-local/tree/3.28.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.29.0" }, - "time": "2024-05-06T20:05:52+00:00" + "time": "2024-08-09T21:24:39+00:00" }, { "name": "league/mime-type-detection", @@ -2242,16 +2292,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.2.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb" + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", - "reference": "23c79fbbfb725fb92af9bcf41065c8e9a0d49ddb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", "shasum": "" }, "require": { @@ -2294,9 +2344,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.2.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" }, - "time": "2024-09-15T16:40:33+00:00" + "time": "2024-09-29T13:56:26+00:00" }, { "name": "nunomaduro/termwind", @@ -3175,6 +3225,61 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "simplesoftwareio/simple-qrcode", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/SimpleSoftwareIO/simple-qrcode.git", + "reference": "0d8fbf73f7adc166ec5aabbf898b7327f6c69600" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SimpleSoftwareIO/simple-qrcode/zipball/0d8fbf73f7adc166ec5aabbf898b7327f6c69600", + "reference": "0d8fbf73f7adc166ec5aabbf898b7327f6c69600", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "1.0.*", + "ext-gd": "*", + "illuminate/support": ">=4.2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "0.9.*", + "phpunit/phpunit": "~5" + }, + "type": "library", + "autoload": { + "psr-0": { + "SimpleSoftwareIO\\QrCode\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Simple Software LLC", + "email": "support@simplesoftware.io" + } + ], + "description": "Simple QrCode is a QR code generator made for Laravel.", + "homepage": "http://www.simplesoftware.io", + "keywords": [ + "Simple", + "generator", + "laravel", + "qrcode", + "wrapper" + ], + "support": { + "issues": "https://github.com/SimpleSoftwareIO/simple-qrcode/issues", + "source": "https://github.com/SimpleSoftwareIO/simple-qrcode/tree/1.5.1" + }, + "time": "2016-12-06T01:39:52+00:00" + }, { "name": "symfony/console", "version": "v6.4.12", @@ -5670,26 +5775,26 @@ }, { "name": "filp/whoops", - "version": "2.15.4", + "version": "2.16.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546" + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/a139776fa3f5985a50b509f2a02ff0f709d2a546", - "reference": "a139776fa3f5985a50b509f2a02ff0f709d2a546", + "url": "https://api.github.com/repos/filp/whoops/zipball/befcdc0e5dce67252aa6322d82424be928214fa2", + "reference": "befcdc0e5dce67252aa6322d82424be928214fa2", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "psr/log": "^1.0.1 || ^2.0 || ^3.0" }, "require-dev": { - "mockery/mockery": "^0.9 || ^1.0", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", - "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" }, "suggest": { "symfony/var-dumper": "Pretty print complex values better with var-dumper available", @@ -5729,7 +5834,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.15.4" + "source": "https://github.com/filp/whoops/tree/2.16.0" }, "funding": [ { @@ -5737,7 +5842,7 @@ "type": "github" } ], - "time": "2023-11-03T12:00:00+00:00" + "time": "2024-09-25T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -5792,16 +5897,16 @@ }, { "name": "laravel/pint", - "version": "v1.17.3", + "version": "v1.18.1", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "9d77be916e145864f10788bb94531d03e1f7b482" + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/9d77be916e145864f10788bb94531d03e1f7b482", - "reference": "9d77be916e145864f10788bb94531d03e1f7b482", + "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", "shasum": "" }, "require": { @@ -5854,20 +5959,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-03T15:00:28+00:00" + "time": "2024-09-24T17:22:50+00:00" }, { "name": "laravel/sail", - "version": "v1.32.0", + "version": "v1.34.0", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "4a7e41d280861ca7e35710cea011a07669b4003b" + "reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/4a7e41d280861ca7e35710cea011a07669b4003b", - "reference": "4a7e41d280861ca7e35710cea011a07669b4003b", + "url": "https://api.github.com/repos/laravel/sail/zipball/511e9c95b0f3ee778dc9e11e242bcd2af8e002cd", + "reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd", "shasum": "" }, "require": { @@ -5917,7 +6022,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-09-11T20:14:29+00:00" + "time": "2024-09-27T14:58:09+00:00" }, { "name": "mockery/mockery", diff --git a/config/app.php b/config/app.php index bca112f..5f5dc03 100644 --- a/config/app.php +++ b/config/app.php @@ -185,6 +185,7 @@ /* * Package Service Providers... */ + SimpleSoftwareIO\QrCode\QrCodeServiceProvider::class, /* * Application Service Providers... @@ -209,7 +210,7 @@ */ 'aliases' => Facade::defaultAliases()->merge([ - // 'ExampleClass' => App\Example\ExampleClass::class, + 'QrCode' => SimpleSoftwareIO\QrCode\Facades\QrCode::class, ])->toArray(), ]; diff --git a/database/factories/AssetAttachmentFactory.php b/database/factories/AssetAttachmentFactory.php new file mode 100644 index 0000000..a219a59 --- /dev/null +++ b/database/factories/AssetAttachmentFactory.php @@ -0,0 +1,33 @@ + + */ +class AssetAttachmentFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string<\Illuminate\Database\Eloquent\Model> + */ + protected $model = AssetAttachment::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'filename' => fake()->filePath(), + 'asset_id' => Asset::factory(), + ]; + } +} diff --git a/database/factories/AssetFactory.php b/database/factories/AssetFactory.php new file mode 100644 index 0000000..7234e3d --- /dev/null +++ b/database/factories/AssetFactory.php @@ -0,0 +1,50 @@ + + */ +class AssetFactory extends Factory +{ + /** + * The name of the factory's corresponding model. + * + * @var class-string<\Illuminate\Database\Eloquent\Model> + */ + protected $model = Asset::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->word(), + 'code' => fake()->unique()->numerify('#/INV/####'), + 'category_id' => Category::factory(), + 'brand_id' => Brand::factory(), + 'type' => fake()->catchPhrase(), + 'manufacturer' => fake()->company(), + 'serial_number' => fake()->unique()->numerify('SN-####'), + 'production_year' => fake()->year(), + 'description' => fake()->optional()->sentence(), + 'purchase_date' => fake()->date(), + 'distributor_id' => Distributor::factory(), + 'invoice_number' => fake()->numerify('INV-####'), + 'qty' => fake()->randomDigitNotNull(), + 'unit_price' => fake()->randomNumber(7, true), + 'photo' => fake()->imageUrl(), + 'qr_code' => fake()->imageUrl(), + 'notes' => fake()->optional()->paragraph(), + ]; + } +} diff --git a/database/migrations/2024_09_22_084345_create_assets_table.php b/database/migrations/2024_09_22_084345_create_assets_table.php new file mode 100644 index 0000000..3b6b23d --- /dev/null +++ b/database/migrations/2024_09_22_084345_create_assets_table.php @@ -0,0 +1,54 @@ +uuid('id')->primary(); + $table->string('name'); + $table->string('code')->unique(); + $table->foreignUuid('category_id') + ->constrained('categories', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->foreignUuid('brand_id') + ->constrained('brands', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->string('type'); + $table->string('manufacturer'); + $table->string('serial_number')->unique(); + $table->integer('production_year'); + $table->text('description')->nullable(); + $table->date('purchase_date'); + $table->foreignUuid('distributor_id') + ->constrained('distributors', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->string('invoice_number'); + $table->integer('qty'); + $table->bigInteger('unit_price'); + $table->bigInteger('total_price'); + $table->text('photo')->nullable(); + $table->text('notes')->nullable(); + $table->text('qr_code'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('assets'); + } +}; diff --git a/database/migrations/2024_09_22_112925_create_asset_attachments_table.php b/database/migrations/2024_09_22_112925_create_asset_attachments_table.php new file mode 100644 index 0000000..f80e340 --- /dev/null +++ b/database/migrations/2024_09_22_112925_create_asset_attachments_table.php @@ -0,0 +1,32 @@ +uuid('id')->primary(); + $table->string('filename'); + $table->foreignUuid('asset_id') + ->constrained('assets', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('asset_attachments'); + } +}; diff --git a/database/migrations/2024_09_27_030818_create_asset_histories_table.php b/database/migrations/2024_09_27_030818_create_asset_histories_table.php new file mode 100644 index 0000000..8198692 --- /dev/null +++ b/database/migrations/2024_09_27_030818_create_asset_histories_table.php @@ -0,0 +1,44 @@ +uuid('id')->primary(); + $table->foreignUuid('asset_id') + ->constrained('assets', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->date('date_from'); + $table->foreignUuid('responsible_person_id') + ->constrained('responsible_persons', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->foreignUuid('location_id') + ->constrained('locations', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->integer('qty'); + $table->integer('condition_percentage'); + $table->integer('completeness_percentage'); + $table->text('notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('asset_histories'); + } +}; diff --git a/database/migrations/2024_09_30_102100_create_asset_finances_table.php b/database/migrations/2024_09_30_102100_create_asset_finances_table.php new file mode 100644 index 0000000..2046770 --- /dev/null +++ b/database/migrations/2024_09_30_102100_create_asset_finances_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->foreignUuid('asset_id') + ->constrained('assets', 'id') + ->onDelete('cascade') + ->onUpdate('cascade'); + $table->string('type'); + $table->date('date'); + $table->bigInteger('amount'); + $table->text('notes')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('asset_finances'); + } +}; diff --git a/database/seeders/AssetAttachmentSeeder.php b/database/seeders/AssetAttachmentSeeder.php new file mode 100644 index 0000000..a39566e --- /dev/null +++ b/database/seeders/AssetAttachmentSeeder.php @@ -0,0 +1,18 @@ +count(25) + ->create(); + } +} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ee573eb..566e900 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -17,6 +17,7 @@ public function run(): void CategorySeeder::class, BrandSeeder::class, DistributorSeeder::class, + AssetAttachmentSeeder::class, ResponsiblePersonSeeder::class, LocationSeeder::class, ]); diff --git a/resources/images/photo.png b/resources/images/photo.png new file mode 100644 index 0000000..f9f85b6 Binary files /dev/null and b/resources/images/photo.png differ diff --git a/resources/js/app.js b/resources/js/app.js index 5badf7b..0328b36 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,16 +1,236 @@ import { + Autocomplete, + Collapse, + Datepicker, Dropdown, initTE, Input, + Lightbox, Offcanvas, PerfectScrollbar, Ripple, + Select, Toast, Tooltip, } from 'tw-elements'; import './autosize'; import './bootstrap'; +/** Autocomplete Handler */ +const autocompleteList = [].slice.call( + document.querySelectorAll('[data-autocomplete]') +); + +autocompleteList.map((autocompleteItem) => { + return new Autocomplete(autocompleteItem, { + autoSelect: true, + container: 'body', + customContent: '', + debounce: 300, + displayValue: (value) => value, + filter: (value) => { + const items = eval(autocompleteItem.dataset.autocompleteItems); + + return items.filter((item) => { + return item.toLowerCase().startsWith(value.toLowerCase()); + }); + }, + itemContent: null, + listHeight: 240, + loaderCloseDelay: 300, + noResults: 'No results found', + threshold: 2, + }, { + autocompleteItem: + "flex flex-row items-center justify-between w-full h-[48px] px-4 py-[0.4375rem] truncate text-black/[0.87] bg-transparent select-none cursor-pointer hover:[&:not([data-te-autocomplete-option-disabled])]:bg-black/[0.04] data-[te-autocomplete-item-active]:bg-black/[0.10] data-[data-te-autocomplete-option-disabled]:text-black/[0.38] data-[data-te-autocomplete-option-disabled]:cursor-default dark:text-white/[0.87] dark:hover:[&:not([data-te-autocomplete-option-disabled])]:bg-white/[0.04] dark:data-[te-autocomplete-item-active]:bg-white/[0.10]", + autocompleteList: + "list-none m-0 p-0 overflow-y-auto", + autocompleteLoader: + "absolute right-1 top-2 w-[1.4rem] h-[1.4rem] border-[0.15em]", + dropdown: + "relative outline-none min-w-[100px] m-0 scale-[0.8] opacity-0 bg-white shadow-[0_2px_5px_0_rgba(0,0,0,0.16),_0_2px_10px_0_rgba(0,0,0,0.12)] transition duration-200 motion-reduce:transition-none data-[te-autocomplete-state-open]:scale-100 data-[te-autocomplete-state-open]:opacity-100 dark:bg-charleston-green", + dropdownContainer: + "z-[0]", + scrollbar: + "[&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar]:h-1 [&::-webkit-scrollbar-button]:block [&::-webkit-scrollbar-button]:h-0 [&::-webkit-scrollbar-button]:bg-transparent [&::-webkit-scrollbar-track-piece]:bg-transparent [&::-webkit-scrollbar-track-piece]:rounded-none [&::-webkit-scrollbar-track-piece]: [&::-webkit-scrollbar-track-piece]:rounded-l [&::-webkit-scrollbar-thumb]:h-[50px] [&::-webkit-scrollbar-thumb]:bg-[#999] [&::-webkit-scrollbar-thumb]:rounded", + spinnerIcon: + "inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]", + }); +}); + +/** Datepicker Handler */ +const datepickerList = [].slice.call( + document.querySelectorAll('[data-te-datepicker-init]') +); + +datepickerList.map((datepickerItem) => { + return new Datepicker(datepickerItem, { + cancelBtnLabel: 'Cancel selection', + cancelBtnText: 'Cancel', + changeMonthIconTemplate: ` + + + + `, + clearBtnLabel: 'Clear selection', + clearBtnText: 'Clear', + confirmDateOnSelect: false, + container: 'body', + disablePast: false, + disableFuture: true, + disableInput: false, + disableToggleButton: false, + filter: null, + format: 'yyyy-mm-dd', + inline: false, + max: '-', + min: '-', + monthsFull: [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ], + monthsShort: [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ], + nextMonthLabel: 'Next month', + nextMultiYearLabel: 'Next 24 years', + nextYearLabel: 'Next year', + okBtnText: 'Ok', + okBtnLabel: 'Confirm selection', + prevMonthLabel: 'Previous month', + prevMultiYearLabel: 'Previous 24 years', + prevYearLabel: 'Previous year', + removeCancelBtn: false, + removeClearBtn: false, + removeOkBtn: false, + startDate: null, + startDay: 0, + switchToDayViewLabel: 'Choose date', + switchToMonthViewLabel: 'Choose date', + switchToMultiYearViewLabel: 'Choose year and month', + title: 'Select date', + view: 'days', + viewChangeIconTemplate: ` + + + + `, + weekdaysFull: [ + 'Sunday', + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + ], + weekdaysNarrow: [ + 'S', + 'M', + 'T', + 'W', + 'T', + 'F', + 'S', + ], + weekdaysShort: [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + ], + }, { + datepickerArrowControls: + "mt-2.5", + datepickerBackdrop: + "w-full h-full fixed top-0 right-0 left-0 bottom-0 bg-black/40 z-[1065]", + datepickerCell: + "text-center data-[te-datepicker-cell-disabled]:text-neutral-300 data-[te-datepicker-cell-disabled]:cursor-default data-[te-datepicker-cell-disabled]:pointer-events-none data-[te-datepicker-cell-disabled]:hover:cursor-default hover:cursor-pointer group", + datepickerCellContent: + "mx-auto group-[:not([data-te-datepicker-cell-disabled]):not([data-te-datepicker-cell-selected]):hover]:bg-neutral-300 group-[[data-te-datepicker-cell-selected]]:bg-blue-500 group-[[data-te-datepicker-cell-selected]]:text-white group-[:not([data-te-datepicker-cell-selected])[data-te-datepicker-cell-focused]]:bg-neutral-100 group-[[data-te-datepicker-cell-focused]]:data-[te-datepicker-cell-selected]:bg-blue-500 group-[[data-te-datepicker-cell-current]]:border-solid group-[[data-te-datepicker-cell-current]]:border-black group-[[data-te-datepicker-cell-current]]:border dark:group-[:not([data-te-datepicker-cell-disabled]):not([data-te-datepicker-cell-selected]):hover]:bg-white/10 dark:group-[[data-te-datepicker-cell-current]]:border-white dark:group-[:not([data-te-datepicker-cell-selected])[data-te-datepicker-cell-focused]]:bg-white/10 dark:text-white dark:group-[[data-te-datepicker-cell-disabled]]:text-neutral-500", + datepickerCellContentLarge: + "w-[72px] h-10 leading-10 py-[1px] px-0.5 rounded-[999px]", + datepickerCellContentSmall: + "w-9 h-9 leading-9 rounded-[50%] text-[13px]", + datepickerCellLarge: + "w-[76px] h-[42px]", + datepickerCellSmall: + "w-10 h-10 xs:max-md:landscape:w-8 xs:max-md:landscape:h-8", + datepickerClearBtn: + "mr-auto", + datepickerDate: + "xs:max-md:landscape:mt-24 h-[72px] flex flex-col justify-end", + datepickerDateText: + "text-[34px] font-normal text-white", + datepickerDateControls: + "px-3 pt-2.5 pb-0 flex justify-between text-black/[64]", + datepickerDayHeading: + "w-10 h-10 text-center text-[12px] font-normal dark:text-white", + datepickerFooter: + "h-14 flex absolute w-full bottom-0 justify-end items-center px-3", + datepickerDropdownContainer: + "w-[328px] h-[380px] bg-white rounded-lg shadow-[0px_2px_15px_-3px_rgba(0,0,0,.07),_0px_10px_20px_-2px_rgba(0,0,0,.04)] z-[1066] dark:bg-zinc-700", + datepickerFooterBtn: + "outline-none bg-white text-blue-500 border-none cursor-pointer py-0 px-2.5 uppercase text-[0.8rem] leading-10 font-medium h-10 tracking-[.1rem] rounded-[10px] mb-2.5 hover:bg-neutral-200 focus:bg-neutral-200 dark:bg-transparent dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10", + datepickerHeader: + "xs:max-md:landscape:h-full h-[120px] px-6 bg-blue-500 flex flex-col rounded-t-lg dark:bg-zinc-800", + datepickerNextButton: + "p-0 w-10 h-10 leading-10 border-none outline-none m-0 text-gray-600 bg-transparent hover:bg-neutral-200 hover:rounded-[50%] focus:bg-neutral-200 focus:rounded-[50%] dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10 [&>svg]:w-4 [&>svg]:h-4 [&>svg]:rotate-180 [&>svg]:mx-auto", + datepickerPreviousButton: + "p-0 w-10 h-10 leading-10 border-none outline-none m-0 text-gray-600 bg-transparent mr-6 hover:bg-neutral-200 hover:rounded-[50%] focus:bg-neutral-200 focus:rounded-[50%] dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10 [&>svg]:w-4 [&>svg]:h-4 [&>svg]:mx-auto", + datepickerTable: + "mx-auto w-[304px]", + datepickerTitle: + "h-8 flex flex-col justify-end", + datepickerTitleText: + "text-[10px] font-normal uppercase tracking-[1.7px] text-white", + datepickerToggleButton: + "flex items-center justify-content-center [&>svg]:w-5 [&>svg]:h-5 absolute outline-none border-none bg-transparent right-0.5 top-1/2 -translate-x-1/2 -translate-y-1/2 hover:text-primary focus:text-primary dark:hover:text-primary-400 dark:focus:text-primary-400 dark:text-neutral-200", + datepickerMain: + "relative h-full", + datepickerView: + "outline-none px-3", + datepickerViewChangeIcon: + "inline-block pointer-events-none ml-[3px] [&>svg]:w-4 [&>svg]:h-4 [&>svg]:fill-neutral-500 dark:[&>svg]:fill-white", + datepickerViewChangeButton: + "flex items-center outline-none p-2.5 text-neutral-500 font-medium text-[0.9rem] rounded-xl shadow-none bg-transparent m-0 border-none hover:bg-neutral-200 focus:bg-neutral-200 dark:text-white dark:hover:bg-white/10 dark:focus:bg-white/10", + fadeIn: + "animate-[fade-in_0.3s_both] px-[auto] motion-reduce:transition-none motion-reduce:animate-none", + fadeInShort: + "animate-[fade-in_0.15s_both] px-[auto] motion-reduce:transition-none motion-reduce:animate-none", + fadeOut: + "animate-[fade-out_0.3s_both] px-[auto] motion-reduce:transition-none motion-reduce:animate-none", + fadeOutShort: + "animate-[fade-out_0.15s_both] px-[auto] motion-reduce:transition-none motion-reduce:animate-none", + modalContainer: + "flex flex-col fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[328px] h-[512px] bg-white rounded-[0.6rem] shadow-lg z-[1066] xs:max-md:landscape:w-[475px] xs:max-md:landscape:h-[360px] xs:max-md:landscape:flex-row dark:bg-zinc-700", + }); +}); + /** Dropdown Handler */ const dropdownList = [].slice.call( document.querySelectorAll('[data-te-dropdown-toggle-ref]') @@ -126,6 +346,142 @@ perfectScrollbarList.map((perfectScrollbarItem) => { }); }); +/** Select Handler */ +const selectList = [].slice.call( + document.querySelectorAll('[data-te-select-init]') +); + +selectList.map((selectItem) => { + return new Select(selectItem, { + selectAutoSelect: true, + selectContainer: 'body', + selectClearButton: false, + disabled: false, + selectDisplayedLabels: 5, + selectFormWhite: false, + multiple: false, + selectOptionsSelectedLabel: 'options selected', + selectOptionHeight: 48, + selectAll: true, + selectAllLabel: 'Select all', + selectSearchPlaceholder: 'Search...', + selectSize: 'default', + selectVisibleOptions: 5, + // selectFilter: false, + selectFilterDebounce: 300, + selectNoResultText: 'No results', + selectValidation: false, + selectValidFeedback: 'Valid', + selectInvalidFeedback: 'Invalid', + selectPlaceholder: '', + customArrow: ` + + + + + `, + }, { + dropdown: + "relative outline-none min-w-[100px] m-0 scale-[0.8] opacity-0 bg-white shadow-[0_2px_5px_0_rgba(0,0,0,0.16),_0_2px_10px_0_rgba(0,0,0,0.12)] transition duration-200 motion-reduce:transition-none data-[te-select-open]:scale-100 data-[te-select-open]:opacity-100 dark:bg-charleston-green", + formCheckInput: + "relative float-left ml-0 mr-2 mt-0.5 h-[1.125rem] w-[1.125rem] appearance-none rounded-[0.25rem] border-[0.125rem] border-solid border-black/[0.60] outline-none before:pointer-events-none before:absolute before:h-[0.875rem] before:w-[0.875rem] before:scale-0 before:rounded-full before:bg-transparent before:opacity-0 before:shadow-[0px_0px_0px_13px_transparent] before:content-[''] checked:border-primary checked:bg-primary checked:before:opacity-[0.16] checked:after:absolute checked:after:-mt-px checked:after:ml-[0.25rem] checked:after:block checked:after:h-[0.8125rem] checked:after:w-[0.375rem] checked:after:rotate-45 checked:after:border-[0.125rem] checked:after:border-l-0 checked:after:border-t-0 checked:after:border-solid checked:after:border-white checked:after:bg-transparent checked:after:content-[''] hover:cursor-pointer hover:before:opacity-[0.04] hover:before:shadow-[0px_0px_0px_13px_rgba(0,0,0,0.6)] focus:shadow-none focus:transition-[border-color_0.2s] focus:before:scale-100 focus:before:opacity-[0.12] focus:before:shadow-[0px_0px_0px_13px_rgba(0,0,0,0.6)] focus:before:transition-[box-shadow_0.2s,transform_0.2s] focus:after:absolute focus:after:z-[1] focus:after:block focus:after:h-[0.875rem] focus:after:w-[0.875rem] focus:after:rounded-[0.125rem] focus:after:content-[''] checked:focus:before:scale-100 checked:focus:before:shadow-[0px_0px_0px_13px_theme(colors.primary)] checked:focus:before:transition-[box-shadow_0.2s,transform_0.2s] checked:focus:after:-mt-px checked:focus:after:ml-[0.25rem] checked:focus:after:h-[0.8125rem] checked:focus:after:w-[0.375rem] checked:focus:after:rotate-45 checked:focus:after:rounded-none checked:focus:after:border-[0.125rem] checked:focus:after:border-l-0 checked:focus:after:border-t-0 checked:focus:after:border-solid checked:focus:after:border-white checked:focus:after:bg-transparent dark:border-white/[0.60] dark:checked:border-primary dark:checked:bg-primary dark:checked:after:border-black dark:checked:focus:after:border-black dark:focus:before:shadow-[0px_0px_0px_13px_rgba(255,255,255,0.4)] dark:checked:focus:before:shadow-[0px_0px_0px_13px_theme(colors.primary)]", + formOutline: + "relative", + initialized: + "hidden", + inputGroup: + "flex items-center whitespace-nowrap p-2.5 text-center text-base font-normal leading-[1.6] text-gray-700 dark:bg-zinc-800 dark:text-gray-200 dark:placeholder:text-gray-200", + noResult: + "flex items-center px-4", + optionsList: + "list-none m-0 p-0", + optionsWrapper: + "overflow-y-auto", + optionsWrapperScrollbar: + "[&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar]:h-1 [&::-webkit-scrollbar-button]:block [&::-webkit-scrollbar-button]:h-0 [&::-webkit-scrollbar-button]:bg-transparent [&::-webkit-scrollbar-track-piece]:bg-transparent [&::-webkit-scrollbar-track-piece]:rounded-none [&::-webkit-scrollbar-track-piece]: [&::-webkit-scrollbar-track-piece]:rounded-l [&::-webkit-scrollbar-thumb]:h-[50px] [&::-webkit-scrollbar-thumb]:bg-[#999] [&::-webkit-scrollbar-thumb]:rounded", + selectArrow: + "block w-9 h-9 p-1.5 m-0 text-black/[0.60] rounded-full align-middle cursor-pointer outline-none transition duration-150 ease-in-out absolute right-0 !top-1/2 -translate-y-1/2 z-0 dark:text-white/[0.60] peer-disabled:opacity-70 peer-disabled:cursor-not-allowed peer-disabled:hover:!bg-transparent peer-disabled:active:!bg-transparent peer-disabled:focus:!bg-transparent", + selectArrowWhite: + "text-gray-50 peer-focus:!text-white peer-data-[te-input-focused]:!text-white", + selectArrowDefault: + "top-2", + selectArrowLg: + "top-[13px]", + selectArrowSm: + "top-1", + selectClearBtn: + "absolute top-2 right-9 text-black cursor-pointer focus:text-primary outline-none dark:text-gray-200", + selectClearBtnWhite: + "!text-gray-50", + selectClearBtnDefault: + "top-2 text-base", + selectClearBtnLg: + "top-[11px] text-base", + selectClearBtnSm: + "top-1 text-[0.8rem]", + selectDropdownContainer: + "z-[0]", + selectFakeValue: + "transform-none hidden data-[te-input-state-active]:block", + selectFilterInput: + "relative m-0 block w-full min-w-0 flex-auto rounded border border-solid border-gray-300 bg-transparent bg-clip-padding px-3 py-1.5 text-base font-normal text-gray-700 transition duration-300 ease-in-out motion-reduce:transition-none focus:border-primary focus:text-gray-700 focus:shadow-te-primary focus:outline-none dark:text-gray-200 dark:placeholder:text-gray-200", + selectInput: + "peer block min-h-[48px] w-full border-0 bg-transparent pt-3 pb-2 subtitle-1 text-black/[0.87] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 data-[te-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-white/[0.87] dark:placeholder:text-white/[0.87] [&:not([data-te-input-placeholder-active])]:placeholder:opacity-0 disabled:opacity-60 disabled:cursor-not-allowed group-has-[.is-invalid]:!caret-error", + selectInputWhite: + "!text-gray-50", + selectInputSizeDefault: + "p-0 m-0 leading-[1.6]", + selectInputSizeLg: + "py-[0.32rem] px-3 leading-[2.15]", + selectInputSizeSm: + "py-[0.33rem] px-3 text-xs leading-[1.5]", + selectLabel: + "pointer-events-none absolute top-0 left-0 mb-0 max-w-[90%] origin-[0_0] truncate text-black/[0.60] transition-all duration-200 ease-out peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[te-input-state-active]:scale-[0.8] peer-data-[te-input-focused]:text-primary motion-reduce:transition-none dark:text-white/[0.60] dark:peer-focus:text-primary data-[te-input-state-active]:scale-[0.8] group-data-[te-validation-state='invalid']:!text-error group-data-[te-validation-state='invalid']:peer-focus:!text-error peer-disabled:opacity-60 peer-disabled:cursor-not-allowed group-has-[.is-invalid]:!text-error group-has-[.is-invalid]:peer-focus:!text-error group-has-[.is-invalid]:peer-data-[te-input-focused]:!text-error group-has-[.is-invalid]:dark:!text-error group-has-[.is-invalid]:dark:peer-focus:!text-error group-has-[.is-invalid]:dark:peer-data-[te-input-focused]:!text-error", + selectLabelWhite: + "!text-gray-50", + selectLabelSizeDefault: + "pt-3 leading-[1.6] peer-focus:-translate-y-[1.25rem] peer-data-[te-input-state-active]:-translate-y-[1.25rem] data-[te-input-state-active]:-translate-y-[1.25rem]", + selectLabelSizeLg: + "pt-[0.37rem] leading-[2.15] peer-focus:-translate-y-[1.15rem] peer-data-[te-input-state-active]:-translate-y-[1.15rem] data-[te-input-state-active]:-translate-y-[1.15rem]", + selectLabelSizeSm: + "pt-[0.37rem] text-xs leading-[1.5] peer-focus:-translate-y-[0.75rem] peer-data-[te-input-state-active]:-translate-y-[0.75rem] data-[te-input-state-active]:-translate-y-[0.75rem]", + selectOption: + "flex flex-row items-center justify-between w-full px-4 truncate text-black/[0.87] bg-transparent select-none cursor-pointer data-[te-input-multiple-active]:bg-black/[0.10] hover:[&:not([data-te-select-option-disabled])]:bg-black/[0.04] hover:data-[te-input-state-active]:!bg-black/[0.10] data-[te-input-state-active]:bg-black/[0.10] data-[te-select-option-selected]:data-[te-input-state-active]:bg-black/[0.10] data-[te-select-selected]:data-[te-select-option-disabled]:cursor-default data-[te-select-selected]:data-[te-select-option-disabled]:text-black/[0.38] data-[te-select-selected]:data-[te-select-option-disabled]:bg-transparent data-[te-select-option-selected]:bg-black/[0.10] data-[te-select-option-disabled]:text-black/[0.38] data-[te-select-option-disabled]:cursor-default group-data-[te-select-option-group-ref]/opt:pl-7 dark:text-white/[0.87] dark:hover:[&:not([data-te-select-option-disabled])]:bg-white/[0.04] dark:hover:data-[te-input-state-active]:!bg-white/[0.10] dark:data-[te-input-state-active]:bg-white/[0.10] dark:data-[te-select-option-selected]:data-[te-input-state-active]:bg-white/[0.10] dark:data-[te-select-option-disabled]:text-white/[0.38] dark:data-[te-input-multiple-active]:bg-white/[0.10]", + selectAllOption: + "", + selectOptionGroup: + "group/opt", + selectOptionGroupLabel: + "flex flex-row items-center w-full px-4 truncate bg-transparent text-black/50 select-none dark:text-gray-300", + selectOptionIcon: + "w-7 h-7 rounded-full", + selectOptionSecondaryText: + "block text-[0.8rem] text-gray-500 dark:text-gray-300", + selectOptionText: + "group", + selectValidationValid: + "hidden absolute -mt-3 w-auto text-sm text-green-600 cursor-pointer group-data-[te-was-validated]/validation:peer-valid:block", + selectValidationInvalid: + "hidden absolute -mt-3 w-auto text-sm text-[rgb(220,76,100)] cursor-pointer group-data-[te-was-validated]/validation:peer-invalid:block", + + /** Code below is input style */ + notch: + "group flex w-full max-w-full h-full text-left select-none pointer-events-none absolute transition-all top-0 after:content[''] after:block after:w-full after:absolute after:bottom-0 left-0 after:border-b-2 after:transition-transform after:duration-300 after:scale-x-0 after:border-primary peer-focus:after:scale-x-100 peer-focus:after:border-primary peer-data-[te-input-focused]:after:scale-x-100 peer-data-[te-input-focused]:after:border-primary group-data-[te-validation-state='invalid']:after:!border-error group-has-[.is-invalid]:after:!border-error group-has-[.is-invalid]:peer-focus:after:!border-error group-has-[.is-invalid]:peer-data-[te-input-focused]:after:!border-error", + notchLeading: + "pointer-events-none border-b border-solid box-border bg-transparent transition-none duration-0 left-0 top-0 h-full w-0 border-r-0 rounded-l-none group-data-[te-input-focused]:border-r-0 group-data-[te-input-state-active]:border-r-0 peer-disabled:group-[]:border-dotted", + notchLeadingNormal: + "border-black/[0.24] dark:border-white/[0.24] group-data-[te-input-focused]:shadow-none group-data-[te-input-focused]:border-black/[0.24] dark:group-data-[te-input-focused]:border-white/[0.24] group-has-[.is-invalid]:!border-error group-has-[.is-invalid]:dark:!border-error", + notchMiddle: + "pointer-events-none border-b border-solid box-border bg-transparent transition-none duration-0 grow-0 shrink-0 basis-auto w-auto max-w-[calc(100%-1rem)] h-full border-r-0 border-l-0 group-data-[te-input-focused]:border-x-0 group-data-[te-input-state-active]:border-x-0 group-data-[te-input-focused]:border-t-0 group-data-[te-input-state-active]:border-t-0 group-data-[te-input-focused]:border-solid group-data-[te-input-state-active]:border-solid group-data-[te-input-focused]:border-t-transparent group-data-[te-input-state-active]:border-t-transparent peer-disabled:group-[]:border-dotted", + notchMiddleNormal: + "border-black/[0.24] dark:border-white/[0.24] group-data-[te-input-focused]:shadow-none group-data-[te-input-focused]:border-black/[0.24] dark:group-data-[te-input-focused]:border-white/[0.24] group-has-[.is-invalid]:!border-error group-has-[.is-invalid]:dark:!border-error", + notchTrailing: + "pointer-events-none border-b border-solid box-border bg-transparent transition-none duration-0 grow h-full border-l-0 rounded-r-none group-data-[te-input-focused]:border-l-0 group-data-[te-input-state-active]:border-l-0 peer-disabled:group-[]:border-dotted", + notchTrailingNormal: + "border-black/[0.24] dark:border-white/[0.24] group-data-[te-input-focused]:shadow-none group-data-[te-input-focused]:border-black/[0.24] dark:group-data-[te-input-focused]:border-white/[0.24] group-has-[.is-invalid]:!border-error group-has-[.is-invalid]:dark:!border-error", + }); +}); + /** Toast Handler */ const toastList = [].slice.call( document.querySelectorAll('[data-te-toast-init]') @@ -196,11 +552,16 @@ tooltipList.map((tooltipItem) => { }); initTE({ + Autocomplete, + Collapse, + Datepicker, Dropdown, Input, + Lightbox, Offcanvas, PerfectScrollbar, Ripple, + Select, Toast, Tooltip, }, { diff --git a/resources/views/active-assets/create.blade.php b/resources/views/active-assets/create.blade.php new file mode 100644 index 0000000..0b123c4 --- /dev/null +++ b/resources/views/active-assets/create.blade.php @@ -0,0 +1,605 @@ +@extends('app') + +@section('title', 'Active') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('POST') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Create') }} +
+
+
+ +
+
+ +
+
+
+ +
+ + {{ __('General') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('name') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('code') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('category') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Photo') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('photo') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Detail Asset') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('brand') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('type') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('manufacturer') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('serial_number') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('production_year') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('description') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Purchase') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('purchase_date') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('distributor') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('invoice_number') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('qty') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('unit_price') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('total_price') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Attachments') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('attachments.*') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Additional Notes') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/active-assets/edit.blade.php b/resources/views/active-assets/edit.blade.php new file mode 100644 index 0000000..32b64a1 --- /dev/null +++ b/resources/views/active-assets/edit.blade.php @@ -0,0 +1,611 @@ +@extends('app') + +@section('title', 'Active') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('PUT') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Edit') }} +
+
+
+ +
+
+ +
+
+
+ +
+ + {{ __('General') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('name') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('code') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('category') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Photo') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('photo') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Detail Asset') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('brand') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('type') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('manufacturer') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('serial_number') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('production_year') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('description') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Purchase') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('purchase_date') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('distributor') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('invoice_number') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('qty') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('unit_price') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('total_price') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Attachments') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('attachments.*') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Additional Notes') }} + +
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/active-assets/index.blade.php b/resources/views/active-assets/index.blade.php new file mode 100644 index 0000000..094a306 --- /dev/null +++ b/resources/views/active-assets/index.blade.php @@ -0,0 +1,332 @@ +@extends('app') + +@section('title', 'Active') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + @if (session()->has('message')) + {{-- Toast --}} + + {{-- End Toast --}} + @endif + + {{-- Cards --}} +
+ +
+
+ +
+
+
+ {{ __('All Active') }} +
+
+
+ +
+
+ + {{-- Search --}} +
+ + + +
+ {{-- End Search --}} + +
+
+ +
+
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+ + {{-- Dropdown --}} + + {{-- End Dropdown --}} + +
+
+ +
+
+ +
+ + {{-- Perfect Scrollbar --}} +
+
+ + {{-- Table --}} + + + + + + + + + + + + + + + + + + @forelse($assets as $asset) + + + + + + + + + + + + + @empty + + + + @endforelse + +
+ {{ __('#') }} + + {{ __('QR Code') }} + + {{ __('Photo') }} + + {{ __('Name') }} + + {{ __('Category') }} + + {{ __('Code') }} + + {{ __('Purchase Date') }} + + {{ __('Brand & Type') }} + + {{ __('Latest History') }} + + {{ __('Action') }} +
+ {{ $loop->iteration + ($assets->currentPage() - 1) * $assets->perPage() }} + + {{ $asset->name }} + + {{ $asset->name }} + + {{ $asset->name }} + + {{ $asset->category?->name }} + + {{ $asset->code }} + + {{ $asset->purchase_date }} + + {{ $asset->brand?->name }} & {{ $asset->type }} + + @if($asset->latestHistory) + {{ $asset->latestHistory?->responsiblePerson?->name }} + ({{ $asset->latestHistory?->location?->name }}, + {{ $asset->latestHistory?->date_from }}) + @else + {{ __('N/A') }} + @endif + +
+ @csrf + @method('DELETE') + + {{-- Dropdown --}} +
+ + + +
+ {{-- End Dropdown --}} + +
+
+ {{ __('No data available.') }} +
+ {{-- End Table --}} + +
+
+ {{-- End Perfect Scrollbar --}} + +
+ +
+
+ + {{-- Pagination --}} +
+ {{ $assets->onEachSide(2)->links() }} +
+ {{-- End Pagination --}} + +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/active-assets/show.blade.php b/resources/views/active-assets/show.blade.php new file mode 100644 index 0000000..f94292c --- /dev/null +++ b/resources/views/active-assets/show.blade.php @@ -0,0 +1,733 @@ +@extends('app') + +@section('title', 'Active') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+
+ +
+ + {{-- Icon Button --}} + + {{-- End Icon Button --}} + +
+ +
+
+
+ {{ __('Details') }} +
+
+
+ +
+
+ @csrf + @method('DELETE') + +
+ + {{-- Icon Button --}} +
+ +
+ {{-- End Icon Button --}} + +
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+ + {{-- Icon Button --}} +
+ +
+ {{-- End Icon Button --}} + +
+ +
+
+ +
+
+ +
+
+ +
    +
  • + + {{ __('General') }} + +
  • + +
  • +
    +
    + + {{ __('Id') }} + +
    + +
    + + {{ $asset->id }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Name') }} + +
    + +
    + + {{ $asset->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Code') }} + +
    + +
    + + {{ $asset->code }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Category') }} + +
    + +
    + + {{ $asset->category?->name }} + +
    +
    +
  • +
+ +
    +
  • + + {{ __('QR Code & Photo') }} + +
  • + +
  • +
    +
    + + {{ __('QR Code & Photo') }} + +
    + +
    +
    + {{ $asset->name }} +
    + +
    + {{ $asset->name }} +
    +
    +
    +
  • +
+ +
    +
  • + + {{ __('Detail Asset') }} + +
  • + +
  • +
    +
    + + {{ __('Brand') }} + +
    + +
    + + {{ $asset->brand?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Type') }} + +
    + +
    + + {{ $asset->type }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Manufacturer') }} + +
    + +
    + + {{ $asset->manufacturer }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Serial Number') }} + +
    + +
    + + {{ $asset->serial_number }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Production Year') }} + +
    + +
    + + {{ $asset->production_year }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Description') }} + +
    + +
    + + {{ $asset->description }} + +
    +
    +
  • +
+ +
    +
  • + + {{ __('Purchase') }} + +
  • + +
  • +
    +
    + + {{ __('Purchase Date') }} + +
    + +
    + + {{ $asset->purchase_date }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Distributor') }} + +
    + +
    + + {{ $asset->distributor?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Invoice Number') }} + +
    + +
    + + {{ $asset->invoice_number }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Qty') }} + +
    + +
    + + {{ $asset->qty }} {{ __('Unit') }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Unit Price') }} + +
    + +
    + + {{ Number::currency($asset->unit_price, in: 'IDR', locale: 'id') }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Total Price') }} + +
    + +
    + + {{ Number::currency($asset->total_price, in: 'IDR', locale: 'id') }} + +
    +
    +
  • +
+ +
    +
  • + + {{ __('Additional Notes') }} + +
  • + +
  • +
    +
    + + {{ __('Notes') }} + +
    + +
    + + {{ $asset->notes }} + +
    +
    +
  • +
+ + + +
    +
  • + + {{ __('Histories') }} + +
  • + + @forelse($asset->assetHistories as $history) +
  • +
    +
    +
    + + {{ __('Date From') }} + +
    + +
    + + {{ $history->date_from }} + +
    +
    + +
    +
    + + {{ __('Responsible Person') }} + +
    + +
    + + {{ $history->responsiblePerson?->name }} + +
    +
    + +
    +
    + + {{ __('Location') }} + +
    + +
    + + {{ $history->location?->name }} + +
    +
    + +
    +
    + + {{ __('Qty') }} + +
    + +
    + + {{ $history->qty }} + +
    +
    + +
    +
    + + {{ __('Condition') }} + +
    + +
    + + {{ Number::percentage($history->condition_percentage, precision: 0, locale: 'id') }} + +
    +
    + +
    +
    + + {{ __('Completeness') }} + +
    + +
    + + {{ Number::percentage($history->completeness_percentage, precision: 0, locale: 'id') }} + +
    +
    + +
    +
    + + {{ __('Notes') }} + +
    + +
    + + {{ $history->notes }} + +
    +
    +
    +
  • + @empty +
  • +
    +
    + + {{ __('No data available.') }} + +
    +
    +
  • + @endforelse +
+ +
    +
  • + + {{ __('Finances') }} + +
  • + + @forelse($asset->assetFinances as $finance) +
  • +
    +
    +
    + + {{ __('Type') }} + +
    + +
    + + {{ $finance->type->label() }} + +
    +
    + +
    +
    + + {{ __('Date') }} + +
    + +
    + + {{ $finance->date }} + +
    +
    + +
    +
    + + {{ __('Amount') }} + +
    + +
    + + {{ Number::currency($finance->amount, in: 'IDR', locale: 'id') }} + +
    +
    + +
    +
    + + {{ __('Notes') }} + +
    + +
    + + {{ $finance->notes }} + +
    +
    +
    +
  • + @empty +
  • +
    +
    + + {{ __('No data available.') }} + +
    +
    +
  • + @endforelse +
+ +
    +
  • +
    +
    + + {{ __('Created At') }} + +
    + +
    + + {{ $asset->created_at }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Updated At') }} + +
    + +
    + + {{ $asset->updated_at }} + +
    +
    +
  • +
+ +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-finances/create.blade.php b/resources/views/asset-finances/create.blade.php new file mode 100644 index 0000000..dad6001 --- /dev/null +++ b/resources/views/asset-finances/create.blade.php @@ -0,0 +1,265 @@ +@extends('app') + +@section('title', 'Finances') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('POST') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Create') }} +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('asset_id') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Type *') }} + +
+ +
+
+
+ +
+ @foreach(\App\Enums\FinanceTypeEnum::cases() as $key => $type) + {{-- Checkbox --}} +
+ value) /> + + +
+ {{-- End Checkbox --}} + @endforeach +
+ +
+ + @error('type') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('date') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('amount') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-finances/edit.blade.php b/resources/views/asset-finances/edit.blade.php new file mode 100644 index 0000000..f311b64 --- /dev/null +++ b/resources/views/asset-finances/edit.blade.php @@ -0,0 +1,271 @@ +@extends('app') + +@section('title', 'Finances') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('PUT') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Edit') }} +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('asset_id') + + {{ $message }} + + @enderror +
+
+ +
+ + {{ __('Type *') }} + +
+ +
+
+
+ +
+ @foreach(\App\Enums\FinanceTypeEnum::cases() as $key => $type) + {{-- Checkbox --}} +
+ type ?: $key === 0) == $type->value) /> + + +
+ {{-- End Checkbox --}} + @endforeach +
+ +
+ + @error('type') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('date') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('amount') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-finances/index.blade.php b/resources/views/asset-finances/index.blade.php new file mode 100644 index 0000000..694c7e3 --- /dev/null +++ b/resources/views/asset-finances/index.blade.php @@ -0,0 +1,255 @@ +@extends('app') + +@section('title', 'Finances') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + @if (session()->has('message')) + {{-- Toast --}} + + {{-- End Toast --}} + @endif + + {{-- Cards --}} +
+ +
+
+ +
+
+
+ {{ __('All Finance') }} +
+
+
+ +
+
+ + {{-- Search --}} +
+ + + +
+ {{-- End Search --}} + +
+
+ +
+
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+
+ +
+
+ +
+ + {{-- Perfect Scrollbar --}} +
+
+ + {{-- Table --}} + + + + + + + + + + + + + + @forelse($finances as $finance) + + + + + + + + + @empty + + + + @endforelse + +
+ {{ __('#') }} + + {{ __('Asset Name') }} + + {{ __('Type') }} + + {{ __('Date') }} + + {{ __('Amount') }} + + {{ __('Action') }} +
+ {{ $loop->iteration + ($finances->currentPage() - 1) * $finances->perPage() }} + + {{ $finance->asset?->name }} + + {{ $finance->type }} + + {{ $finance->date }} + + {{ Number::currency($finance->amount, in: 'IDR', locale: 'id') }} + +
+ @csrf + @method('DELETE') + + {{-- Dropdown --}} +
+ + + +
+ {{-- End Dropdown --}} + +
+
+ {{ __('No data available.') }} +
+ {{-- End Table --}} + +
+
+ {{-- End Perfect Scrollbar --}} + +
+ +
+
+ + {{-- Pagination --}} +
+ {{ $finances->onEachSide(2)->links() }} +
+ {{-- End Pagination --}} + +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-finances/show.blade.php b/resources/views/asset-finances/show.blade.php new file mode 100644 index 0000000..2aed52b --- /dev/null +++ b/resources/views/asset-finances/show.blade.php @@ -0,0 +1,254 @@ +@extends('app') + +@section('title', 'Finances') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+
+ +
+ + {{-- Icon Button --}} + + {{-- End Icon Button --}} + +
+ +
+
+
+ {{ __('Details') }} +
+
+
+ +
+
+ @csrf + @method('DELETE') + +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+ + {{-- Icon Button --}} +
+ +
+ {{-- End Icon Button --}} + +
+ +
+
+ +
+
+ +
+
+ +
    +
  • +
    +
    + + {{ __('Id') }} + +
    + +
    + + {{ $finance->id }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Asset Name') }} + +
    + +
    + + {{ $finance->asset?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Type') }} + +
    + +
    + + {{ $finance->type->label() }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Date') }} + +
    + +
    + + {{ $finance->date }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Amount') }} + +
    + +
    + + {{ Number::currency($finance->amount, in: 'IDR', locale: 'id') }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Notes') }} + +
    + +
    + + {{ $finance->notes }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Created At') }} + +
    + +
    + + {{ $finance->created_at }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Updated At') }} + +
    + +
    + + {{ $finance->updated_at }} + +
    +
    +
  • +
+ +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-histories/create.blade.php b/resources/views/asset-histories/create.blade.php new file mode 100644 index 0000000..5eee9f7 --- /dev/null +++ b/resources/views/asset-histories/create.blade.php @@ -0,0 +1,327 @@ +@extends('app') + +@section('title', 'Histories') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('POST') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Create') }} +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('asset_id') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('date_from') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('responsible_person') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('location') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('qty') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('condition_percentage') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('completeness_percentage') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-histories/edit.blade.php b/resources/views/asset-histories/edit.blade.php new file mode 100644 index 0000000..753dc5f --- /dev/null +++ b/resources/views/asset-histories/edit.blade.php @@ -0,0 +1,331 @@ +@extends('app') + +@section('title', 'Histories') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+ @csrf + @method('PUT') + +
+
+ +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+
+
+ {{ __('Edit') }} +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ + {{-- Select --}} +
+ + + +
+ {{-- End Select --}} + +
+ + @error('asset_id') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + + + +
+ {{-- End Input --}} + +
+ + @error('date_from') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('responsible_person') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('location') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('qty') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('condition_percentage') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('completeness_percentage') + + {{ $message}} + + @enderror +
+
+ +
+
+
+ + {{-- Input --}} +
+ + + +
+ {{-- End Input --}} + +
+ + @error('notes') + + {{ $message }} + + @enderror +
+
+ +
+
+
+ +
+
+ +
+
+ + {{-- Button --}} +
+ +
+ {{-- End Button --}} + + {{-- Button --}} +
+ +
+ {{-- End Button --}} + +
+
+ +
+
+
+ +
+ +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-histories/index.blade.php b/resources/views/asset-histories/index.blade.php new file mode 100644 index 0000000..4cc2524 --- /dev/null +++ b/resources/views/asset-histories/index.blade.php @@ -0,0 +1,273 @@ +@extends('app') + +@section('title', 'Histories') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + @if (session()->has('message')) + {{-- Toast --}} + + {{-- End Toast --}} + @endif + + {{-- Cards --}} +
+ +
+
+ +
+
+
+ {{ __('All History') }} +
+
+
+ +
+
+ + {{-- Search --}} +
+ + + +
+ {{-- End Search --}} + +
+
+ +
+
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+
+ +
+
+ +
+ + {{-- Perfect Scrollbar --}} +
+
+ + {{-- Table --}} + + + + + + + + + + + + + + + + + @forelse($histories as $history) + + + + + + + + + + + + @empty + + + + @endforelse + +
+ {{ __('#') }} + + {{ __('Asset Name') }} + + {{ __('Date From') }} + + {{ __('Responsible Person') }} + + {{ __('Location') }} + + {{ __('Qty') }} + + {{ __('Condition') }} + + {{ __('Completeness') }} + + {{ __('Action') }} +
+ {{ $loop->iteration + ($histories->currentPage() - 1) * $histories->perPage() }} + + {{ $history->asset?->name }} + + {{ $history->date_from }} + + {{ $history->responsiblePerson?->name }} + + {{ $history->location?->name }} + + {{ $history->qty }} + + {{ Number::percentage($history->condition_percentage, precision: 0, locale: 'id') }} + + {{ Number::percentage($history->completeness_percentage, precision: 0, locale: 'id') }} + +
+ @csrf + @method('DELETE') + + {{-- Dropdown --}} +
+ + + +
+ {{-- End Dropdown --}} + +
+
+ {{ __('No data available.') }} +
+ {{-- End Table --}} + +
+
+ {{-- End Perfect Scrollbar --}} + +
+ +
+
+ + {{-- Pagination --}} +
+ {{ $histories->onEachSide(2)->links() }} +
+ {{-- End Pagination --}} + +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/asset-histories/show.blade.php b/resources/views/asset-histories/show.blade.php new file mode 100644 index 0000000..2daff6f --- /dev/null +++ b/resources/views/asset-histories/show.blade.php @@ -0,0 +1,302 @@ +@extends('app') + +@section('title', 'Histories') + +@section('content') +
+ + @include('layout.header') + + @include('layout.sidebar') + +
+
+ + {{-- Breadcrumbs --}} + + {{-- End Breadcrumbs --}} + + {{-- Cards --}} +
+ +
+
+ +
+ + {{-- Icon Button --}} + + {{-- End Icon Button --}} + +
+ +
+
+
+ {{ __('Details') }} +
+
+
+ +
+
+ @csrf + @method('DELETE') + +
+ + {{-- Icon Link --}} + + {{-- End Icon Link --}} + +
+ +
+ + {{-- Icon Button --}} +
+ +
+ {{-- End Icon Button --}} + +
+ +
+
+ +
+
+ +
+
+ +
    +
  • +
    +
    + + {{ __('Id') }} + +
    + +
    + + {{ $history->id }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Asset Name') }} + +
    + +
    + + {{ $history->asset?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Date From') }} + +
    + +
    + + {{ $history->date_from }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Responsible Person') }} + +
    + +
    + + {{ $history->responsiblePerson?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Location') }} + +
    + +
    + + {{ $history->location?->name }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Qty') }} + +
    + +
    + + {{ $history->qty }} {{ __('Unit')}} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Condition') }} + +
    + +
    + + {{ Number::percentage($history->condition_percentage, precision: 0, locale: 'id') }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Completeness') }} + +
    + +
    + + {{ Number::percentage($history->completeness_percentage, precision: 0, locale: 'id') }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Notes') }} + +
    + +
    + + {{ $history->notes }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Created At') }} + +
    + +
    + + {{ $history->created_at }} + +
    +
    +
  • + +
  • +
    +
    + + {{ __('Updated At') }} + +
    + +
    + + {{ $history->updated_at }} + +
    +
    +
  • +
+ +
+
+ +
+ {{-- End Cards --}} + +
+
+ + @include('layout.footer') + +
+@endsection diff --git a/resources/views/brands/create.blade.php b/resources/views/brands/create.blade.php index 8a83315..048482f 100644 --- a/resources/views/brands/create.blade.php +++ b/resources/views/brands/create.blade.php @@ -82,7 +82,7 @@ {{-- Input --}}
- +
-
+
{{-- Input --}}
- +
diff --git a/resources/views/brands/show.blade.php b/resources/views/brands/show.blade.php index aafb382..93561dd 100644 --- a/resources/views/brands/show.blade.php +++ b/resources/views/brands/show.blade.php @@ -109,7 +109,7 @@
-
    +
    • diff --git a/resources/views/categories/create.blade.php b/resources/views/categories/create.blade.php index 0108c37..6272509 100644 --- a/resources/views/categories/create.blade.php +++ b/resources/views/categories/create.blade.php @@ -82,7 +82,7 @@ {{-- Input --}}
      - +
      -
      +
      {{-- Input --}}
      - +
      @@ -233,7 +186,7 @@
    • - - - -
- {{-- End Dropdown --}} - -
diff --git a/resources/views/distributors/show.blade.php b/resources/views/distributors/show.blade.php index dd71cb3..609c350 100644 --- a/resources/views/distributors/show.blade.php +++ b/resources/views/distributors/show.blade.php @@ -109,7 +109,7 @@
-
- -
- - {{-- Dropdown --}} - - {{-- End Dropdown --}} - -
diff --git a/resources/views/responsible-persons/index.blade.php b/resources/views/responsible-persons/index.blade.php index 2b42f4e..c077b17 100644 --- a/resources/views/responsible-persons/index.blade.php +++ b/resources/views/responsible-persons/index.blade.php @@ -88,53 +88,6 @@ {{-- End Icon Link --}}
- -
- - {{-- Dropdown --}} - - {{-- End Dropdown --}} - -
diff --git a/routes/web.php b/routes/web.php index 1cdf7ca..345d5da 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,8 @@ LocationController::class, ]); + Route::resource('active-assets', ActiveAssetController::class) + ->parameters(['active-assets' => 'asset']); + Route::resource('asset-histories', AssetHistoryController::class) + ->parameters(['asset-histories' => 'history']); + Route::resource('asset-finances', AssetFinanceController::class) + ->parameters(['asset-finances' => 'finance']); Route::resource('responsible-persons', ResponsiblePersonController::class) ->parameters(['responsible-persons' => 'person']);