fixing perusahaan

main
marszayn 2025-02-26 08:24:55 +07:00
parent 0562099ca0
commit 08c45c61ca
44 changed files with 2868 additions and 440 deletions

View File

@ -1,64 +0,0 @@
APP_NAME=Laravel
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=UTC
APP_URL=http://localhost
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=sqlite
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}"

View File

@ -63,4 +63,17 @@ class JenisKegiatanController extends Controller
return back()->with('error', 'Something went wrong.');
}
}
public function getAll()
{
try {
$jeniskegiatan = JenisKegiatan::where('IsPublish', true)
->select('JenisKegiatanId', 'NamaJenisKegiatan')
->get();
return response()->json($jeniskegiatan);
} catch (\Exception $e) {
Log::error('Error fetching all Jenis Kegiatan: ' . $e->getMessage());
return response()->json(['error' => 'Something went wrong'], 500);
}
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Inertia\Inertia;
class PelaporanALController extends Controller
{
public function index()
{
try {
return Inertia::render('admin/pelaporan/AL/index_AL');
} catch (\Exception $e) {
Log::error('Error rendering view: ' . $e->getMessage());
return back()->with('error', 'Something went wrong.');
}
}
}

View File

@ -0,0 +1,178 @@
<?php
namespace App\Http\Controllers;
use App\Models\Perusahaan;
use App\Models\JenisKegiatan;
use App\Http\Requests\PerusahaanRequest;
use App\Models\JenisDokIL;
use App\Models\Kabupaten;
use App\Models\Kecamatan;
use App\Models\Kelurahan;
use App\Models\Verifikator;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Inertia\Inertia;
use Illuminate\Support\Facades\Session;
class PerusahaanController extends Controller
{
public function index()
{
try {
$perusahaan = Perusahaan::with('jenisKegiatan', 'kelurahan.kecamatan.kabupaten', 'verifikator', 'jenisDokIL')->get();
return Inertia::render('admin/perusahaan/index_perusahaan', [
'perusahaan' => $perusahaan,
'jenisKegiatan' => JenisKegiatan::all(),
'jenisDokIL' => JenisDokIL::all(),
'verifikator' => Verifikator::all(),
'kabupaten' => Kabupaten::all(),
'kecamatan' => Kecamatan::all(),
'kelurahan' => Kelurahan::all(),
]);
} catch (\Exception $e) {
Log::error('Error fetching data: ' . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan saat memuat data.');
}
}
// public function store(Request $request)
// {
// $request->validate([
// 'ILDokumen' => 'required|file|mimes:pdf|max:20480',
// ]);
// try {
// return DB::transaction(function () use ($request) {
// if ($request->hasFile('ILDokumen')) {
// $file = $request->file('ILDokumen');
// $fileName = time() . '_' . $file->getClientOriginalName();
// if (!Storage::exists('public/files/il')) {
// Storage::makeDirectory('public/files/il');
// }
// $path = $file->storeAs('files/il', $fileName, 'public');
// $request->merge(['ILDokumen' => $path]);
// }
// $perusahaan = Perusahaan::create($request->all());
// return response()->json([
// 'message' => 'Perusahaan berhasil ditambahkan',
// 'data' => $perusahaan
// ]);
// });
// } catch (\Exception $e) {
// if (isset($path) && Storage::exists($path)) {
// Storage::delete($path);
// }
// return response()->json([
// 'message' => 'Error: ' . $e->getMessage()
// ], 500);
// }
// }
public function store(Request $request)
{
$request->validate([
'NomorInduk' => 'required|string|unique:Perusahaan',
'NamaPerusahaan' => 'required|string',
'JenisKegiatanId' => 'required|string',
'VerifikatorId' => 'required|string',
'KelurahanId' => 'required|string',
'Email' => 'required|email',
]);
try {
DB::beginTransaction();
$data = $request->all();
if ($request->hasFile('ILDokumen')) {
$file = $request->file('ILDokumen');
if (!$file->isValid()) {
throw new \Exception('Invalid PDF file');
}
$fileName = time() . '_' . $file->getClientOriginalName();
$path = $file->storeAs('files/il', $fileName, 'public');
if ($path === false) {
throw new \Exception('Failed to store PDF file');
}
$data['ILDokumen'] = $path;
}
// Convert boolean string to actual boolean
$data['IsPublish'] = filter_var($request->input('IsPublish'), FILTER_VALIDATE_BOOLEAN);
$data['ReportLocked'] = filter_var($request->input('ReportLocked'), FILTER_VALIDATE_BOOLEAN);
$perusahaan = Perusahaan::create($data);
DB::commit();
return redirect()
->route('admin.perusahaan.index')
->with('success', 'Perusahaan berhasil ditambahkan');
} catch (\Exception $e) {
DB::rollBack();
if (isset($path) && Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
Log::error('Error creating perusahaan: ' . $e->getMessage());
return response()->json([
'message' => 'Error: ' . $e->getMessage()
], 500);
}
}
public function show(Perusahaan $perusahaan): JsonResponse
{
$perusahaan->load([
'JenisKegiatan',
'Kelurahan',
'JenisDokIL',
'Verifikator',
'Kawasan'
]);
return response()->json([
'status' => 'success',
'data' => $perusahaan
]);
}
public function update(PerusahaanRequest $request, Perusahaan $perusahaan): JsonResponse
{
$perusahaan->update($request->validated());
return response()->json([
'status' => 'success',
'message' => 'Data perusahaan berhasil diperbarui',
'data' => $perusahaan
]);
}
public function destroy(Perusahaan $perusahaan): JsonResponse
{
$perusahaan->delete();
return response()->json([
'status' => 'success',
'message' => 'Data perusahaan berhasil dihapus'
]);
}
}

View File

@ -95,41 +95,52 @@ class PostController extends Controller
public function edit(Post $post)
{
// Debug the image path
Log::info('Image path:', ['path' => $post->ImagePost]);
return Inertia::render('admin/post/edit_post', [
'post' => $post,
'posting' => [
...$post->toArray(),
'ImagePost' => $post->ImagePost ? '/storage/' . $post->ImagePost : null,
],
'kategori' => Kategori::all(),
'subkategori' => SubKategori::where('KategoriId', $post->KategoriId)->get(),
'subkategori' => SubKategori::all(),
'existingPosts' => Post::where('PostId', '!=', $post->PostId)
->select('JudulPost', 'KategoriId')
->get()
]);
}
public function update(PostRequest $request, Post $post)
{
try {
$data = array_filter($request->validated(), function($value) {
return $value !== null;
});
DB::beginTransaction();
$data = $request->validated();
// Only update image if new one is uploaded
if ($request->hasFile('ImagePost')) {
// Delete old image if exists
if ($post->ImagePost && Storage::disk('public')->exists($post->ImagePost)) {
Storage::disk('public')->delete($post->ImagePost);
}
$data['ImagePost'] = $request->file('ImagePost')->store('images/posts', 'public');
} else {
// Keep existing image if no new one uploaded
unset($data['ImagePost']);
}
if (isset($data['IsPublish'])) {
$data['IsPublish'] = (bool) $data['IsPublish'];
}
$data['IsPublish'] = $request->boolean('IsPublish');
$post->update($data);
return redirect()
->route('admin.post.index')
->with('success', 'Post berhasil diperbarui.');
DB::commit();
return redirect()->route('admin.post.index')->with('success', 'Post berhasil diperbarui.');
} catch (\Exception $e) {
DB::rollBack();
Log::error('Error updating Post: ' . $e->getMessage());
return back()->with('error', 'Terjadi kesalahan saat memperbarui post.');
}
}
public function destroy(Post $post)

View File

@ -23,7 +23,6 @@ class JenisDokILRequest extends FormRequest
{
return [
'NamaJenisDokIL' => 'required|string|max:255',
'KodeJenisDokIL' => 'required|string|max:20',
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PerusahaanRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'NomorInduk' => ['string'],
'JenisKegiatanId' => ['required', 'integer'],
'NamaPerusahaan' => ['required', 'string'],
'Alamat' => ['string'],
'KelurahanId' => ['required', 'integer'],
'KodePos' => ['string'],
'Telepon' => ['string'],
'Fax' => ['string'],
'Email' => ['required', 'string'],
'Lintang' => ['string'],
'Bujur' => ['string'],
'CPNama' => ['string'],
'CPTelepon' => ['string'],
'ILNomor' => ['string'],
'ILTanggal' => ['string'],
'JenisDokILId' => ['integer'],
'VerifikatorId' => ['required', 'integer'],
'IsPublish' => ['required', 'boolean'],
'ILDokumen' => ['string'],
'Kawasan' => ['string'],
];
}
}

View File

@ -9,7 +9,6 @@ class JenisDokIL extends Model
protected $table = 'JenisDokIL';
protected $primaryKey = 'JenisDokILId';
protected $fillable = [
'KodeJenisDokIL',
'NamaJenisDokIL'
];
}

View File

@ -21,4 +21,9 @@ class JenisKegiatan extends Model
'IsPublish',
];
public function Perusahaan()
{
return $this->hasMany(Perusahaan::class, 'JenisKegiatanId');
}
}

View File

@ -2,16 +2,20 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
class Perusahaan extends Model
{
use HasFactory;
protected $table = 'Perusahaan';
protected $primaryKey = 'PerusahaanId';
protected $fillable = [
'NomorInduk',
'NomorInduk',
'JenisKegiatanId',
'NamaPerusahaan',
'Alamat',
@ -28,57 +32,67 @@ class Perusahaan extends Model
'ILTanggal',
'JenisDokILId',
'VerifikatorId',
'ReportLocked',
'IsPublish',
'ILDokumen',
'ILPdlNomor',
'ILPdlJenis',
'DocPdlOrig',
'DocPdlHash',
'DocPdlPath',
'ILPdlTanggal',
'Kawasan'
'ReportLocked'
];
protected $casts = [
'ReportLocked' => 'boolean',
'IsPublish' => 'boolean',
'ILPdlTanggal' => 'date',
'JenisKegiatanId' => 'integer',
'VerifikatorId' => 'integer',
'KelurahanId' => 'integer',
];
// Relationship with JenisKegiatan
public function jenisKegiatan(): BelongsTo
public function jenisKegiatan()
{
return $this->belongsTo(JenisKegiatan::class, 'JenisKegiatanId', 'JenisKegiatanId');
}
// Relationship with Kelurahan
public function kelurahan(): BelongsTo
protected $with = ['JenisKegiatan', 'Kelurahan'];
public function kelurahan()
{
return $this->belongsTo(Kelurahan::class, 'KelurahanId', 'KelurahanId');
}
public function kecamatan()
{
return $this->hasOneThrough(
Kecamatan::class,
Kelurahan::class,
'KelurahanId', // Foreign key di tabel Kelurahan
'KecamatanId', // Foreign key di tabel Kecamatan
'KelurahanId', // Foreign key di tabel Perusahaan
'KecamatanId' // Foreign key di tabel Kelurahan
);
}
public function kabupaten()
{
return $this->hasOneThrough(
Kabupaten::class,
Kecamatan::class,
'KecamatanId', // Foreign key di tabel Kecamatan
'KabupatenId', // Foreign key di tabel Kabupaten
'KelurahanId', // Foreign key di tabel Perusahaan
'KabupatenId' // Foreign key di tabel Kecamatan
);
}
public function verifikator()
{
return $this->belongsTo(Verifikator::class, 'VerifikatorId');
}
// Relationship with JenisDokIL
public function jenisDokIL(): BelongsTo
public function jenisDokIL()
{
return $this->belongsTo(JenisDokIL::class, 'JenisDokILId', 'JenisDokILId');
}
// Relationship with JenisDokIL for PDL
public function jenisDokILPdl(): BelongsTo
{
return $this->belongsTo(JenisDokIL::class, 'ILPdlJenis', 'JenisDokILId');
}
// Relationship with Verifikator
public function verifikator(): BelongsTo
{
return $this->belongsTo(Verifikator::class, 'VerifikatorId', 'VerifikatorId');
}
// Self-referential relationship for Kawasan
public function kawasan(): BelongsTo
{
return $this->belongsTo(Perusahaan::class, 'Kawasan', 'PerusahaanId');
}
}

View File

@ -14,6 +14,7 @@ return new class extends Migration
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('username')->unique();
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');

View File

@ -13,7 +13,7 @@ return new class extends Migration
{
Schema::create('Kategori', function (Blueprint $table) {
$table->id('KategoriId');
$table->string('NamaKategori')->unique();
$table->string('NamaKategori');
$table->softDeletes();
$table->timestamps();
});

View File

@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique()->after('name');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('username');
});
}
};

View File

@ -19,6 +19,7 @@ return new class extends Migration
$table->string('SlugPost');
$table->text('DescPost');
$table->string('ImagePost')->nullable();
$table->boolean('IsPublish')->default(1);
$table->timestamps();

View File

@ -1,29 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('Post', function (Blueprint $table) {
$table->boolean('IsPublish')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('Post', function (Blueprint $table) {
$table->dropColumn('IsPublish');
});
}
};

View File

@ -11,10 +11,11 @@ return new class extends Migration
*/
public function up(): void
{
Schema::create('historykegiatan', function (Blueprint $table) {
Schema::create('HistoryKegiatan', function (Blueprint $table) {
$table->id('HistoryKegiatanId');
$table->string('NamaHistoryKegiatan');
$table->boolean('IsPublish')->default(0);
$table->boolean('IsPublish')->default(1);
$table->softDeletes();
$table->timestamps();
});
}
@ -24,6 +25,6 @@ return new class extends Migration
*/
public function down(): void
{
Schema::dropIfExists('historykegiatan');
Schema::dropIfExists('HistoryKegiatan');
}
};

View File

@ -11,10 +11,11 @@ return new class extends Migration
*/
public function up(): void
{
Schema::create('jeniskegiatan', function (Blueprint $table) {
Schema::create('JenisKegiatan', function (Blueprint $table) {
$table->id('JenisKegiatanId');
$table->string('NamaJenisKegiatan');
$table->boolean('IsPublish')->default(0);
$table->boolean('IsPublish')->default(1);
$table->softDeletes();
$table->timestamps();
});
}
@ -24,6 +25,6 @@ return new class extends Migration
*/
public function down(): void
{
Schema::dropIfExists('jeniskegiatan');
Schema::dropIfExists('JenisKegiatan');
}
};

View File

@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('JenisKegiatan', function (Blueprint $table) {
$table->softDeletes()->after('IsPublish');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('JenisKegiatan', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};

View File

@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('HistoryKegiatan', function (Blueprint $table) {
$table->softDeletes()->after('IsPublish');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('HistoryKegiatan', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};

View File

@ -1,28 +0,0 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('Kategori', function (Blueprint $table) {
$table->dropUnique(['NamaKategori']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('Kategori', function (Blueprint $table) {
$table->unique('NamaKategori');
});
}
};

View File

@ -30,22 +30,13 @@ return new class extends Migration
$table->string('CPTelepon')->nullable();
$table->string('ILNomor')->nullable();
$table->string('ILTanggal')->nullable();
$table->string('ILDokumen')->nullable();
$table->unsignedBigInteger('JenisDokILId');
$table->foreign('JenisDokILId')->references('JenisDokILId')->on('JenisDokIL')->onDelete('cascade');
$table->unsignedBigInteger('VerifikatorId');
$table->foreign('VerifikatorId')->references('VerifikatorId')->on('Verifikator')->onDelete('cascade');
$table->boolean('ReportLocked')->default(1);
$table->boolean('IsPublish')->default(0);
$table->string('ILDokumen')->nullable();
$table->string('ILPdlNomor')->default('n')->comment('Dok IL versi PDL');
$table->unsignedInteger('ILPdlJenis')->nullable()->comment('id dari refjenisdokil');
$table->foreign('ILPdlJenis')->references('JenisDokILId')->on('JenisDokIL')->onDelete('set null');
$table->string('DocPdlOrig')->nullable()->comment('nama file original');
$table->string('DocPdlHash')->nullable()->comment('nama file hash');
$table->string('DocPdlPath')->nullable()->comment('upload path');
$table->date('ILPdlTanggal')->nullable();
$table->unsignedInteger('Kawasan')->nullable()->comment('id dari perusahaan, digunakan utk pengelola kawasan');
$table->foreign('Kawasan')->references('PerusahaanId')->on('Perusahaan')->onDelete('set null');
$table->boolean('IsPublish')->default(1);
$table->timestamps();

View File

@ -13,7 +13,6 @@ return new class extends Migration
{
Schema::create('JenisDokIL', function (Blueprint $table) {
$table->id('JenisDokILId');
$table->string('KodeJenisDokIL')->unique();
$table->string('NamaJenisDokIL');
$table->timestamps();
});

View File

@ -16,6 +16,7 @@ return new class extends Migration
$table->string('NamaUnitKerja');
$table->string('NamaKepala');
$table->string('NIP');
$table->softDeletes();
$table->timestamps();
});
}

74
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "DLH-SKL",
"name": "skl2025",
"lockfileVersion": 3,
"requires": true,
"packages": {
@ -8,6 +8,7 @@
"@iconify/react": "^5.2.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.2",
@ -1240,6 +1241,77 @@
}
}
},
"node_modules/@radix-ui/react-checkbox": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz",
"integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.2.tgz",
"integrity": "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
"integrity": "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",

View File

@ -28,6 +28,7 @@
"@iconify/react": "^5.2.0",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-dropdown-menu": "^2.1.2",

View File

@ -1,13 +1,17 @@
"use client";
import { TrendingUp } from "lucide-react";
import { Bar, BarChart, CartesianGrid, XAxis } from "recharts";
import { useState } from "react";
import { TrendingUp, BarChart2 } from "lucide-react";
import {
Bar,
BarChart,
CartesianGrid,
XAxis,
YAxis,
ResponsiveContainer,
} from "recharts";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
@ -17,10 +21,11 @@ import {
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
const chartData = [
{ month: "Genset", desktop: 186 },
{ month: "Boiler", desktop: 305 },
{ month: "Proses", desktop: 237 },
{ month: "Genset", desktop: 186, color: "#22c55e" },
{ month: "Boiler", desktop: 305, color: "#16a34a" },
{ month: "Proses", desktop: 237, color: "#15803d" },
];
const chartConfig = {
@ -31,40 +36,151 @@ const chartConfig = {
} satisfies ChartConfig;
export function ChartCard() {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const handleMouseEnter = (index: number) => {
setActiveIndex(index);
};
const handleMouseLeave = () => {
setActiveIndex(null);
};
return (
<Card>
<CardHeader>
<CardTitle>Sumber Emisi</CardTitle>
{/* <CardDescription>January - June 2024</CardDescription> */}
<Card className="relative overflow-hidden transition-all duration-300 hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-green-50/30 to-transparent dark:from-green-950/30" />
<CardHeader className="relative">
<div className="flex items-center gap-2">
<BarChart2 className="h-6 w-6 text-green-600" />
<CardTitle className="text-xl font-bold">
Sumber Emisi
</CardTitle>
</div>
<CardDescription className="text-sm text-green-600 font-medium">
Total Emisi per Sumber
</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey="month"
tickLine={false}
tickMargin={10}
axisLine={false}
// tickFormatter={(value) => value.slice(0, 3)}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar dataKey="desktop" fill="#53a946" radius={8} />
</BarChart>
</ChartContainer>
<CardContent className="relative pb-6">
<div className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<BarChart data={chartData}>
<defs>
{chartData.map((entry, index) => (
<linearGradient
key={`gradient-${index}`}
id={`barGradient-${index}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={entry.color}
stopOpacity={0.8}
/>
<stop
offset="100%"
stopColor={entry.color}
stopOpacity={0.3}
/>
</linearGradient>
))}
</defs>
<CartesianGrid
vertical={false}
stroke="#e5e7eb"
strokeDasharray="4 4"
/>
<XAxis
dataKey="month"
tickLine={false}
axisLine={false}
tick={{ fill: "#6b7280", fontSize: 12 }}
tickMargin={12}
/>
<YAxis
axisLine={false}
tickLine={false}
tick={{ fill: "#6b7280", fontSize: 12 }}
tickMargin={8}
/>
<ChartTooltip
cursor={false}
content={({ active, payload }) => {
if (active && payload && payload.length) {
return (
<div className="rounded-lg bg-white p-3 shadow-lg border border-green-100 backdrop-blur-sm">
<p className="font-medium text-green-800">
{payload[0].payload.month}
</p>
<p className="text-sm text-green-600">
{payload[0].value} ton CO
</p>
</div>
);
}
return null;
}}
/>
<Bar
dataKey="desktop"
radius={[8, 8, 0, 0]}
onMouseEnter={(data, index) =>
handleMouseEnter(index)
}
onMouseLeave={handleMouseLeave}
>
{chartData.map((entry, index) => (
<rect
key={`bar-${index}`}
fill={`url(#barGradient-${index})`}
className={`transition-all duration-300 ${
activeIndex === index
? "opacity-100 scale-y-105"
: activeIndex === null
? "opacity-90"
: "opacity-50"
}`}
/>
))}
</Bar>
</BarChart>
</ResponsiveContainer>
</div>
<div className="mt-6 flex justify-between items-center px-4">
{chartData.map((item, index) => (
<div
key={item.month}
className="flex flex-col items-center gap-2"
onMouseEnter={() => handleMouseEnter(index)}
onMouseLeave={handleMouseLeave}
>
<div
className={`h-3 w-3 rounded-full transition-all duration-300 ${
activeIndex === index
? "scale-150"
: activeIndex === null
? "scale-100"
: "scale-75 opacity-50"
}`}
style={{ backgroundColor: item.color }}
/>
<span className="text-sm font-medium text-gray-600">
{item.month}
</span>
</div>
))}
</div>
</CardContent>
{/* <CardFooter className="flex-col items-start gap-2 text-sm">
<div className="flex gap-2 font-medium leading-none">
Trending up by 5.2% this month{" "}
<TrendingUp className="h-4 w-4" />
</div>
<div className="leading-none text-muted-foreground">
Showing total visitors for the last 6 months
</div>
</CardFooter> */}
</Card>
);
}

View File

@ -97,7 +97,7 @@ const AnnouncementSection = ({ posts }: CardPengumumanProps) => {
{/* List of Announcements */}
{posts.length > 1 && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{posts.slice(1).map((post) => (
{posts.slice(1, 4).map((post) => (
<Card key={post.PostId} className="p-4">
<img
src={`/storage/${post.ImagePost}`}

View File

@ -38,7 +38,7 @@ const SearchDialog: React.FC = () => {
<DialogContent className="bg-white text-black p-6 rounded-lg w-[90%] max-w-md">
<DialogHeader>
<DialogTitle className="text-lg font-bold text-center">
Search
Ketik Pencarianmu di sini...
</DialogTitle>
</DialogHeader>
<form

View File

@ -55,7 +55,7 @@ const UndanganSection = ({ undangan }: CardUndanganProps) => {
{/* List of Announcements */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{undangan.map((item) => (
{undangan.slice(0, 3).map((item) => (
<Card key={item.PostId} className="md:p-4">
<img
src={`/storage/${item.ImagePost}`}

View File

@ -0,0 +1,812 @@
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import Select from "react-select";
import { useState } from "react";
import { Checkbox } from "@/components/ui/checkbox";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { format } from "date-fns";
import { cn } from "@/lib/utils";
import {
Calendar as CalendarIcon,
FileText,
Hotel,
LocateFixed,
MapPinned,
UsersRound,
Verified,
VerifiedIcon,
} from "lucide-react";
import { Separator } from "../ui/separator";
import { Textarea } from "../ui/textarea";
import {
JenisDokIL,
JenisKegiatan,
Verifikator,
Kabupaten,
Kecamatan,
Kelurahan,
Perusahaan,
Kawasan,
} from "@/types/perusahaan";
import { useForm, usePage } from "@inertiajs/react";
import { useToast } from "@/hooks/use-toast";
interface FormDataType {
NomorInduk: string;
JenisKegiatanId: string;
NamaPerusahaan: string;
Alamat: string;
KelurahanId: string;
KodePos: string;
Telepon: string;
Fax: string;
Email: string;
Lintang: string;
Bujur: string;
CPNama: string;
CPTelepon: string;
JenisDokILId: string;
VerifikatorId: string;
IsPublish: boolean;
ILNomor: string;
ILTanggal: string;
ILDokumen: File | null;
ReportLocked: true;
PerusahaanId: string;
}
// interface ExistingPerusahaan {
// NamaPerusahaan: string;
// PerusahaanId: string;
// }
interface ExistingInduk {
NomorInduk: string;
}
interface AddPerusahaanModalProps {
open: boolean;
onClose: () => void;
onSuccess: () => void;
jenisKegiatan: JenisKegiatan[];
jenisDokIL: JenisDokIL[];
verifikator: Verifikator[];
kabupaten: Kabupaten[];
kecamatan: Kecamatan[];
kelurahan: Kelurahan[];
perusahaan: Perusahaan[];
// kawasan: Kawasan[];
// existingPerusahaan: ExistingPerusahaan[];
// existingInduk: ExistingInduk[];
}
export function AddPerusahaanModal({
open,
onClose,
onSuccess,
jenisKegiatan,
jenisDokIL,
verifikator,
kabupaten,
kecamatan,
kelurahan,
perusahaan,
}: // existingPerusahaan,
// existingInduk,
// kawasan,
AddPerusahaanModalProps) {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const [date, setDate] = useState<Date>();
const { data, setData, post, reset } = useForm({
NomorInduk: "",
PerusahaanId: "",
JenisKegiatanId: "",
NamaPerusahaan: "",
Alamat: "",
KelurahanId: "",
KodePos: "",
Telepon: "",
Fax: "",
Email: "",
Lintang: "",
Bujur: "",
CPNama: "",
CPTelepon: "",
JenisDokILId: "",
VerifikatorId: "",
IsPublish: true,
ILDokumen: null as File | null,
ILNomor: "",
ILTanggal: "",
ReportLocked: true,
});
const [selectedKabupaten, setSelectedKabupaten] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKecamatan, setSelectedKecamatan] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKelurahan, setSelectedKelurahan] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedPerusahaan, setSelectedPerusahaan] = useState<{
value: number;
label: string;
} | null>(null);
const jenisKegiatanOptions = jenisKegiatan.map((jk) => ({
value: jk.JenisKegiatanId,
label: jk.NamaJenisKegiatan,
}));
const jenisDokILOptions = jenisDokIL.map((jdi) => ({
value: jdi.JenisDokILId,
label: jdi.NamaJenisDokIL,
}));
const verifikatorOptions = verifikator.map((v) => ({
value: v.VerifikatorId,
label: v.NamaUnitKerja,
}));
const kabupatenOptions = kabupaten.map((k) => ({
value: k.KabupatenId,
label: k.NamaKabupaten,
}));
const perusahaanOptions = perusahaan.map((per) => ({
value: per.PerusahaanId,
label: per.PerusahaanId,
}));
const kecamatanOptions = kecamatan
.filter(
(kec) =>
selectedKabupaten && kec.KabupatenId === selectedKabupaten.value
)
.map((kec) => ({
value: kec.KecamatanId,
label: kec.NamaKecamatan,
}));
const kelurahanOptions = kelurahan
.filter(
(kel) =>
selectedKecamatan && kel.KecamatanId === selectedKecamatan.value
)
.map((kel) => ({
value: kel.KelurahanId,
label: kel.NamaKelurahan,
}));
const [showAlert, setShowAlert] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const validateForm = () => {
const newErrors: Record<string, string> = {};
// const titleExists = existingPerusahaan.some(
// (perus) =>
// perus.PerusahaanId.toLowerCase() ===
// data.PerusahaanId.toLowerCase()
// );
// const indukExists = existingInduk.some(
// (induk) =>
// induk.NomorInduk.toLowerCase() === data.NomorInduk.toLowerCase()
// );
// if (indukExists) {
// setShowAlert(true);
// newErrors.NomorInduk = "Nomor Induk sudah ada";
// // Auto-hide alert after 5 seconds
// setTimeout(() => setShowAlert(false), 5000);
// }
// if (titleExists) {
// setShowAlert(true);
// newErrors.NamaPerusahaan = "Nama Perusahaan sudah ada";
// // Auto-hide alert after 5 seconds
// setTimeout(() => setShowAlert(false), 5000);
// }
// if (!data.PerusahaanId) {
// newErrors.PerusahaanId = "Perusahaan harus diisi";
// }
if (!data.JenisKegiatanId) {
newErrors.JenisKegiatanId = "Jenis Kegiatan harus dipilih";
}
if (!data.VerifikatorId) {
newErrors.VerifikatorId = "Admin harus dipilih";
}
if (!data.Email) {
newErrors.Email = "Email harus diisi";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
toast({
title: "Validasi Gagal",
description: "Silakan periksa kembali form anda",
variant: "destructive",
});
return;
}
setLoading(true);
const formData = new FormData();
formData.append("NomorInduk", data.NomorInduk);
formData.append("JenisKegiatanId", data.JenisKegiatanId);
formData.append("VerifikatorId", data.VerifikatorId);
formData.append("KelurahanId", data.KelurahanId);
formData.append("NamaPerusahaan", data.NamaPerusahaan);
formData.append("Alamat", data.Alamat);
formData.append("KodePos", data.KodePos);
formData.append("Telepon", data.Telepon);
formData.append("Fax", data.Fax);
formData.append("Email", data.Email);
formData.append("Lintang", data.Lintang);
formData.append("Bujur", data.Bujur);
formData.append("CPNama", data.CPNama);
formData.append("CPTelepon", data.CPTelepon);
formData.append("JenisDokILId", data.JenisDokILId);
formData.append("IsPublish", data.IsPublish.toString());
formData.append("ILNomor", data.ILNomor);
formData.append("ILTanggal", data.ILTanggal);
formData.append("ReportLocked", data.ReportLocked.toString());
if (data.ILDokumen) {
formData.append("ILDokumen", data.ILDokumen);
}
post("/admin/perusahaan", {
data: formData,
forceFormData: true,
onSuccess: (response) => {
toast({
title: "Berhasil",
description: "Perusahaan berhasil ditambahkan",
variant: "default",
});
reset();
onSuccess();
onClose();
},
onError: (error) => {
console.error("Error:", error);
toast({
title: "Gagal",
description:
"Terjadi kesalahan saat menambahkan perusahaan",
variant: "destructive",
});
},
});
};
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Tambah Perusahaan Baru</DialogTitle>
<DialogDescription>
Masukkan informasi perusahaan yang akan ditambahkan ke
dalam sistem.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-6">
<div className="flex flex-row gap-6">
{/* Sisi Kiri */}
<div className="w-1/2">
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<Hotel size={16} />
<h3 className="text-sm ">
Data Perusahaan
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="NomorInduk">
Nomor Induk
</Label>
<Input
id="NomorInduk"
value={data.NomorInduk}
onChange={(e) =>
setData({
...data,
NomorInduk: e.target.value,
})
}
/>
{errors.NomorInduk && (
<p className="text-red-500 text-sm">
{errors.NomorInduk}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="NamaPerusahaan">
Nama Perusahaan *
</Label>
<Input
id="NamaPerusahaan"
required
value={data.NamaPerusahaan}
onChange={(e) =>
setData({
...data,
NamaPerusahaan: e.target.value,
})
}
/>
{errors.PerusahaanId && (
<p className="text-red-500 text-sm">
{errors.PerusahaanId}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="NamaPerusahaan">
Jenis Kegiatan *
</Label>
<Select
id="JenisKegiatanId"
options={jenisKegiatanOptions}
required
placeholder="Pilih Jenis Kegiatan"
onChange={(option) =>
setData({
...data,
JenisKegiatanId:
option?.value?.toString() ||
"",
})
}
/>
{errors.JenisKegiatanId && (
<p className="text-red-500 text-sm">
{errors.JenisKegiatanId}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="NamaUnitKerja">
Admin *
</Label>
<Select
id="VerifikatorId"
options={verifikatorOptions}
required
placeholder="Pilih Admin"
onChange={(option) =>
setData({
...data,
VerifikatorId:
option?.value?.toString() ||
"",
})
}
/>
{errors.VerifikatorId && (
<p className="text-red-500 text-sm">
{errors.VerifikatorId}
</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="PerusahaanId">
Pengelola Kawasan
</Label>
<Select
id="PerusahaanId"
options={perusahaanOptions}
placeholder="Pilih Pengelola Kawasan"
onChange={(option) =>
setData({
...data,
PerusahaanId:
option?.value?.toString() ||
"",
})
}
/>
</div>
<Separator />
{/* Kontak Person */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<UsersRound size={16} />
<h3 className="text-sm ">
Kontak Person
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="CPNama">Nama</Label>
<Input
id="CPNama"
value={data.CPNama}
onChange={(e) =>
setData({
...data,
CPNama: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="CPTelepon">
Telepon
</Label>
<Input
id="CPTelepon"
value={data.CPTelepon}
onChange={(e) =>
setData({
...data,
CPTelepon: e.target.value,
})
}
/>
</div>
</div>
<Separator className="space-y-4" />
{/* Dokumen Izin */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<FileText size={16} />
<h3 className="text-sm ">
Dokumen Izin
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="ILNomor">Nomor</Label>
<Input
id="ILNomor"
value={data.ILNomor}
onChange={(e) =>
setData({
...data,
ILNomor: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ILTanggal">
Tanggal
</Label>
<Input
id="ILTanggal"
type="date"
value={data.ILTanggal}
onChange={(e) =>
setData({
...data,
ILTanggal: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="JenisDokILId">
Jenis Dokumen
</Label>
<Select
id="JenisDokILId"
options={jenisDokILOptions}
placeholder="Pilih Jenis Dokumen"
onChange={(option) =>
setData({
...data,
JenisDokILId:
option?.value?.toString() ||
"",
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ILDokumen">
Unggah Dokumen
</Label>
<Input
id="ILDokumen"
type="file"
accept="pdf"
onChange={(e) =>
setData({
...data,
ILDokumen: e.target.files
? e.target.files[0]
: null,
})
}
/>
</div>
</div>
</div>
</div>
{/* Sisi Kanan */}
<div className="w-1/2">
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<MapPinned size={16} />
<h3 className="text-sm ">
Alamat Perusahaan
</h3>
</div>
<div className="space-y-2">
<Label htmlFor="KelurahanId">
Kabupaten/Kota *
</Label>
<Select
id="KabupatenId"
options={kabupatenOptions}
value={selectedKabupaten}
onChange={(value) => {
setSelectedKabupaten(value);
setSelectedKecamatan(null);
setSelectedKelurahan(null);
}}
placeholder="Pilih Kabupaten/Kota"
isSearchable
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="KelurahanId">
Kecamatan *
</Label>
<Select
id="KecamatanId"
options={kecamatanOptions}
value={selectedKecamatan}
onChange={(value) => {
setSelectedKecamatan(value);
setSelectedKelurahan(null);
}}
placeholder="Pilih Kecamatan"
isSearchable
isDisabled={!selectedKabupaten}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="KelurahanId">
Kelurahan *
</Label>
<Select
id="KelurahanId"
options={kelurahanOptions}
value={selectedKelurahan}
onChange={(value) => {
setSelectedKelurahan(value);
setData({
...data,
KelurahanId:
value?.value?.toString() ||
"",
});
}}
placeholder="Pilih Kelurahan"
isSearchable
isDisabled={!selectedKecamatan}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="Alamat">Alamat</Label>
<Textarea
id="Alamat"
value={data.Alamat}
onChange={(e) =>
setData({
...data,
Alamat: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="KodePos">Kode Pos</Label>
<Input
id="KodePos"
value={data.KodePos}
onChange={(e) =>
setData({
...data,
KodePos: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Telepon">Telepon</Label>
<Input
id="Telepon"
value={data.Telepon}
onChange={(e) =>
setData({
...data,
Telepon: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Fax">Fax</Label>
<Input
id="Fax"
value={data.Fax}
onChange={(e) =>
setData({
...data,
Fax: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Email">Email *</Label>
<Input
id="Email"
type="email"
required
value={data.Email}
onChange={(e) =>
setData({
...data,
Email: e.target.value,
})
}
/>
</div>
<Separator className="space-y-4" />
{/* Kontak dan Koordinat */}
<div className="space-y-4">
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<LocateFixed size={16} />
<h3 className="text-sm ">Koordinat</h3>
</div>
<div className="space-y-2">
<Label htmlFor="Lintang">Lintang</Label>
<Input
id="Lintang"
value={data.Lintang}
onChange={(e) =>
setData({
...data,
Lintang: e.target.value,
})
}
/>
</div>
<div className="space-y-2">
<Label htmlFor="Bujur">Bujur</Label>
<Input
id="Bujur"
value={data.Bujur}
onChange={(e) =>
setData({
...data,
Bujur: e.target.value,
})
}
/>
</div>
</div>
<Separator className="space-y-4" />
<div className="flex flex-row bg-green-200 px-2 py-1 rounded w-fit gap-2 items-center">
<VerifiedIcon size={16} />
<h3 className="text-sm">Status</h3>
</div>
<div className="flex space-x-4">
<div className="flex items-center space-x-2">
<Checkbox
id="IsPublishActive"
checked={data.IsPublish === true}
onCheckedChange={() =>
setData({
...data,
IsPublish: true,
})
}
/>
<Label htmlFor="IsPublishActive">
Aktif
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="IsPublishInactive"
checked={data.IsPublish === false}
onCheckedChange={() =>
setData({
...data,
IsPublish: false,
})
}
/>
<Label htmlFor="IsPublishInactive">
Non Aktif
</Label>
</div>
</div>
</div>
</div>
</div>
<Separator className="space-y-4" />
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={onClose}
disabled={loading}
>
Batal
</Button>
<Button type="submit" disabled={loading}>
{loading ? "Menyimpan..." : "Simpan"}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}

View File

@ -0,0 +1,28 @@
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { Check } from "lucide-react"
import { cn } from "@/lib/utils"
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn("flex items-center justify-center text-current")}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
export { Checkbox }

View File

@ -0,0 +1,135 @@
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { FileUp, X } from "lucide-react";
interface UploadDocProps {
isOpen: boolean;
onClose: () => void;
onUpload: (file: File) => void;
}
const UploadDoc = ({ isOpen, onClose, onUpload }: UploadDocProps) => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [dragActive, setDragActive] = useState<boolean>(false);
const handleDrag = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
if (e.type === "dragenter" || e.type === "dragover") {
setDragActive(true);
} else if (e.type === "dragleave") {
setDragActive(false);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
e.stopPropagation();
setDragActive(false);
const file = e.dataTransfer.files[0];
if (file && file.type === "application/pdf") {
setSelectedFile(file);
} else {
alert("Hanya file PDF yang diperbolehkan");
}
};
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file && file.type === "application/pdf") {
setSelectedFile(file);
} else {
alert("Hanya file PDF yang diperbolehkan");
}
};
const handleUpload = () => {
if (selectedFile) {
onUpload(selectedFile);
onClose();
}
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Lampiran *</DialogTitle>
<div className="flex flex-row items-center">
<div className="h-8 w-[3px] bg-gray-300 mr-2" />
<small>
Silahkan unggah dokumen Anda dengan drag & drop
semua berkas atau klik pada kotak ini. Ukuran file
tidak boleh melebihi 20 MB.
</small>
</div>
</DialogHeader>
<div
className={`mt-4 p-6 border-2 border-dashed rounded-lg text-center ${
dragActive
? "border-teal-500 bg-teal-50"
: "border-gray-300"
}`}
onDragEnter={handleDrag}
onDragLeave={handleDrag}
onDragOver={handleDrag}
onDrop={handleDrop}
>
<FileUp className="mx-auto h-12 w-12 text-gray-400" />
<div className="mt-4">
<p className="text-sm text-gray-600">
Drag and drop file PDF di sini atau
</p>
<label className="mt-2 cursor-pointer">
<Input
type="file"
accept=".pdf"
className="hidden"
onChange={handleFileChange}
/>
<span className="text-teal-600 hover:text-teal-500">
Pilih file
</span>
</label>
</div>
{selectedFile && (
<div className="mt-4 p-2 bg-gray-50 rounded flex items-center justify-between">
<span className="text-sm text-gray-600">
{selectedFile.name}
</span>
<Button
variant="ghost"
size="sm"
onClick={() => setSelectedFile(null)}
>
<X className="h-4 w-4" />
</Button>
</div>
)}
</div>
<div className="mt-4 flex justify-end gap-3">
<Button variant="outline" onClick={onClose}>
Batal
</Button>
<Button
onClick={handleUpload}
disabled={!selectedFile}
className="bg-teal-600 hover:bg-teal-700"
>
Unggah
</Button>
</div>
</DialogContent>
</Dialog>
);
};
export default UploadDoc;

View File

@ -14,6 +14,7 @@ import {
BreadcrumbPage,
} from "@/components/ui/breadcrumb";
import AppearanceDropdown from "@/components/appearance-dropdown";
import { Toaster } from "@/components/ui/toaster";
export default function AuthenticatedLayout({
header,
@ -48,6 +49,7 @@ export default function AuthenticatedLayout({
<main className="p-4 md:pt-0 h-full w-full max-w-full overflow-x-hidden">
{children}
<Toaster />
</main>
<footer className="text-sm p-4 text-center bg-background border-t">
© Copyright {new Date().getFullYear()} Bidang Tata

View File

@ -0,0 +1,25 @@
import React from "react";
import { Head, Link } from "@inertiajs/react";
import "../../css/app.css";
const NotFound = () => {
return (
<>
<Head title="Not Found" />
<div className="flex flex-col items-center justify-center h-screen bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-white">
<h1 className="text-6xl font-bold">404</h1>
<p className="text-lg mt-4">
Oops! Halaman yang kamu cari tidak ditemukan.
</p>
<Link
href="/dashboard"
className="mt-6 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition"
>
Kembali ke Dashboard
</Link>
</div>
</>
);
};
export default NotFound;

View File

@ -43,7 +43,6 @@ import {
interface JenisDokIL {
JenisDokILId: number | null;
KodeJenisDokIL: string;
NamaJenisDokIL: string;
}
@ -61,7 +60,6 @@ export default function JenisDokILIndex({
reset,
} = useForm<JenisDokIL>({
JenisDokILId: null,
KodeJenisDokIL: "",
NamaJenisDokIL: "",
});
@ -79,14 +77,8 @@ export default function JenisDokILIndex({
let filtered = jenisdokil;
if (search) {
filtered = filtered.filter(
(item) =>
item.KodeJenisDokIL.toLowerCase().includes(
search.toLowerCase()
) ||
item.NamaJenisDokIL.toLowerCase().includes(
search.toLowerCase()
)
filtered = filtered.filter((item) =>
item.NamaJenisDokIL.toLowerCase().includes(search.toLowerCase())
);
}
@ -227,21 +219,6 @@ export default function JenisDokILIndex({
onSubmit={handleSubmit}
className="space-y-4"
>
<div className="space-y-2">
<label>
Kode Jenis Dokumen
</label>
<Input
value={data.KodeJenisDokIL}
onChange={(e) =>
setData(
"KodeJenisDokIL",
e.target.value
)
}
placeholder="Masukkan kode jenis dokumen"
/>
</div>
<div className="space-y-2">
<label>
Nama Jenis Dokumen
@ -275,7 +252,6 @@ export default function JenisDokILIndex({
<TableHeader>
<TableRow>
<TableHead>No</TableHead>
<TableHead>Kode</TableHead>
<TableHead>Nama Jenis Dokumen</TableHead>
<TableHead>Aksi</TableHead>
</TableRow>
@ -286,9 +262,6 @@ export default function JenisDokILIndex({
<TableCell>
{startIndex + index + 1}
</TableCell>
<TableCell>
{item.KodeJenisDokIL}
</TableCell>
<TableCell>
{item.NamaJenisDokIL}
</TableCell>

View File

@ -0,0 +1,367 @@
import React, { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { CircleHelp, FileDown, Printer } from "lucide-react";
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head } from "@inertiajs/react";
import { Textarea } from "@/components/ui/textarea";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import UploadDoc from "@/components/upload_doc";
interface PelaporanALFormProps {
onSubmit: (data: any) => void;
}
const PelaporanALForm = ({ onSubmit }: PelaporanALFormProps) => {
const [isConnected, setIsConnected] = useState<boolean | null>(null);
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
const [uploadedFile, setUploadedFile] = useState<File | null>(null);
return (
<AuthenticatedLayout header="Pelaporan Air Limbah">
<Head title="Pelaporan Air Limbah" />
<div className="p-4 space-y-6">
<Card className="shadow-lg">
<CardHeader className="bg-[#166534] text-white rounded-t-lg">
<div className="flex justify-between items-center">
<div>
<CardTitle className="text-xl">
Laporan Pengelolaan Air Limbah - Ujicoba
</CardTitle>
<p className="text-sm opacity-90">
Tahun 2025 - Periode Triwulan 1
</p>
</div>
<div className="flex gap-2">
<Button
variant="secondary"
size="sm"
onClick={() => window.history.back()}
>
Kembali
</Button>
<Button
variant="secondary"
size="sm"
onClick={() => window.print()}
>
<Printer />
</Button>
<Button
variant="secondary"
size="sm"
onClick={() => {
// Add PDF download logic here
}}
>
<FileDown />
</Button>
</div>
</div>
</CardHeader>
<CardContent className="p-6">
<div className="space-y-6">
{/* Header Section */}
<div className="flex justify-between items-center gap-4 mb-6">
<div className="flex items-center gap-2">
<h2>Tersambung IPAL Komunal:</h2>
<div className="flex flex-row gap-3">
<div className="flex items-center gap-2">
<input
type="radio"
id="tersambung"
name="connection"
className="w-4 h-4 text-teal-600"
onChange={() =>
setIsConnected(true)
}
checked={isConnected === true}
/>
<Label htmlFor="tersambung">
Ya
</Label>
</div>
<div className="flex items-center gap-2">
<input
type="radio"
id="tidak"
name="connection"
className="w-4 h-4 text-teal-600"
onChange={() =>
setIsConnected(false)
}
checked={isConnected === false}
/>
<Label htmlFor="tidak">Tidak</Label>
</div>
</div>
</div>
<div>
{isConnected && (
<div className="flex items-center gap-2">
<h2>
Lampiran
<span className="text-red-600">
*
</span>{" "}
:
</h2>
<Button
variant="outline"
className="w-48"
onClick={() =>
setIsUploadModalOpen(true)
}
>
{uploadedFile
? uploadedFile.name
: "Unggah Surat Kerjasama"}
</Button>
</div>
)}
</div>
</div>
<UploadDoc
isOpen={isUploadModalOpen}
onClose={() => setIsUploadModalOpen(false)}
onUpload={(file) => {
setUploadedFile(file);
// Handle the file upload logic here
console.log("File uploaded:", file);
}}
/>
{/* Main Form Table */}
<div className="border rounded-lg overflow-hidden">
<table className="w-full">
<thead>
<tr className="bg-[#166534] text-white">
<th className="p-3 text-left w-16">
No
</th>
<th className="p-3 text-left">
Komponen
</th>
<th className="p-3 text-left w-32">
Hasil
</th>
<th className="p-3 text-left w-24">
Nilai
</th>
<th className="p-3 text-left w-32">
Lampiran
</th>
<th className="p-3 text-left w-32">
Verifikasi
</th>
<th className="p-3 text-left w-32">
Keterangan
</th>
</tr>
</thead>
<tbody>
{/* Section 1 */}
<tr className="bg-teal-50">
<td className="p-3">I</td>
<td
className="p-3 font-medium"
colSpan={1}
>
Nilai tingkat ketaatan
pengelolaan air limbah (IKLl)
(%)
</td>
<td className="p-3"></td>
<td className="p-3">100</td>
<td className="p-3"></td>
<td className="p-3"></td>
<td className="p-3"></td>
</tr>
{/* Technical Requirements */}
{[
"Instalasi pengolah air limbah",
"Flowmeter",
"Titik pengambilan sampel",
"Saluran air limbah & air hujan terpisah",
"Izin pembuangan air limbah",
].map((item, index) => (
<tr
key={index}
className="border-t"
>
<td className="p-3">
A{index + 1}
</td>
<td className="p-3">
<div className="flex justify-between items-center w-full">
<div>{item}</div>
<Tooltip>
<TooltipTrigger>
<CircleHelp className="text-teal-600 w-5 h-5" />
</TooltipTrigger>
<TooltipContent>
<p>
Deskripsi
untuk {item}
</p>
</TooltipContent>
</Tooltip>
</div>
</td>
<td className="p-3">
{" "}
<Select>
<SelectTrigger className="w-24">
<SelectValue placeholder="Ada" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ada">
Ada
</SelectItem>
<SelectItem value="tidak">
Tidak Ada
</SelectItem>
</SelectContent>
</Select>
</td>
<td className="p-3">100</td>
<td className="p-3"></td>
<td className="p-3"></td>
<td className="p-3"></td>
</tr>
))}
{/* Implementation Section */}
{[
{
name: "Pengujian air limbah",
hasAttachment: true,
},
{
name: "Pemenuhan baku mutu air limbah",
hasAttachment: false,
},
{
name: "Pelaksanaan dan pemutusan wewenang",
hasSelect: true,
},
{
name: "Pembuatan neraca air",
hasSelect: true,
},
{
name: "Sertifikasi kompetensi",
hasSelect: true,
},
].map((item, index) => (
<tr
key={index}
className="border-t"
>
<td className="p-3">
A{index + 6}
</td>
<td className="p-3">
<div className="flex justify-between items-center w-full">
<div>{item.name}</div>
<CircleHelp className="text-teal-600 w-5 h-5" />
</div>
</td>
<td className="p-3">
<Select>
<SelectTrigger className="w-24">
<SelectValue placeholder="Ada" />
</SelectTrigger>
<SelectContent>
<SelectItem value="ada">
Ada
</SelectItem>
<SelectItem value="tidak">
Tidak Ada
</SelectItem>
</SelectContent>
</Select>
</td>
<td className="p-3">100</td>
<td className="p-3">
{item.hasAttachment && (
<span className="text-teal-600">
Data...
</span>
)}
</td>
<td className="p-3"></td>
<td className="p-3"></td>
</tr>
))}
</tbody>
</table>
</div>
{/* Notes Section */}
<div className="bg-gray-50 p-6 rounded-lg border border-gray-200 mt-6">
<h2 className="text-lg font-semibold mb-3 text-gray-800">
Catatan
</h2>
<Textarea
placeholder="Tambahkan catatan atau komentar tambahan di sini..."
className="min-h-[100px] bg-white"
/>
</div>
{/* Agreement & Submit Section */}
<div className="mt-8 space-y-6">
<div className="flex items-start gap-3 p-4 bg-green-50 rounded-lg border border-teal-200">
<Checkbox id="agreement" className="mt-1" />
<Label
htmlFor="agreement"
className="text-sm leading-relaxed text-gray-700"
>
Dengan mencentang ini, kami menyatakan
bahwa laporan ini telah disusun
berdasarkan ketentuan peraturan yang
berlaku dan kami bersedia bertanggung
jawab atas kebenaran data-data yang kami
kirimkan sesuai dengan fakta dilapangan.
</Label>
</div>
<div className="flex justify-end gap-4">
<Button
variant="outline"
className="hover:bg-gray-100"
>
Simpan Draft
</Button>
<Button className="bg-[#166534] hover:bg-green-700 transition-colors px-8">
Kirim Laporan
</Button>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
</AuthenticatedLayout>
);
};
export default PelaporanALForm;

View File

@ -1,5 +1,5 @@
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head } from "@inertiajs/react";
import { Head, Link } from "@inertiajs/react";
import React from "react";
import { useState } from "react";
import {
@ -288,12 +288,16 @@ export default function PelaporanIndex() {
>
00.00
</TableCell>
<TableCell
colSpan={1}
className="text-center text-muted-foreground border-r border-l bg-red-100 text-red-700"
>
00.00
<Link href="/admin/pelaporan/al">
00.00
</Link>
</TableCell>
<TableCell
colSpan={1}
className="text-center text-muted-foreground border-r border-l bg-red-100 text-red-700"

View File

@ -0,0 +1,637 @@
import AuthenticatedLayout from "@/layouts/authenticated-layout";
import { Head } from "@inertiajs/react";
import React from "react";
import { useState } from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
BadgeCheck,
ChevronLeft,
ChevronRight,
ChevronUp,
Download,
FileText,
Key,
LockKeyhole,
Plus,
Search,
TouchpadOff,
Upload,
} from "lucide-react";
import Select from "react-select";
import { AddPerusahaanModal } from "@/components/modals/add-perusahaan-modal";
import {
Perusahaan,
JenisDokIL,
JenisKegiatan,
Verifikator,
Kabupaten,
Kecamatan,
Kelurahan,
Kawasan,
} from "@/types/perusahaan";
export default function PerusahaanIndex({
perusahaan,
jenisKegiatan,
jenisDokIL,
verifikator,
kabupaten,
kecamatan,
kelurahan,
}: {
perusahaan: Perusahaan[];
jenisKegiatan: JenisKegiatan[];
jenisDokIL: JenisDokIL[];
verifikator: Verifikator[];
kabupaten: Kabupaten[];
kecamatan: Kecamatan[];
kelurahan: Kelurahan[];
}) {
console.log("Perusahaan data:", perusahaan);
// console.log("Jenis Kegiatan data:", jenisKegiatan);
// console.log("Jenis Dok IL data:", jenisDokIL);
// console.log("Verifikator data:", verifikator);
// console.log("Kabupaten data:", kabupaten);
// console.log("Kecamatan data:", kecamatan);
// console.log("Kelurahan data:", kelurahan);
const [year, setYear] = useState<string>("2025");
const [quarter, setQuarter] = useState<string>("Triwulan 1");
const [selectedJenisKegiatan, setSelectedJenisKegiatan] = useState<{
value: string;
label: string;
} | null>(null);
const [selectedJenisDokIL, setSelectedJenisDokIL] = useState<{
value: string;
label: string;
} | null>(null);
const [selectedVerifikator, setSelectedVerifikator] = useState<{
value: string;
label: string;
} | null>(null);
const [selectedKabupaten, setSelectedKabupaten] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKecamatan, setSelectedKecamatan] = useState<{
value: number;
label: string;
} | null>(null);
const [selectedKelurahan, setSelectedKelurahan] = useState<{
value: number;
label: string;
} | null>(null);
const jenisKegiatanOptions = jenisKegiatan.map((jk) => ({
value: jk.JenisKegiatanId.toString(),
label: jk.NamaJenisKegiatan,
}));
const jenisDokILOptions = jenisDokIL.map((jdi) => ({
value: jdi.JenisDokILId.toString(),
label: jdi.NamaJenisDokIL,
}));
const verifikatorOptions = verifikator.map((v) => ({
value: v.VerifikatorId.toString(),
label: v.NamaUnitKerja,
}));
const kabupatenOptions = kabupaten.map((k) => ({
value: k.KabupatenId,
label: k.NamaKabupaten,
}));
const kecamatanOptions = kecamatan
.filter(
(kec) =>
selectedKabupaten && kec.KabupatenId === selectedKabupaten.value
)
.map((kec) => ({
value: kec.KecamatanId,
label: kec.NamaKecamatan,
}));
const kelurahanOptions = kelurahan
.filter(
(kel) =>
selectedKecamatan && kel.KecamatanId === selectedKecamatan.value
)
.map((kel) => ({
value: kel.KelurahanId,
label: kel.NamaKelurahan,
}));
// const companyOptions = [
// { value: "PT Ajinomoto Indonesia", label: "PT Ajinomoto Indonesia" },
// { value: "PT Unilever Indonesia", label: "PT Unilever Indonesia" },
// {
// value: "PT Indofood Sukses Makmur",
// label: "PT Indofood Sukses Makmur",
// },
// { value: "PT Mayora Indah", label: "PT Mayora Indah" },
// ];
type CompanyOption = {
value: string;
label: string;
};
const companyOptions: CompanyOption[] = perusahaan.map((c) => ({
value: c.PerusahaanId.toString(),
label: c.NamaPerusahaan,
}));
const [company, setCompany] = useState<CompanyOption | null>(
companyOptions[0]
);
const [showAddModal, setShowAddModal] = useState(false);
const handleSuccess = () => {
// Refresh data
window.location.reload();
};
// Color coding helper
const getStatusColor = (status: string) => {
switch (status) {
case "belum":
return "bg-red-100 text-red-800";
case "pending":
return "bg-gray-100 text-gray-800";
case "siap":
return "bg-orange-100 text-orange-800";
case "selesai":
return "bg-green-100 text-green-800";
default:
return "";
}
};
return (
<AuthenticatedLayout header={"Daftar Perusahaan"}>
<Head title="Daftar Perusahaan" />
<AddPerusahaanModal
open={showAddModal}
onClose={() => setShowAddModal(false)}
onSuccess={handleSuccess}
jenisKegiatan={jenisKegiatan}
jenisDokIL={jenisDokIL}
verifikator={verifikator}
kabupaten={kabupaten}
kecamatan={kecamatan}
kelurahan={kelurahan}
perusahaan={perusahaan}
// existingPerusahaan={perusahaan.map(p => ({...p, PerusahaanId: p.PerusahaanId.toString()}))}
// existingInduk={[]}
// kawasan={kawasan}
/>
{/* Filter Section */}
<div className="bg-white p-6 rounded-lg shadow-sm mb-6">
<div className="flex flex-col md:flex-row md:gap-6 py-4">
<div className="w-1/2">
<h2 className="text-lg font-semibold mb-4 text-gray-800">
Filter Pencarian
</h2>
</div>
<div className="w-1/2">
<div className="flex md:mt-0 md:justify-end">
<Button
className="bg-blue-600 hover:bg-blue-950 text-white"
onClick={() => setShowAddModal(true)}
>
<Plus className="w-4 h-4 mr-2" />
Tambah Perusahaan
</Button>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Left Column */}
<div className="space-y-4">
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Perusahaan
</label>
<div className="col-span-2">
<Select
options={companyOptions}
value={company}
onChange={setCompany}
placeholder="Pilih Perusahaan"
isSearchable
className="w-full"
isClearable
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Kabupaten/Kota
</label>
<div className="col-span-2">
<Select
options={kabupatenOptions}
value={selectedKabupaten}
onChange={(value) => {
setSelectedKabupaten(value);
setSelectedKecamatan(null);
setSelectedKelurahan(null);
}}
placeholder="Pilih Kabupaten/Kota"
isSearchable
className="w-full"
isClearable
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Kecamatan
</label>
<div className="col-span-2">
<Select
options={kecamatanOptions}
value={selectedKecamatan}
onChange={(value) => {
setSelectedKecamatan(value);
setSelectedKelurahan(null);
}}
placeholder="Pilih Kecamatan"
isSearchable
className="w-full"
isClearable
isDisabled={!selectedKabupaten}
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Kelurahan
</label>
<div className="col-span-2">
<Select
options={kelurahanOptions}
value={selectedKelurahan}
onChange={setSelectedKelurahan}
placeholder="Pilih Kelurahan"
isSearchable
className="w-full"
isClearable
isDisabled={!selectedKecamatan}
/>
</div>
</div>
</div>
{/* Right Column */}
<div className="space-y-4">
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Jenis Kegiatan
</label>
<div className="col-span-2">
<Select
options={jenisKegiatanOptions}
value={selectedJenisKegiatan}
onChange={setSelectedJenisKegiatan}
placeholder="Pilih Jenis Kegiatan"
isSearchable
className="w-full"
isClearable
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Admin
</label>
<div className="col-span-2">
<Select
options={verifikatorOptions}
value={selectedVerifikator}
onChange={setSelectedVerifikator}
placeholder="Pilih Admin"
isSearchable
className="w-full"
isClearable
/>
</div>
</div>
<div className="grid grid-cols-3 items-center gap-4">
<label className="text-sm font-medium text-gray-700">
Jenis Izin
</label>
<div className="col-span-2">
<Select
options={jenisDokILOptions}
value={selectedJenisDokIL}
onChange={setSelectedJenisDokIL}
placeholder="Pilih Jenis Izin"
isSearchable
className="w-full"
isClearable
/>
</div>
</div>
<div className="flex justify-end mt-6">
<Button className="bg-green-600 hover:bg-green-700 text-white">
<Search className="w-4 h-4 mr-2" />
Cari
</Button>
</div>
</div>
</div>
</div>
{/* Table Section */}
<div className="w-full overflow-x-auto">
<Table className="min-w-[1000px] border-collapse">
<TableHeader>
<TableRow className="border-b border-t bg-green-800">
<TableHead
rowSpan={2}
className="w-[50px] border-r border-l text-white"
>
No.
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Nama Perusahaan
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Jenis Kegiatan
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Alamat
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Kelurahan
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Kecamatan
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Kota
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Kode Pos
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Telp
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Fax
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Email
</TableHead>
<TableHead
className="border-r text-center text-white"
colSpan={2}
>
Koordinat
</TableHead>
<TableHead
className="border-r text-center text-white"
colSpan={2}
>
Kontak Person
</TableHead>
<TableHead
className="border-r text-center text-white"
rowSpan={2}
>
Admin
</TableHead>
<TableHead
className="border-r text-center text-white"
colSpan={4}
>
Dokumen Izin / Persetujuan Lingkungan Bidang
Tata Lingkungan
</TableHead>
<TableHead
rowSpan={2}
className="border-r text-center text-white"
>
Status Laporan
</TableHead>
</TableRow>
<TableRow className="border-b bg-green-600">
<TableHead className="text-center border-r text-white">
Lintang
</TableHead>
<TableHead className="text-center border-r text-white">
Bujur
</TableHead>
<TableHead className="text-center border-r text-white">
Nama
</TableHead>
<TableHead className="text-center border-r text-white">
Telepon
</TableHead>
<TableHead className="text-center border-r text-white">
Nomor
</TableHead>
<TableHead className="text-center border-r text-white">
Tanggal
</TableHead>
<TableHead className="text-center border-r text-white">
Jenis
</TableHead>
<TableHead className="text-center border-r text-white">
Dok
</TableHead>
</TableRow>
</TableHeader>
<TableBody className="border-b">
{perusahaan.length === 0 ? (
<TableRow>
<TableCell
colSpan={20}
className="text-center py-4"
>
Tidak ada data perusahaan
</TableCell>
</TableRow>
) : (
perusahaan.map((item, index) => {
// Add console log to debug each perusahaan item
// console.log(
// "Perusahaan item:",
// item.verifikator
// );
return (
<TableRow
key={item.PerusahaanId}
className="border-b"
>
<TableCell className="text-center border-r border-l">
{index + 1}
</TableCell>
<TableCell className="text-center border-r">
{item.NamaPerusahaan || "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.jenis_kegiatan
?.NamaJenisKegiatan || "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.Alamat}
</TableCell>
<TableCell className="text-center border-r">
{item.kelurahan?.NamaKelurahan ||
"N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.kelurahan?.kecamatan
?.NamaKecamatan || "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.kelurahan?.kecamatan
?.kabupaten?.NamaKabupaten ||
"N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.KodePos}
</TableCell>
<TableCell className="text-center border-r">
{item.Telepon}
</TableCell>
<TableCell className="text-center border-r">
{item.Fax}
</TableCell>
<TableCell className="text-center border-r">
{item.Email}
</TableCell>
<TableCell className="text-center border-r">
{item.Lintang}
</TableCell>
<TableCell className="text-center border-r">
{item.Bujur}
</TableCell>
<TableCell className="text-center border-r">
{item.CPNama}
</TableCell>
<TableCell className="text-center border-r">
{item.CPTelepon}
</TableCell>
<TableCell className="text-center border-r">
{item.verifikator?.NamaUnitKerja ??
"N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.ILNomor || "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.ILTanggal
? new Date(
item.ILTanggal
).toLocaleDateString("en-GB")
: "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.jenis_dok_i_l
?.NamaJenisDokIL || "N/A"}
</TableCell>
<TableCell className="text-center border-r">
{item.ILDokumen ? (
<a
href={`/storage/${item.ILDokumen}`}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center"
>
<FileText className="w-4 h-4 text-green-600 hover:text-green-800" />
</a>
) : (
<div className="flex items-center justify-center">
<TouchpadOff className="w-4 h-4" />
</div>
)}
</TableCell>
<TableCell className="text-center border-r">
<div className="flex items-center justify-center">
{item.ReportLocked ? (
<LockKeyhole className="w-4 h-4 text-red-700" />
) : (
<Key className="w-4 h-4 text-green-700" />
)}
</div>
</TableCell>
</TableRow>
);
})
)}
</TableBody>
</Table>
</div>
{/* Pagination */}
<div className="mt-4 flex items-center gap-2 justify-end">
<Button variant="outline" size="icon">
<ChevronLeft className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</AuthenticatedLayout>
);
}

View File

@ -214,7 +214,7 @@ export default function VerifikatorIndex({
}}
>
<Plus className="h-4 w-4 mr-2" />
Tambah Verifikator
Tambah Dinas
</Button>
</DialogTrigger>

View File

@ -5,120 +5,177 @@ import {
Building,
School,
Skull,
TestTubeDiagonalIcon,
UsersRound,
Waves,
TrendingUp,
ArrowUpRight,
ArrowDownRight,
Activity,
} from "lucide-react";
import StatsCard from "@/components/Dashboard/DashCard";
import CerobongCard from "@/components/Dashboard/CerobongCard";
import { ChartCard } from "@/components/Dashboard/ChartCard";
import { Card } from "@/components/ui/card";
export default function Dashboard() {
return (
<AuthenticatedLayout header="Dashboard">
<Head title="Dashboard" />
<div className="flex flex-1 flex-col gap-4 h-full">
<DatePickerWithRange />
<div className="grid gap-4 md:grid-cols-5 items-center w-full">
<StatsCard
title="Total Laporan Perusahaan"
icon={<School size={24} />}
value={1305}
fromColor="from-[#E6F9FF]"
toColor=""
/>
<StatsCard
title="Jumlah Perusahaan"
icon={<Building size={24} />}
value={1305}
fromColor="from-[#F7E9FF]"
toColor=""
/>
<StatsCard
title="Jumlah Verifikator"
icon={<UsersRound size={24} />}
value={1305}
fromColor="from-[#F7E9FF]"
toColor=""
/>
<StatsCard
title="Air Limbah"
icon={<Waves size={24} />}
value={1305}
fromColor="from-[#F7E9FF]"
toColor="#fffff"
/>
<StatsCard
title="Limbah B3"
icon={<Skull size={24} />}
value={1305}
fromColor="from-[#F7E9FF]"
toColor="#fffff"
/>
<div className="flex flex-1 flex-col gap-6 h-full p-6">
{/* Top Section */}
<div className="flex justify-between items-center gap-4 flex-wrap">
<div className="space-y-1">
<h2 className="text-2xl font-bold tracking-tight">
Dashboard Overview
</h2>
{/* <p className="text-muted-foreground">
Detailed monitoring and analytics of environmental
metrics
</p> */}
</div>
<DatePickerWithRange />
</div>
<div className="flex flex-col items-stretch space-y-0 border-b p-0 sm:flex-row mt-8">
<div className="flex flex-1 flex-col justify-center gap-1 px-6 py-5 sm:py-6">
<div className="font-semibold leading-none tracking-tight">
Total Cerobong per hari ini
{/* Stats Cards Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5">
<Card className="relative overflow-hidden group transition-all hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-blue-50 to-transparent opacity-50 group-hover:opacity-70 transition-opacity" />
<div className="p-6 relative">
<div className="flex justify-between items-start">
<div className="p-2 bg-blue-100 rounded-lg">
<School className="h-5 w-5 text-blue-600" />
</div>
<div className="flex items-center text-sm text-green-600">
<TrendingUp className="h-4 w-4 mr-1" />
+12.5%
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-muted-foreground">
Total Laporan
</h3>
<p className="text-2xl font-bold">1,305</p>
</div>
</div>
<div className="text-sm text-muted-foreground">
Senin, 03 Feb 2025
</Card>
<Card className="relative overflow-hidden group transition-all hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-purple-50 to-transparent opacity-50 group-hover:opacity-70 transition-opacity" />
<div className="p-6 relative">
<div className="flex justify-between items-start">
<div className="p-2 bg-purple-100 rounded-lg">
<Building className="h-5 w-5 text-purple-600" />
</div>
<div className="flex items-center text-sm text-green-600">
<TrendingUp className="h-4 w-4 mr-1" />
+8.2%
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-muted-foreground">
Jumlah Perusahaan
</h3>
<p className="text-2xl font-bold">847</p>
</div>
</div>
</div>
<div className="flex">
<button
data-active="true"
className="flex flex-1 flex-col justify-center gap-1 border-t px-6 py-4 text-left even:border-l data-[active=true]:bg-muted/50 sm:border-l sm:border-t-0 sm:px-8 sm:py-6"
>
<span className="text-xs text-muted-foreground">
Seluruh Perusahaan
</span>
<span className="text-lg font-bold leading-none sm:text-3xl">
2336
</span>
</button>
</div>
</div>
<div className="w-full">
<ChartCard />
</Card>
<Card className="relative overflow-hidden group transition-all hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-green-50 to-transparent opacity-50 group-hover:opacity-70 transition-opacity" />
<div className="p-6 relative">
<div className="flex justify-between items-start">
<div className="p-2 bg-green-100 rounded-lg">
<UsersRound className="h-5 w-5 text-green-600" />
</div>
<div className="flex items-center text-sm text-red-600">
<ArrowDownRight className="h-4 w-4 mr-1" />
-3.1%
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-muted-foreground">
Jumlah Verifikator
</h3>
<p className="text-2xl font-bold">124</p>
</div>
</div>
</Card>
<Card className="relative overflow-hidden group transition-all hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-cyan-50 to-transparent opacity-50 group-hover:opacity-70 transition-opacity" />
<div className="p-6 relative">
<div className="flex justify-between items-start">
<div className="p-2 bg-cyan-100 rounded-lg">
<Waves className="h-5 w-5 text-cyan-600" />
</div>
<div className="flex items-center text-sm text-green-600">
<ArrowUpRight className="h-4 w-4 mr-1" />
+5.7%
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-muted-foreground">
Air Limbah
</h3>
<p className="text-2xl font-bold">2,431</p>
</div>
</div>
</Card>
<Card className="relative overflow-hidden group transition-all hover:shadow-lg">
<div className="absolute inset-0 bg-gradient-to-br from-red-50 to-transparent opacity-50 group-hover:opacity-70 transition-opacity" />
<div className="p-6 relative">
<div className="flex justify-between items-start">
<div className="p-2 bg-red-100 rounded-lg">
<Skull className="h-5 w-5 text-red-600" />
</div>
<div className="flex items-center text-sm text-red-600">
<ArrowDownRight className="h-4 w-4 mr-1" />
-2.3%
</div>
</div>
<div className="mt-4">
<h3 className="text-sm font-medium text-muted-foreground">
Limbah B3
</h3>
<p className="text-2xl font-bold">567</p>
</div>
</div>
</Card>
</div>
{/* <div className="flex flex-1 flex-col gap-4 h-full mt-4">
<div>
<h2>Total Cerobong per hari ini</h2>
<span>Senin, 03 Feb 2025</span>
</div>
<div className="grid gap-4 md:grid-cols-4 items-center w-full">
<CerobongCard
title="Seluruh Perusahaan"
value={1305}
fromColor="from-[#F7E9FF]"
toColor="#fffff"
/>
<CardChart />
</div>
</div> */}
{/* <div className="flex-row mt-5">
<div className="w-1/3 gap-4 md:grid-cols-3 items-center">
<div className="flex flex-row gap-4 h-full">
<div className="flex flex-col">
<h2>Total Cerobong per hari ini</h2>
<span>Senin, 03 Feb 2025</span>
{/* Cerobong Stats Section */}
<Card className="relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-br from-orange-50/50 to-transparent" />
<div className="relative p-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-orange-100 rounded-lg">
<Activity className="h-5 w-5 text-orange-600" />
</div>
<div>
<h3 className="text-xl font-semibold">
Total Cerobong
</h3>
<p className="text-sm text-muted-foreground">
Update terakhir: Senin, 03 Feb 2025
</p>
</div>
</div>
<div className="text-right">
<p className="text-3xl font-bold">2,336</p>
<div className="flex items-center justify-end gap-1 text-sm text-green-600 mt-1">
<span>Seluruh Perusahaan</span>
</div>
</div>
</div>
</div>
<div className="w-2/3 gap-4 md:grid-cols-3 items-center mt-3 ">
<CerobongCard
title="Jumlah Perusahaan"
value={1305}
fromColor="from-[#F7E9FF]"
toColor=""
/>
</div>
</div> */}
</Card>
{/* Chart Section */}
<div className="w-full">
<ChartCard />
</div>
</div>
</AuthenticatedLayout>
);

View File

@ -0,0 +1,97 @@
export interface Perusahaan {
[x: string]: any;
PerusahaanId: number;
NamaPerusahaan: string;
Alamat: string;
KodePos: string;
Telepon: string;
Fax: string;
Email: string;
Lintang: string;
Bujur: string;
CPNama: string;
CPTelepon: string;
ILNomor: string;
ILTanggal: string;
ILDokumen: string;
ReportLocked: boolean;
JenisKegiatan: {
NamaJenisKegiatan: string;
};
Kelurahan: {
NamaKelurahan: string;
Kecamatan: {
NamaKecamatan: string;
Kabupaten: {
NamaKabupaten: string;
};
};
};
Verifikator: {
NamaUnitKerja: string;
};
JenisDokIL: {
NamaJenisDokIL: string;
};
}
export interface FormDataType {
NomorInduk: string;
JenisKegiatanId: string;
NamaPerusahaan: string;
Alamat: string;
KelurahanId: string;
KodePos: string;
Telepon: string;
Fax: string;
Email: string;
Lintang: string;
Bujur: string;
CPNama: string;
CPTelepon: string;
JenisDokILId: string;
VerifikatorId: string;
IsPublish: boolean;
ILNomor: string;
ILTanggal: string;
ILDokumen: File | null;
ReportLocked: true;
PerusahaanId: string;
}
export interface Kawasan {
PerusahaanId: number;
NamaPerusahaan: string;
}
export interface JenisDokIL {
JenisDokILId: number;
NamaJenisDokIL: string;
}
export interface JenisKegiatan {
JenisKegiatanId: number;
NamaJenisKegiatan: string;
}
export interface Verifikator {
VerifikatorId: number;
NamaUnitKerja: string;
}
export interface Kabupaten {
KabupatenId: number;
NamaKabupaten: string;
}
export interface Kecamatan {
KecamatanId: number;
NamaKecamatan: string;
KabupatenId: number;
}
export interface Kelurahan {
KelurahanId: number;
NamaKelurahan: string;
KecamatanId: number;
}

View File

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\CategoryController;
use App\Http\Controllers\DaftarPerusahaanController;
use App\Http\Controllers\HistoryKegiatanController;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\HukumController;
@ -9,9 +10,11 @@ use App\Http\Controllers\JenisKegiatanController;
use App\Http\Controllers\JenisSanksiController;
use App\Http\Controllers\KategoriController;
use App\Http\Controllers\MenuController;
use App\Http\Controllers\PelaporanALController;
use App\Http\Controllers\PelaporanController;
use App\Http\Controllers\PengumumanController;
use App\Http\Controllers\PeraturanController;
use App\Http\Controllers\PerusahaanController;
use App\Http\Controllers\PostController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\RefCategoryController;
@ -37,7 +40,17 @@ Route::get('/', [HomeController::class, 'index'])->name('home');
// });
Route::fallback(function () {
return Inertia::render('404');
$path = request()->path();
if (str_starts_with($path, 'admin')) {
return Inertia::render('404Admin', [
'backUrl' => route('dashboard')
]);
}
return Inertia::render('404', [
'backUrl' => route('home')
]);
});
Route::get('/pengumuman', [PengumumanController::class, 'index'])->name('pengumuman');
@ -122,6 +135,10 @@ Route::middleware(['auth'])->prefix('admin')->group(function () {
Route::get('/pelaporan', [PelaporanController::class, 'index'])->name('admin.pelaporan.index');
});
Route::middleware(['auth'])->prefix('admin')->group(function () {
Route::get('/pelaporan/al', [PelaporanALController::class, 'index'])->name('admin.pelaporanAL.index');
});
Route::middleware(['auth'])->prefix('admin')->group(function () {
Route::get('/verifikasi', [VerifPelaporanController::class, 'index'])->name('admin.verifikasi.index');
});
@ -135,8 +152,17 @@ Route::middleware(['auth'])->prefix('admin')->group(function () {
Route::get('/post/add', [PostController::class, 'create'])->name('admin.post.create');
Route::post('/post', [PostController::class, 'store'])->name('admin.post.store');
Route::get('/post/{post}', [PostController::class, 'edit'])->name('admin.post.edit');
Route::put('/post/{post}', [PostController::class, 'update'])->name('admin.post.update');
Route::post('/post/{post}', [PostController::class, 'update'])->name('admin.post.update');
Route::delete('/post/{post}', [PostController::class, 'destroy'])->name('admin.post.destroy');
});
Route::middleware(['auth'])->prefix('admin')->group(function () {
Route::get('/perusahaan', [PerusahaanController::class, 'index'])->name('admin.perusahaan.index');
Route::get('/perusahaan/add', [PerusahaanController::class, 'create'])->name('admin.perusahaan.create');
Route::post('/perusahaan', [PerusahaanController::class, 'store'])->name('admin.perusahaan.store');
Route::get('/perusahaan/{perusahaan}', [PerusahaanController::class, 'edit'])->name('admin.perusahaan.edit');
Route::post('/perusahaan/{perusahaan}', [PerusahaanController::class, 'update'])->name('admin.perusahaan.update');
Route::delete('/perusahaan/{perusahaan}', [PerusahaanController::class, 'destroy'])->name('admin.perusahaan.destroy');
});
require __DIR__.'/auth.php';