diff --git a/app/Http/Controllers/Api/AdvertisementController.php b/app/Http/Controllers/Api/AdvertisementController.php index 713d128..554213e 100644 --- a/app/Http/Controllers/Api/AdvertisementController.php +++ b/app/Http/Controllers/Api/AdvertisementController.php @@ -9,6 +9,9 @@ use Illuminate\Http\Response; use App\Http\Controllers\Controller; use App\Http\Resources\AdvertisementResource; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Validator; +use Maatwebsite\Excel\Facades\Excel; +use App\Imports\AdvertisementImport; class AdvertisementController extends Controller { @@ -18,7 +21,32 @@ class AdvertisementController extends Controller public function index(Request $request) { $perPage = $request->input('per_page', 15); // Default 15 jika tidak dikirim oleh client - $advertisements = Advertisement::paginate($perPage); + $search = $request->input('search', ''); // Ambil parameter 'search' jika ada + + // Query dasar untuk mengambil iklan + $query = Advertisement::query(); + // Jika ada pencarian, filter berdasarkan kolom yang diinginkan + if ($search) { + $query->where(function ($q) use ($search) { + $q->where('business_name', 'like', "%$search%") + ->orWhere('npwpd', 'like', "%$search%") + ->orWhere('advertisement_content', 'like', "%$search%") + ->orWhere('business_address', 'like', "%$search%") + ->orWhere('advertisement_location', 'like', "%$search%") + ->orWhereIn('village_code', function ($subQuery) use ($search) { + $subQuery->select('village_code') + ->from('villages') + ->where('village_name', 'like', "%$search%"); + }) + ->orWhereIn('district_code', function ($subQuery) use ($search) { + $subQuery->select('district_code') + ->from('districts') + ->where('district_name', 'like', "%$search%"); + }); + }); + } + + $advertisements = $query->paginate($perPage); $advertisements->getCollection()->transform(function ($advertisement) { $village = DB::table('villages')->where('village_code', $advertisement->village_code)->first(); @@ -47,24 +75,58 @@ class AdvertisementController extends Controller { $data = $request->validated(); - // Transformasi village_name dan district_name menjadi village_code dan district_code - if (isset($data['village_name'])) { - // Ambil village_code berdasarkan village_name dari database - $data['village_code'] = DB::table('villages')->where('village_name', $data['village_name'])->value('village_code'); - unset($data['village_name']); // Hapus village_name jika sudah tidak diperlukan - } + // Cari village_code berdasarkan village_name + $village_code = DB::table('villages')->where('village_name', $data['village_name'])->value('village_code'); + // Cari district_code berdasarkan district_name + $district_code = DB::table('districts')->where('district_name', $data['district_name'])->value('district_code'); - if (isset($data['district_name'])) { - // Ambil district_code berdasarkan district_name dari database - $data['district_code'] = DB::table('districts')->where('district_name', $data['district_name'])->value('district_code'); - unset($data['district_name']); // Hapus district_name jika sudah tidak diperlukan - } + // Tambahkan village_code dan district_code ke data + $data['village_code'] = $village_code; + $data['district_code'] = $district_code; // Log data setelah transformasi info($data); return Advertisement::create($data); } + /** + * Import advertisements from Excel or CSV. + */ + public function importFromFile(Request $request) + { + // Validasi file + info($request); + $validator = Validator::make($request->all(), [ + 'file' => 'required|mimes:xlsx,xls|max:10240', // Max 10MB + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'File validation failed.', + 'errors' => $validator->errors() + ], 400); + } + + try { + // Ambil file dari request + $file = $request->file('file'); + + // Menggunakan Laravel Excel untuk mengimpor file + Excel::import(new AdvertisementImport, $file); + + // Jika sukses, kembalikan respons sukses + return response()->json([ + 'message' => 'File uploaded and imported successfully!' + ], 200); + } catch (\Exception $e) { + // Jika ada error, kembalikan error response + return response()->json([ + 'message' => 'Error during file import.', + 'error' => $e->getMessage() + ], 500); + } + } + /** * Display the specified resource. */ @@ -79,21 +141,19 @@ class AdvertisementController extends Controller public function update(AdvertisementRequest $request, Advertisement $advertisement): Advertisement { $data = $request->validated(); - // Transformasi village_name dan district_name menjadi village_code dan district_code - if (isset($data['village_name'])) { - // Ambil village_code berdasarkan village_name dari database - $data['village_code'] = DB::table('villages')->where('village_name', $data['village_name'])->value('village_code'); - unset($data['village_name']); // Hapus village_name jika sudah tidak diperlukan - } - if (isset($data['district_name'])) { - // Ambil district_code berdasarkan district_name dari database - $data['district_code'] = DB::table('districts')->where('district_name', $data['district_name'])->value('district_code'); - unset($data['district_name']); // Hapus district_name jika sudah tidak diperlukan - } + // Cari village_code berdasarkan village_name + $village_code = DB::table('villages')->where('village_name', $data['village_name'])->value('village_code'); + // Cari district_code berdasarkan district_name + $district_code = DB::table('districts')->where('district_name', $data['district_name'])->value('district_code'); + + // Tambahkan village_code dan district_code ke data + $data['village_code'] = $village_code; + $data['district_code'] = $district_code; // Log data setelah transformasi info($data); + $advertisement->update($data); return $advertisement; @@ -105,4 +165,35 @@ class AdvertisementController extends Controller return response()->noContent(); } + + public function searchOptionsInAdvertisements(Request $request) + { + $query = $request->input('query'); + $field = $request->input('field'); + $district = $request->input('district'); // Ambil kecamatan jika ada + + info("Query: $query, Field: $field, District: $district"); + + if ($field === 'district_name') { + $results = DB::table('districts') + ->where('district_name', 'like', '%' . $query . '%') + ->limit(10) + ->get(['district_name AS name', 'district_code AS code']); + } elseif ($field === 'village_name' && $district) { + $results = DB::table('villages') + ->where('village_name', 'like', '%' . $query . '%') + ->whereExists(function ($query) use ($district) { + $query->select(DB::raw(1)) + ->from('districts') + ->whereColumn('villages.district_code', 'districts.district_code') + ->where('districts.district_name', $district); + }) + ->limit(10) + ->get(['village_name AS name', 'village_code AS code']); + } else { + $results = collect(); + } + + return response()->json($results); + } } diff --git a/app/Http/Controllers/Data/AdvertisementController.php b/app/Http/Controllers/Data/AdvertisementController.php index 603057c..83606cc 100644 --- a/app/Http/Controllers/Data/AdvertisementController.php +++ b/app/Http/Controllers/Data/AdvertisementController.php @@ -17,6 +17,15 @@ class AdvertisementController extends Controller return view('data.advertisements.index'); } + /** + * Show the form for uploading a file. + */ + public function bulkCreate() + { + // Mengembalikan view form-upload + return view('data.advertisements.form-upload'); + } + /** * Show the form for creating a new resource. */ @@ -38,7 +47,7 @@ class AdvertisementController extends Controller // $route = 'advertisements.create'; // info("AdvertisementController@edit diakses dengan ID: $title"); - return view('form-create-update.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions')); + return view('data.advertisements.form', compact('title', 'subtitle', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions')); } /** @@ -77,7 +86,7 @@ class AdvertisementController extends Controller // $route = 'advertisements.update'; // Menggunakan route update untuk form edit // info("AdvertisementController@edit diakses dengan route: $route"); - return view('form-create-update.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions')); + return view('data.advertisements.form', compact('title', 'subtitle', 'modelInstance', 'fields', 'fieldTypes', 'apiUrl', 'dropdownOptions')); } private function getFields() @@ -90,8 +99,8 @@ class AdvertisementController extends Controller "advertisement_content" => "Isi Reklame", "business_address" => "Alamat Wajib Pajak", "advertisement_location" => "Lokasi Reklame", - "village_name" => "Desa", "district_name" => "Kecamatan", + "village_name" => "Desa", "length" => "Panjang", "width" => "Lebar", "viewing_angle" => "Sudut Pandang", diff --git a/app/Imports/AdvertisementImport.php b/app/Imports/AdvertisementImport.php new file mode 100644 index 0000000..1c27243 --- /dev/null +++ b/app/Imports/AdvertisementImport.php @@ -0,0 +1,92 @@ +isEmpty()) + { + return; + } + + // Ambil data districts dengan normalisasi nama + $districts = DB::table('districts') + ->get() + ->mapWithKeys(function ($item) { + return [strtolower(trim($item->district_name)) => $item->district_code]; + }) + ->toArray(); + + // Cari header secara otomatis + $header = $rows->first(); + $headerIndex = collect($header)->search(fn($value) => !empty($value)); + + // Pastikan header ditemukan + if ($headerIndex === false) { + return; + } + + $dataToInsert = []; + + foreach ($rows->skip(1) as $row) { + // Normalisasi nama kecamatan dan desa + $districtName = strtolower(trim(str_replace('Kecamatan ', '', $row[8]))); + $villageName = strtolower(trim($row[7])); + + // Cari district_code dari tabel districts + $districtCode = $districts[$districtName] ?? null; + + $listTrueVillage = DB::table('villages') + ->where('district_code', $districtCode) // Perbaikan pada where() + ->get() + ->mapWithKeys(function ($item) { + return [strtolower(trim($item->village_name)) => [ + 'village_code' => $item->village_code, + 'district_code' => $item->district_code + ]]; + }) + ->toArray(); + + // ambil village code yang village_name sama dengan $villageName + $villageCode = $listTrueVillage[$villageName]['village_code'] ?? '0000'; + + $dataToInsert[] = [ + 'no' => $row[0], + 'business_name' => $row[1], + 'npwpd' => $row[2], + 'advertisement_type' => $row[3], + 'advertisement_content' => $row[4], + 'business_address' => $row[5], + 'advertisement_location' => $row[6], + 'village_code' => $villageCode, + 'district_code' => $districtCode, + 'length' => $row[9], + 'width' => $row[10], + 'viewing_angle' => $row[11], + 'face' => $row[12], + 'area' => $row[13], + 'angle' => $row[14], + 'contact' => $row[15] ?? "-", + 'created_at' => now(), + 'updated_at' => now() + ]; + } + // Bulk insert untuk efisiensi + if (!empty($dataToInsert)) { + Advertisement::insert($dataToInsert); + } else { + return; + } + } +} + diff --git a/composer.json b/composer.json index 7b57f35..3b92ebc 100755 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "guzzlehttp/guzzle": "^7.9", "laravel/framework": "^11.31", "laravel/sanctum": "^4.0", - "laravel/tinker": "^2.9" + "laravel/tinker": "^2.9", + "maatwebsite/excel": "^3.1" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/composer.lock b/composer.lock index 14ca53b..07f0088 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "600bc468a0c3848ac887e905a0cd06a8", + "content-hash": "37b87e09a4c98ce5b5c07fac061edb29", "packages": [ { "name": "brick/math", @@ -135,6 +135,166 @@ ], "time": "2024-02-09T16:56:22+00:00" }, + { + "name": "composer/pcre", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-strict-rules": "^1 || ^2", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.3.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-11-12T16:29:46+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.3", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -510,6 +670,67 @@ ], "time": "2023-10-06T06:47:41+00:00" }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.18.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/cb56001e54359df7ae76dc522d08845dc741621b", + "reference": "cb56001e54359df7ae76dc522d08845dc741621b", + "shasum": "" + }, + "require": { + "php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0" + }, + "require-dev": { + "cerdic/css-tidy": "^1.7 || ^2.0", + "simpletest/simpletest": "dev-master" + }, + "suggest": { + "cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.", + "ext-bcmath": "Used for unit conversion and imagecrash protection", + "ext-iconv": "Converts text to and from non-UTF-8 encodings", + "ext-tidy": "Used for pretty-printing HTML" + }, + "type": "library", + "autoload": { + "files": [ + "library/HTMLPurifier.composer.php" + ], + "psr-0": { + "HTMLPurifier": "library/" + }, + "exclude-from-classmap": [ + "/library/HTMLPurifier/Language/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "support": { + "issues": "https://github.com/ezyang/htmlpurifier/issues", + "source": "https://github.com/ezyang/htmlpurifier/tree/v4.18.0" + }, + "time": "2024-11-01T03:51:45+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -1893,6 +2114,272 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "maatwebsite/excel", + "version": "3.1.62", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "decfb9140161fcc117571e47e35ddf27983189ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/decfb9140161fcc117571e47e35ddf27983189ce", + "reference": "decfb9140161fcc117571e47e35ddf27983189ce", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.29.7", + "psr/simple-cache": "^1.0||^2.0||^3.0" + }, + "require-dev": { + "laravel/scout": "^7.0||^8.0||^9.0||^10.0", + "orchestra/testbench": "^6.0||^7.0||^8.0||^9.0", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Excel": "Maatwebsite\\Excel\\Facades\\Excel" + }, + "providers": [ + "Maatwebsite\\Excel\\ExcelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Maatwebsite\\Excel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Patrick Brouwers", + "email": "patrick@spartner.nl" + } + ], + "description": "Supercharged Excel exports and imports in Laravel", + "keywords": [ + "PHPExcel", + "batch", + "csv", + "excel", + "export", + "import", + "laravel", + "php", + "phpspreadsheet" + ], + "support": { + "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues", + "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.62" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2025-01-04T12:14:36+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.2" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.16", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^11.0", + "vimeo/psalm": "^6.0" + }, + "suggest": { + "guzzlehttp/psr7": "^2.4", + "psr/http-message": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "ZipStream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paul Duncan", + "email": "pabs@pablotron.org" + }, + { + "name": "Jonatan Männchen", + "email": "jonatan@maennchen.ch" + }, + { + "name": "Jesse Donat", + "email": "donatj@gmail.com" + }, + { + "name": "András Kolesár", + "email": "kolesar@kolesar.hu" + } + ], + "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", + "keywords": [ + "stream", + "zip" + ], + "support": { + "issues": "https://github.com/maennchen/ZipStream-PHP/issues", + "source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-01-27T12:07:53+00:00" + }, + { + "name": "markbaker/complex", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPComplex.git", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Complex\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@lange.demon.co.uk" + } + ], + "description": "PHP Class for working with complex numbers", + "homepage": "https://github.com/MarkBaker/PHPComplex", + "keywords": [ + "complex", + "mathematics" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPComplex/issues", + "source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2" + }, + "time": "2022-12-06T16:21:08+00:00" + }, + { + "name": "markbaker/matrix", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/MarkBaker/PHPMatrix.git", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c", + "reference": "728434227fe21be27ff6d86621a1b13107a2562c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-master", + "phpcompatibility/php-compatibility": "^9.3", + "phpdocumentor/phpdocumentor": "2.*", + "phploc/phploc": "^4.0", + "phpmd/phpmd": "2.*", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "sebastian/phpcpd": "^4.0", + "squizlabs/php_codesniffer": "^3.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Matrix\\": "classes/src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mark Baker", + "email": "mark@demon-angel.eu" + } + ], + "description": "PHP Class for working with matrices", + "homepage": "https://github.com/MarkBaker/PHPMatrix", + "keywords": [ + "mathematics", + "matrix", + "vector" + ], + "support": { + "issues": "https://github.com/MarkBaker/PHPMatrix/issues", + "source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1" + }, + "time": "2022-12-02T22:17:43+00:00" + }, { "name": "monolog/monolog", "version": "3.8.0", @@ -2395,6 +2882,112 @@ ], "time": "2024-11-21T10:39:51+00:00" }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.29.10", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "c80041b1628c4f18030407134fe88303661d4e4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/c80041b1628c4f18030407134fe88303661d4e4e", + "reference": "c80041b1628c4f18030407134fe88303661d4e4e", + "shasum": "" + }, + "require": { + "composer/pcre": "^1||^2||^3", + "ext-ctype": "*", + "ext-dom": "*", + "ext-fileinfo": "*", + "ext-gd": "*", + "ext-iconv": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "ext-xml": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", + "ext-zip": "*", + "ext-zlib": "*", + "ezyang/htmlpurifier": "^4.15", + "maennchen/zipstream-php": "^2.1 || ^3.0", + "markbaker/complex": "^3.0", + "markbaker/matrix": "^3.0", + "php": "^7.4 || ^8.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "dompdf/dompdf": "^1.0 || ^2.0 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.2", + "mitoteam/jpgraph": "^10.3", + "mpdf/mpdf": "^8.1.1", + "phpcompatibility/php-compatibility": "^9.3", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.7", + "tecnickcom/tcpdf": "^6.5" + }, + "suggest": { + "dompdf/dompdf": "Option for rendering PDF with PDF Writer", + "ext-intl": "PHP Internationalization Functions", + "mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers", + "mpdf/mpdf": "Option for rendering PDF with PDF Writer", + "tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer" + }, + "type": "library", + "autoload": { + "psr-4": { + "PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maarten Balliauw", + "homepage": "https://blog.maartenballiauw.be" + }, + { + "name": "Mark Baker", + "homepage": "https://markbakeruk.net" + }, + { + "name": "Franck Lefevre", + "homepage": "https://rootslabs.net" + }, + { + "name": "Erik Tilt" + }, + { + "name": "Adrien Crivelli" + } + ], + "description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine", + "homepage": "https://github.com/PHPOffice/PhpSpreadsheet", + "keywords": [ + "OpenXML", + "excel", + "gnumeric", + "ods", + "php", + "spreadsheet", + "xls", + "xlsx" + ], + "support": { + "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues", + "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.10" + }, + "time": "2025-02-08T02:56:14+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.3", diff --git a/config/excel.php b/config/excel.php new file mode 100644 index 0000000..c1fd34a --- /dev/null +++ b/config/excel.php @@ -0,0 +1,380 @@ + [ + + /* + |-------------------------------------------------------------------------- + | Chunk size + |-------------------------------------------------------------------------- + | + | When using FromQuery, the query is automatically chunked. + | Here you can specify how big the chunk should be. + | + */ + 'chunk_size' => 1000, + + /* + |-------------------------------------------------------------------------- + | Pre-calculate formulas during export + |-------------------------------------------------------------------------- + */ + 'pre_calculate_formulas' => false, + + /* + |-------------------------------------------------------------------------- + | Enable strict null comparison + |-------------------------------------------------------------------------- + | + | When enabling strict null comparison empty cells ('') will + | be added to the sheet. + */ + 'strict_null_comparison' => false, + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV exports. + | + */ + 'csv' => [ + 'delimiter' => ',', + 'enclosure' => '"', + 'line_ending' => PHP_EOL, + 'use_bom' => false, + 'include_separator_line' => false, + 'excel_compatibility' => false, + 'output_encoding' => '', + 'test_auto_detect' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + ], + + 'imports' => [ + + /* + |-------------------------------------------------------------------------- + | Read Only + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might only be interested in the + | data that the sheet exists. By default we ignore all styles, + | however if you want to do some logic based on style data + | you can enable it by setting read_only to false. + | + */ + 'read_only' => true, + + /* + |-------------------------------------------------------------------------- + | Ignore Empty + |-------------------------------------------------------------------------- + | + | When dealing with imports, you might be interested in ignoring + | rows that have null values or empty strings. By default rows + | containing empty strings or empty values are not ignored but can be + | ignored by enabling the setting ignore_empty to true. + | + */ + 'ignore_empty' => false, + + /* + |-------------------------------------------------------------------------- + | Heading Row Formatter + |-------------------------------------------------------------------------- + | + | Configure the heading row formatter. + | Available options: none|slug|custom + | + */ + 'heading_row' => [ + 'formatter' => 'slug', + ], + + /* + |-------------------------------------------------------------------------- + | CSV Settings + |-------------------------------------------------------------------------- + | + | Configure e.g. delimiter, enclosure and line ending for CSV imports. + | + */ + 'csv' => [ + 'delimiter' => null, + 'enclosure' => '"', + 'escape_character' => '\\', + 'contiguous' => false, + 'input_encoding' => Csv::GUESS_ENCODING, + ], + + /* + |-------------------------------------------------------------------------- + | Worksheet properties + |-------------------------------------------------------------------------- + | + | Configure e.g. default title, creator, subject,... + | + */ + 'properties' => [ + 'creator' => '', + 'lastModifiedBy' => '', + 'title' => '', + 'description' => '', + 'subject' => '', + 'keywords' => '', + 'category' => '', + 'manager' => '', + 'company' => '', + ], + + /* + |-------------------------------------------------------------------------- + | Cell Middleware + |-------------------------------------------------------------------------- + | + | Configure middleware that is executed on getting a cell value + | + */ + 'cells' => [ + 'middleware' => [ + //\Maatwebsite\Excel\Middleware\TrimCellValue::class, + //\Maatwebsite\Excel\Middleware\ConvertEmptyCellValuesToNull::class, + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Extension detector + |-------------------------------------------------------------------------- + | + | Configure here which writer/reader type should be used when the package + | needs to guess the correct type based on the extension alone. + | + */ + 'extension_detector' => [ + 'xlsx' => Excel::XLSX, + 'xlsm' => Excel::XLSX, + 'xltx' => Excel::XLSX, + 'xltm' => Excel::XLSX, + 'xls' => Excel::XLS, + 'xlt' => Excel::XLS, + 'ods' => Excel::ODS, + 'ots' => Excel::ODS, + 'slk' => Excel::SLK, + 'xml' => Excel::XML, + 'gnumeric' => Excel::GNUMERIC, + 'htm' => Excel::HTML, + 'html' => Excel::HTML, + 'csv' => Excel::CSV, + 'tsv' => Excel::TSV, + + /* + |-------------------------------------------------------------------------- + | PDF Extension + |-------------------------------------------------------------------------- + | + | Configure here which Pdf driver should be used by default. + | Available options: Excel::MPDF | Excel::TCPDF | Excel::DOMPDF + | + */ + 'pdf' => Excel::DOMPDF, + ], + + /* + |-------------------------------------------------------------------------- + | Value Binder + |-------------------------------------------------------------------------- + | + | PhpSpreadsheet offers a way to hook into the process of a value being + | written to a cell. In there some assumptions are made on how the + | value should be formatted. If you want to change those defaults, + | you can implement your own default value binder. + | + | Possible value binders: + | + | [x] Maatwebsite\Excel\DefaultValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\StringValueBinder::class + | [x] PhpOffice\PhpSpreadsheet\Cell\AdvancedValueBinder::class + | + */ + 'value_binder' => [ + 'default' => Maatwebsite\Excel\DefaultValueBinder::class, + ], + + 'cache' => [ + /* + |-------------------------------------------------------------------------- + | Default cell caching driver + |-------------------------------------------------------------------------- + | + | By default PhpSpreadsheet keeps all cell values in memory, however when + | dealing with large files, this might result into memory issues. If you + | want to mitigate that, you can configure a cell caching driver here. + | When using the illuminate driver, it will store each value in the + | cache store. This can slow down the process, because it needs to + | store each value. You can use the "batch" store if you want to + | only persist to the store when the memory limit is reached. + | + | Drivers: memory|illuminate|batch + | + */ + 'driver' => 'memory', + + /* + |-------------------------------------------------------------------------- + | Batch memory caching + |-------------------------------------------------------------------------- + | + | When dealing with the "batch" caching driver, it will only + | persist to the store when the memory limit is reached. + | Here you can tweak the memory limit to your liking. + | + */ + 'batch' => [ + 'memory_limit' => 60000, + ], + + /* + |-------------------------------------------------------------------------- + | Illuminate cache + |-------------------------------------------------------------------------- + | + | When using the "illuminate" caching driver, it will automatically use + | your default cache store. However if you prefer to have the cell + | cache on a separate store, you can configure the store name here. + | You can use any store defined in your cache config. When leaving + | at "null" it will use the default store. + | + */ + 'illuminate' => [ + 'store' => null, + ], + + /* + |-------------------------------------------------------------------------- + | Cache Time-to-live (TTL) + |-------------------------------------------------------------------------- + | + | The TTL of items written to cache. If you want to keep the items cached + | indefinitely, set this to null. Otherwise, set a number of seconds, + | a \DateInterval, or a callable. + | + | Allowable types: callable|\DateInterval|int|null + | + */ + 'default_ttl' => 10800, + ], + + /* + |-------------------------------------------------------------------------- + | Transaction Handler + |-------------------------------------------------------------------------- + | + | By default the import is wrapped in a transaction. This is useful + | for when an import may fail and you want to retry it. With the + | transactions, the previous import gets rolled-back. + | + | You can disable the transaction handler by setting this to null. + | Or you can choose a custom made transaction handler here. + | + | Supported handlers: null|db + | + */ + 'transactions' => [ + 'handler' => 'db', + 'db' => [ + 'connection' => null, + ], + ], + + 'temporary_files' => [ + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path + |-------------------------------------------------------------------------- + | + | When exporting and importing files, we use a temporary file, before + | storing reading or downloading. Here you can customize that path. + | permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | + */ + 'local_path' => storage_path('framework/cache/laravel-excel'), + + /* + |-------------------------------------------------------------------------- + | Local Temporary Path Permissions + |-------------------------------------------------------------------------- + | + | Permissions is an array with the permission flags for the directory (dir) + | and the create file (file). + | If omitted the default permissions of the filesystem will be used. + | + */ + 'local_permissions' => [ + // 'dir' => 0755, + // 'file' => 0644, + ], + + /* + |-------------------------------------------------------------------------- + | Remote Temporary Disk + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup with queues in which you + | cannot rely on having a shared local temporary path, you might + | want to store the temporary file on a shared disk. During the + | queue executing, we'll retrieve the temporary file from that + | location instead. When left to null, it will always use + | the local path. This setting only has effect when using + | in conjunction with queued imports and exports. + | + */ + 'remote_disk' => null, + 'remote_prefix' => null, + + /* + |-------------------------------------------------------------------------- + | Force Resync + |-------------------------------------------------------------------------- + | + | When dealing with a multi server setup as above, it's possible + | for the clean up that occurs after entire queue has been run to only + | cleanup the server that the last AfterImportJob runs on. The rest of the server + | would still have the local temporary file stored on it. In this case your + | local storage limits can be exceeded and future imports won't be processed. + | To mitigate this you can set this config value to be true, so that after every + | queued chunk is processed the local temporary file is deleted on the server that + | processed it. + | + */ + 'force_resync_remote' => null, + ], +]; diff --git a/resources/js/data/advertisements/data-advertisements.js b/resources/js/data/advertisements/data-advertisements.js index 8b9401c..bade5e5 100644 --- a/resources/js/data/advertisements/data-advertisements.js +++ b/resources/js/data/advertisements/data-advertisements.js @@ -14,26 +14,22 @@ const dataAdvertisementsColumns = [ "Lokasi Reklame", "Desa", "Kecamatan", - "Panjang", - "Lebar", - "Sudut Pandang", - "Muka", - "Luas", - "Sudut", "Kontak", { name: "Actions", widht: "120px", formatter: function(cell, row) { - const id = row.cells[16].data; + const id = row.cells[10].data; const model = "data/advertisements"; return gridjs.html(`
- + data-model="${model}"> + + data-id="${id}"> +
`); } @@ -60,12 +56,6 @@ document.addEventListener("DOMContentLoaded", () => { item.advertisement_location, item.village_name, item.district_name, - item.length, - item.width, - item.viewing_angle, - item.face, - item.area, - item.angle, item.contact, item.id, ]; diff --git a/resources/js/data/advertisements/form-create-update.js b/resources/js/data/advertisements/form-create-update.js new file mode 100644 index 0000000..b22bb16 --- /dev/null +++ b/resources/js/data/advertisements/form-create-update.js @@ -0,0 +1,185 @@ +import GlobalConfig from "../../global-config"; + +document.addEventListener("DOMContentLoaded", function () { + const saveButton = document.querySelector(".modal-footer .btn-primary"); + const modalButton = document.querySelector(".btn-modal"); + const form = document.querySelector("form#create-update-form"); + var authLogo = document.querySelector(".auth-logo"); + + if (!saveButton || !form) return; + + saveButton.addEventListener("click", function () { + // Disable tombol dan tampilkan spinner + modalButton.disabled = true; + modalButton.innerHTML = ` + + Loading... + `; + const isEdit = saveButton.classList.contains("btn-edit"); + const formData = new FormData(form) + const toast = document.getElementById('toastEditUpdate'); + const toastBody = toast.querySelector('.toast-body'); + const toastHeader = toast.querySelector('.toast-header small'); + + const data = {}; + + // Mengonversi FormData ke dalam JSON + formData.forEach((value, key) => { + data[key] = value; + }); + + // Log semua data dalam FormData + for (let pair of formData.entries()) { + console.log(pair[0] + ": " + pair[1]); + } + const url = form.getAttribute("action"); + console.log("Ini adalah url dari form action", url); + + const method = isEdit ? "PUT" : "POST"; + + fetch(url, { + method: method, + body: JSON.stringify(data), + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + } + }) + .then(response => response.json()) + .then(data => { + console.log("Response data:", data); + if (!data.errors) { + // Remove existing icon (if any) before adding the new one + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector('.bx'); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement('i'); + icon.classList.add('bx', 'bxs-check-square'); + icon.style.fontSize = '25px'; + icon.style.color = 'green'; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + + // Set success message for the toast + toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!"; + toast.classList.add('show'); // Show the toast + setTimeout(() => { + toast.classList.remove('show'); // Hide the toast after 3 seconds + }, 3000); + + setTimeout(() => { + window.location.href = '/data/advertisements'; + }, 3000); + } else { + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector('.bx'); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement('i'); + icon.classList.add('bx', 'bxs-error-alt'); + icon.style.fontSize = '25px'; + icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + // Set error message for the toast + toastBody.textContent = "Error: " + (responseData.message || "Something went wrong"); + toast.classList.add('show'); // Show the toast + + // Enable button and reset its text on error + modalButton.disabled = false; + modalButton.innerHTML = isEdit ? "Update" : "Create"; + + setTimeout(() => { + toast.classList.remove('show'); // Hide the toast after 3 seconds + }, 3000); + } + }) + .catch(error => { + console.error("Error:", error); + if (authLogo) { + // Hapus ikon yang sudah ada jika ada + const existingIcon = authLogo.querySelector('.bx'); + if (existingIcon) { + authLogo.removeChild(existingIcon); + } + + // Buat ikon baru + const icon = document.createElement('i'); + icon.classList.add('bx', 'bxs-error-alt'); + icon.style.fontSize = '25px'; + icon.style.color = 'red'; // Pastikan 'green' dalam bentuk string + + // Tambahkan ikon ke dalam auth-logo + authLogo.appendChild(icon); + } + // Set error message for the toast + toastBody.textContent = "An error occurred while processing your request."; + toast.classList.add('show'); // Show the toast + + // Enable button and reset its text on error + modalButton.disabled = false; + modalButton.innerHTML = isEdit ? "Update" : "Create"; + + setTimeout(() => { + toast.classList.remove('show'); // Hide the toast after 3 seconds + }, 3000); + }); + }); + + // Fungsi fetchOptions untuk autocomplete server-side + window.fetchOptions = function (field) { + let inputValue = document.getElementById(field).value; + console.log("Query Value:", inputValue); // Debugging log + if (inputValue.length < 2) return; + let districtValue = document.getElementById("district_name").value; // Ambil kecamatan terpilih + + let url = `${GlobalConfig.apiHost}/api/combobox/search-options?query=${encodeURIComponent(inputValue)}&field=${field}`; + + // Jika field desa, tambahkan kecamatan sebagai filter + if (field === "village_name") { + url += `&district=${encodeURIComponent(districtValue)}`; + } + + fetch(url, { + method: 'GET', + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + } + }) + .then(response => response.json()) + .then(data => { + let dataList = document.getElementById(field + "Options"); + dataList.innerHTML = ""; + + data.forEach(item => { + let option = document.createElement("option"); + option.value = item.name; + option.dataset.code = item.code; + dataList.appendChild(option); + }); + }) + .catch(error => console.error("Error fetching options:", error)); + }; + + document.querySelector('.btn-back').addEventListener('click', function() { + window.history.back(); + }); +}); \ No newline at end of file diff --git a/resources/js/data/advertisements/form-upload.js b/resources/js/data/advertisements/form-upload.js new file mode 100644 index 0000000..b9c3521 --- /dev/null +++ b/resources/js/data/advertisements/form-upload.js @@ -0,0 +1,114 @@ +import { Dropzone } from "dropzone"; + +Dropzone.autoDiscover = false; + +var previewTemplate, + dropzone, + dropzonePreviewNode = document.querySelector("#dropzone-preview-list"); +console.log(previewTemplate); +console.log(dropzone); +console.log(dropzonePreviewNode); + +(dropzonePreviewNode.id = ""), + dropzonePreviewNode && + ((previewTemplate = dropzonePreviewNode.parentNode.innerHTML), + dropzonePreviewNode.parentNode.removeChild(dropzonePreviewNode), + (dropzone = new Dropzone(".dropzone", { + url: "http://localhost:8000/api/advertisements/import", + // url: "https://httpbin.org/post", + method: "post", + acceptedFiles: ".xls,.xlsx", // Use acceptedFiles for better validation + previewTemplate: previewTemplate, + previewsContainer: "#dropzone-preview", + autoProcessQueue: false, // Disable auto post + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}` + }, + init: function() { + // Listen for the success event + this.on("success", function(file, response) { + console.log("File successfully uploaded:", file); + console.log("API Response:", response); + + // Show success toast + showToast('bxs-check-square', 'green', response.message); + document.getElementById("submit-upload").innerHTML = "Upload Files"; + // Tunggu sebentar lalu reload halaman + setTimeout(() => { + window.location.href = "/data/advertisements"; + }, 2000); + }); + // Listen for the error event + this.on("error", function(file, errorMessage) { + console.error("Error uploading file:", file); + console.error("Error message:", errorMessage); + // Handle the error response + + // Show error toast + showToast('bxs-error-alt', 'red', errorMessage.message); + document.getElementById("submit-upload").innerHTML = "Upload Files"; + }); + } + }))); + +// Add event listener to control the submission manually +document.querySelector("#submit-upload").addEventListener("click", function() { + console.log("Ini adalah value dropzone", dropzone.files[0]); + const formData = new FormData() + console.log("Dropzonefiles",dropzone.files); + + this.innerHTML = 'Loading...'; + + // Pastikan ada file dalam queue sebelum memprosesnya + if (dropzone.files.length > 0) { + formData.append('file', dropzone.files[0]) + console.log("ini adalah form data on submit", ...formData); + dropzone.processQueue(); // Ini akan manual memicu upload + } else { + // Show error toast when no file is selected + showToast('bxs-error-alt', 'red', "Please add a file first."); + document.getElementById("submit-upload").innerHTML = "Upload Files"; + } +}); + +// Optional: Listen for the 'addedfile' event to log or control file add behavior +dropzone.on("addedfile", function (file) { + console.log("File ditambahkan:", file); + console.log("Nama File:", file.name); + console.log("Tipe File:", file.type); + console.log("Ukuran File:", (file.size / 1024).toFixed(2) + " KB"); +}); + +dropzone.on("complete", function(file) { + dropzone.removeFile(file); +}); + +// Function to show toast +function showToast(iconClass, iconColor, message) { + const toastElement = document.getElementById('toastUploadAdvertisement'); + const toastBody = toastElement.querySelector('.toast-body'); + const toastHeader = toastElement.querySelector('.toast-header'); + + // Remove existing icon (if any) before adding the new one + const existingIcon = toastHeader.querySelector('.bx'); + if (existingIcon) { + toastHeader.querySelector('.auth-logo').removeChild(existingIcon); // Remove the existing icon + } + + // Add the new icon to the toast header + const icon = document.createElement('i'); + icon.classList.add('bx', iconClass); + icon.style.fontSize = '25px'; + icon.style.color = iconColor; + toastHeader.querySelector('.auth-logo').appendChild(icon); + + // Set the toast message + toastBody.textContent = message; + + // Show the toast + const toast = new bootstrap.Toast(toastElement); // Inisialisasi Bootstrap Toast + toast.show(); +} + diff --git a/resources/js/form-create-update.js b/resources/js/form-create-update.js deleted file mode 100644 index 9caac6c..0000000 --- a/resources/js/form-create-update.js +++ /dev/null @@ -1,93 +0,0 @@ -document.addEventListener("DOMContentLoaded", function () { - const saveButton = document.querySelector(".modal-footer .btn-primary"); - const modalButton = document.querySelector(".btn-modal"); - const form = document.querySelector("form#create-update-form"); - - if (!saveButton || !form) return; - - saveButton.addEventListener("click", function () { - // Disable tombol dan tampilkan spinner - modalButton.disabled = true; - modalButton.innerHTML = ` - - Loading... - `; - const isEdit = saveButton.classList.contains("btn-edit"); - const formData = new FormData(form) - const toast = document.getElementById('toastEditUpdate'); - const toastBody = toast.querySelector('.toast-body'); - const toastHeader = toast.querySelector('.toast-header small'); - - const data = {}; - - // Mengonversi FormData ke dalam JSON - formData.forEach((value, key) => { - data[key] = value; - }); - - // Log semua data dalam FormData - for (let pair of formData.entries()) { - console.log(pair[0] + ": " + pair[1]); - } - const url = form.getAttribute("action"); - console.log("Ini adalah url dari form action", url); - - const method = isEdit ? "PUT" : "POST"; - - fetch(url, { - method: method, - body: JSON.stringify(data), - headers: { - Authorization: `Bearer ${document - .querySelector('meta[name="api-token"]') - .getAttribute("content")}`, - "Content-Type": "application/json", - } - }) - .then(response => response.json()) - .then(data => { - console.log("Response data:", data); - if (!data.errors) { - // Set success message for the toast - toastBody.textContent = isEdit ? "Data updated successfully!" : "Data created successfully!"; - toastHeader.textContent = "Just now"; - toast.classList.add('show'); // Show the toast - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 3000); - - setTimeout(() => { - window.location.reload(); - }, 3000); - } else { - // Set error message for the toast - toastBody.textContent = "Error: " + (responseData.message || "Something went wrong"); - toastHeader.textContent = "Error occurred"; - toast.classList.add('show'); // Show the toast - - // Enable button and reset its text on error - modalButton.disabled = false; - modalButton.innerHTML = isEdit ? "Update" : "Create"; - - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 3000); - } - }) - .catch(error => { - console.error("Error:", error); - // Set error message for the toast - toastBody.textContent = "An error occurred while processing your request."; - toastHeader.textContent = "Error occurred"; - toast.classList.add('show'); // Show the toast - - // Enable button and reset its text on error - modalButton.disabled = false; - modalButton.innerHTML = isEdit ? "Update" : "Create"; - - setTimeout(() => { - toast.classList.remove('show'); // Hide the toast after 3 seconds - }, 3000); - }); - }); -}); \ No newline at end of file diff --git a/resources/js/table-generator.js b/resources/js/table-generator.js index 6500379..00febe4 100644 --- a/resources/js/table-generator.js +++ b/resources/js/table-generator.js @@ -57,11 +57,13 @@ class GeneralTable { if (event.target && event.target.classList.contains('btn-edit')) { this.handleEdit(event); } - if (event.target && event.target.classList.contains('btn-delete')) { + else if (event.target && event.target.classList.contains('btn-delete')) { this.handleDelete(event); } - if (event.target && event.target.classList.contains('btn-create')) { + else if (event.target && event.target.classList.contains('btn-create')) { this.handleCreate(event); + } else if (event.target && event.target.classList.contains('btn-bulk-create')) { + this.handleBulkCreate(event); } }); } @@ -73,6 +75,12 @@ class GeneralTable { window.location.href = `${this.baseUrl}/${model}/create`; } + handleBulkCreate(event) { + // Menggunakan model dan ID untuk membangun URL dinamis + const model = event.target.getAttribute('data-model'); + window.location.href = `${this.baseUrl}/${model}/bulk-create`; + } + // Fungsi untuk menangani edit handleEdit(event) { const id = event.target.getAttribute('data-id'); diff --git a/resources/views/data/advertisements/form-upload.blade.php b/resources/views/data/advertisements/form-upload.blade.php new file mode 100644 index 0000000..1056274 --- /dev/null +++ b/resources/views/data/advertisements/form-upload.blade.php @@ -0,0 +1,91 @@ +@extends('layouts.vertical', ['subtitle' => 'File Uploads']) + +@section('content') + +@include('layouts.partials/page-title', ['title' => 'Form', 'subtitle' => 'File Uploads']) + +
+
+
+
+
Upload Data Reklame
+

+ Please upload a file with the extension .xls or .xlsx with a maximum size of 10 MB. +
+ For .xls and .xlsx files, ensure that the data is contained within a single sheet with the following columns: + No, Nama Wajib Pajak, NPWPD, Jenis Reklame, Isi Reklame, Alamat Wajib Pajak, Lokasi Reklame, Desa, + Kecamatan, Panajang, Lebar, Sudut Pandang, Muka, Luas, Sudut, Kontak. +

+
+ +
+ +
+ +
+
+
+ +
+
+
+ +

Drop files here or click to upload.

+
+
+ +
    +
  • + +
    +
    +
    +
    + +
    +
    +
    +
    +
      +
    +

    + +
    +
    +
    + +
    +
    +
    +
  • +
+ +
+
+ +
+
+
+
+
+ +
+ +
+ +@endsection + +@section('scripts') +@vite(['resources/js/data/advertisements/form-upload.js']) +@endsection diff --git a/resources/views/data/advertisements/form.blade.php b/resources/views/data/advertisements/form.blade.php new file mode 100644 index 0000000..3482aa2 --- /dev/null +++ b/resources/views/data/advertisements/form.blade.php @@ -0,0 +1,120 @@ +@extends('layouts.vertical', ['subtitle' => $subtitle]) + +@section('content') + +@include('layouts.partials/page-title', ['title' => $title, 'subtitle' => $subtitle]) + +
+ @if (session('error')) +
+ {{ session('error') }} +
+ @endif + + @if ($errors->any()) +
+ +
+ @endif + +
+
+
+ +
+
+
+ @csrf + @if(isset($modelInstance)) + @method('PUT') + @endif + +
+ @foreach($fields as $field => $label) +
+ + @php + $fieldType = $fieldTypes[$field] ?? 'text'; // Default text jika tidak ditemukan tipe + @endphp + + @if($fieldType == 'textarea') + + @elseif($fieldType == 'select' && isset($dropdownOptions[$field])) + + @elseif($fieldType == 'combobox' && isset($dropdownOptions[$field])) + + + @else + + @endif +
+ @endforeach +
+ +
+ +
+
+
+
+
+
+ + + + + +
+ +
+ +@endsection + +@section('scripts') +@vite(['resources/js/data/advertisements/form-create-update.js']) +@endsection \ No newline at end of file diff --git a/resources/views/data/advertisements/index.blade.php b/resources/views/data/advertisements/index.blade.php index a33a860..da7898b 100644 --- a/resources/views/data/advertisements/index.blade.php +++ b/resources/views/data/advertisements/index.blade.php @@ -8,16 +8,30 @@ @include('layouts.partials/page-title', ['title' => 'Data', 'subtitle' => 'Reklame']) -
-
- - + +
+
+
Daftar Reklame
-
-
+
+
+
+ + +
+
+
+
+
+ @endsection @section('scripts') diff --git a/resources/views/form-create-update/form.blade.php b/resources/views/form-create-update/form.blade.php deleted file mode 100644 index e075973..0000000 --- a/resources/views/form-create-update/form.blade.php +++ /dev/null @@ -1,117 +0,0 @@ -@extends('layouts.vertical', ['subtitle' => $subtitle]) - -@section('content') - -@include('layouts.partials/page-title', ['title' => $title, 'subtitle' => $subtitle]) - -
- @if (session('error')) -
- {{ session('error') }} -
- @endif - - @if ($errors->any()) -
-
    - @foreach ($errors->all() as $error) -
  • {{ $error }}
  • - @endforeach -
-
- @endif -
-
-
-
- @csrf - @if(isset($modelInstance)) - @method('PUT') - @endif - - @foreach($fields as $field => $label) -
- - @php - $fieldType = $fieldTypes[$field] ?? 'text'; // Default text jika tidak ditemukan tipe - @endphp - - @if($fieldType == 'textarea') - - @elseif($fieldType == 'select' && isset($dropdownOptions[$field])) - - @elseif($fieldType == 'combobox' && isset($dropdownOptions[$field])) - - - @foreach($dropdownOptions[$field] as $code => $name) - - @else - - @endif -
- @endforeach -
- -
-
-
-
-
-
- - - - -
- -
- -@endsection - -@section('scripts') -@vite(['resources/js/form-create-update.js']) -@endsection \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 9a83058..16f4c4b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -43,6 +43,8 @@ Route::group(['middleware' => 'auth:sanctum'], function (){ // reklame Route::apiResource('advertisements', AdvertisementController::class); + Route::get('/combobox/search-options', [AdvertisementController::class, 'searchOptionsInAdvertisements']); + Route::post('/advertisements/import', [AdvertisementController::class, 'importFromFile']); }); diff --git a/routes/web.php b/routes/web.php index 670b664..6dc62ac 100755 --- a/routes/web.php +++ b/routes/web.php @@ -43,6 +43,11 @@ Route::group(['middleware' => 'auth'], function(){ // data Route::group(['prefix' => '/data'], function(){ - Route::resource('/advertisements', AdvertisementController::class); + // Resource route, kecuali create karena dibuat terpisah + Route::resource('/advertisements', AdvertisementController::class)->except(['create', 'show']); + + // Rute khusus untuk create dan bulk-create + Route::get('/advertisements/create', [AdvertisementController::class, 'create'])->name('advertisements.create'); + Route::get('/advertisements/bulk-create', [AdvertisementController::class, 'bulkCreate'])->name('advertisements.bulk-create'); }); }); \ No newline at end of file