commit cc98e0761e93f513eac5477b616dfe1850fdaa11 Author: jouel88 Date: Thu Mar 19 21:16:06 2026 +0700 Initial commit: Proyek HRIS Laravel diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..a186cd20 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[compose.yaml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..c0660ea1 --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +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_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=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}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..fcb21d39 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 00000000..230257c2 --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,12 @@ +name: Issues + +on: + issues: + types: [labeled] + +permissions: + issues: write + +jobs: + help-wanted: + uses: laravel/.github/.github/workflows/issues.yml@main diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 00000000..6f9f97ea --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,12 @@ +name: Pull Requests + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + +jobs: + uneditable: + uses: laravel/.github/.github/workflows/pull-requests.yml@main diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..4b505a03 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: + - master + - '*.x' + pull_request: + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [8.2, 8.3, 8.4] + + name: PHP ${{ matrix.php }} + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Copy environment file + run: cp .env.example .env + + - name: Generate app key + run: php artisan key:generate + + - name: Execute tests + run: php artisan test diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 00000000..70352331 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,13 @@ +name: Update Changelog + +on: + release: + types: [released] + +permissions: {} + +jobs: + update: + permissions: + contents: write + uses: laravel/.github/.github/workflows/update-changelog.yml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b4e5eec0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.fleet +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +Homestead.json +Homestead.yaml +Thumbs.db +_ide_helper.php +.phpstorm.meta.php + diff --git a/_ide_helper_models.php b/_ide_helper_models.php new file mode 100644 index 00000000..e4784e76 --- /dev/null +++ b/_ide_helper_models.php @@ -0,0 +1,753 @@ + + */ + + +namespace App\Models{ +/** + * @property string $id_approval + * @property string $id_surat + * @property int $id_approver + * @property string|null $id_ttd_approver + * @property int $tahap 1=Manajer, 2=HRD + * @property string $status + * @property string|null $catatan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\User $approver + * @property-read \App\Models\SuratIzin $suratIzin + * @property-read \App\Models\TandaTangan|null $tandaTanganApprover + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat query() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereCatatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdApproval($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdApprover($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdTtdApprover($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereTahap($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereUpdatedAt($value) + * @mixin \Eloquent + */ + class ApprovalSurat extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id + * @property int $id_user + * @property string|null $path_model_yml + * @property int $is_verified 0:Pending, 1:Approved, 2:Rejected + * @property string|null $last_updated + * @property string|null $encoding_wajah + * @property string $tanggal_latih + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\User $user + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah query() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereEncodingWajah($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereIsVerified($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereLastUpdated($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah wherePathModelYml($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereTanggalLatih($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereUpdatedAt($value) + * @mixin \Eloquent + */ + class DataWajah extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id + * @property string $id_penggunaan + * @property int $id_poin_sumber + * @property int $jumlah_diambil + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\PenggunaanPoin $penggunaan + * @property-read \App\Models\PoinLembur $poinSumber + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin query() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereIdPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereIdPoinSumber($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereJumlahDiambil($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereUpdatedAt($value) + * @mixin \Eloquent + */ + class DetailPenggunaanPoin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_divisi + * @property string $nama_divisi + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|Divisi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi query() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereIdDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereNamaDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Divisi extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id + * @property string $tanggal + * @property string $keterangan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur query() + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|HariLibur whereUpdatedAt($value) + * @mixin \Eloquent + */ + class HariLibur extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_jabatan + * @property string $nama_jabatan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan query() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereIdJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereNamaJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Jabatan extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_jadwal + * @property int|null $id_user + * @property string|null $tanggal + * @property int|null $id_shift + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\PenggunaanPoin|null $penggunaanPoin + * @property-read \App\Models\ShiftKerja|null $shift + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja query() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdJadwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereUpdatedAt($value) + * @mixin \Eloquent + */ + class JadwalKerja extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_jenis_izin + * @property string $nama_izin + * @property string|null $created_at + * @property string|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereIdJenisIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereNamaIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ + class JenisIzin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_kompensasi + * @property string $nama_kompensasi + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereIdKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereNamaKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereUpdatedAt($value) + * @mixin \Eloquent + */ + class JenisKompensasi extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_pengurangan + * @property string $nama_pengurangan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection $penggunaanPoin + * @property-read int|null $penggunaan_poin_count + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereIdPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereNamaPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereUpdatedAt($value) + * @mixin \Eloquent + */ + class JenisPengurangan extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_kantor + * @property string $nama_kantor + * @property string|null $alamat + * @property numeric|null $latitude + * @property numeric|null $longitude + * @property int $radius + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read int|null $users_count + * @method static \Illuminate\Database\Eloquent\Builder|Kantor newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor query() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereAlamat($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereIdKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereLatitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereLongitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereNamaKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereRadius($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Kantor extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property string $id_lembur + * @property int|null $id_user + * @property \Illuminate\Support\Carbon $tanggal_lembur + * @property string $jam_mulai + * @property string $jam_selesai + * @property int|null $durasi_menit + * @property string|null $keterangan + * @property int|null $jumlah_poin + * @property int|null $id_kompensasi + * @property int $id_status + * @property string|null $alasan_penolakan + * @property \Illuminate\Support\Carbon $tanggal_diajukan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\JenisKompensasi|null $kompensasi + * @property-read \App\Models\StatusPengajuan $status + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|Lembur newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur query() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereDurasiMenit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJamMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJamSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereTanggalLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Lembur extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property string $id_izin + * @property int|null $id_user + * @property string $tanggal_mulai + * @property string $tanggal_selesai + * @property int|null $id_jenis_izin + * @property string|null $alasan + * @property string|null $bukti_file + * @property int $id_status + * @property string|null $alasan_penolakan + * @property string $tanggal_diajukan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\JenisIzin|null $jenisIzin + * @property-read \App\Models\StatusPengajuan $statusPengajuan + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereAlasan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereBuktiFile($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdJenisIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ + class PengajuanIzin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property string $id_penggunaan + * @property int|null $id_user + * @property \Illuminate\Support\Carbon $tanggal_penggunaan + * @property int|null $jumlah_poin + * @property string|null $jam_masuk_custom + * @property string|null $jam_pulang_custom + * @property int|null $id_pengurangan + * @property int $id_status + * @property string|null $alasan_penolakan + * @property \Illuminate\Support\Carbon $tanggal_diajukan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\JenisPengurangan|null $jenisPengurangan + * @property-read \App\Models\StatusPengajuan $status + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin query() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJamMasukCustom($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJamPulangCustom($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereTanggalPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereUpdatedAt($value) + * @mixin \Eloquent + */ + class PenggunaanPoin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_pengumuman + * @property string|null $judul + * @property string|null $isi + * @property \Illuminate\Support\Carbon|null $tanggal + * @property int|null $dibuat_oleh + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\User|null $pembuat + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman query() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereDibuatOleh($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereIdPengumuman($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereIsi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereJudul($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Pengumuman extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_permission + * @property string $nama_permission + * @property string $slug + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection $roles + * @property-read int|null $roles_count + * @method static \Illuminate\Database\Eloquent\Builder|Permission newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Permission newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Permission query() + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereIdPermission($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereNamaPermission($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereSlug($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Permission extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|Poin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Poin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Poin query() + * @mixin \Eloquent + */ + class Poin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_poin + * @property int|null $id_user + * @property int|null $jumlah_poin + * @property int|null $sisa_poin + * @property string|null $id_lembur + * @property string|null $keterangan + * @property \Illuminate\Support\Carbon $tanggal + * @property \Illuminate\Support\Carbon|null $expired_at + * @property bool|null $is_fully_used + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\Lembur|null $lembur + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur query() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereExpiredAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIsFullyUsed($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereSisaPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereUpdatedAt($value) + * @mixin \Eloquent + */ + class PoinLembur extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_presensi + * @property int|null $id_user + * @property string $tanggal + * @property string|null $jam_masuk + * @property string|null $jam_pulang + * @property numeric|null $lat_masuk + * @property numeric|null $lon_masuk + * @property numeric|null $lat_pulang + * @property numeric|null $lon_pulang + * @property string|null $foto_wajah_masuk + * @property string|null $foto_wajah_pulang + * @property int|null $id_status + * @property string|null $alasan_telat + * @property string|null $keterangan_pulang + * @property string|null $waktu_terlambat + * @property string|null $waktu_masuk_awal + * @property string|null $waktu_pulang_awal + * @property string|null $waktu_pulang_akhir + * @property int $verifikasi_wajah + * @property int $id_validasi + * @property string|null $keterangan_luar_radius + * @property string|null $alasan_penolakan + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property string|null $deleted_at + * @property-read \App\Models\User|null $user + * @method static \Illuminate\Database\Eloquent\Builder|Presensi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi query() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereAlasanTelat($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereFotoWajahMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereFotoWajahPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdPresensi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdValidasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereJamMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereJamPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereKeteranganLuarRadius($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereKeteranganPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLatMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLatPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLonMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLonPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereVerifikasiWajah($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuMasukAwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuPulangAkhir($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuPulangAwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuTerlambat($value) + * @mixin \Eloquent + */ + class Presensi extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_riwayat + * @property int $id_user_1 + * @property int $id_jadwal_1 + * @property int $id_user_2 + * @property int $id_jadwal_2 + * @property string|null $keterangan + * @property int $created_by + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\User $execAdmin + * @property-read \App\Models\JadwalKerja $jadwal1 + * @property-read \App\Models\JadwalKerja $jadwal2 + * @property-read \App\Models\User $user1 + * @property-read \App\Models\User $user2 + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift query() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereCreatedBy($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdJadwal1($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdJadwal2($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdRiwayat($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdUser1($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdUser2($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereUpdatedAt($value) + * @mixin \Eloquent + */ + class RiwayatTukarShift extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_role + * @property string $nama_role + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection $permissions + * @property-read int|null $permissions_count + * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read int|null $users_count + * @method static \Illuminate\Database\Eloquent\Builder|Role newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Role newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Role query() + * @method static \Illuminate\Database\Eloquent\Builder|Role whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereIdRole($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereNamaRole($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereUpdatedAt($value) + * @mixin \Eloquent + */ + class Role extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_shift + * @property string|null $nama_shift + * @property string|null $jam_mulai + * @property string|null $jam_selesai + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection $jadwalKerja + * @property-read int|null $jadwal_kerja_count + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja query() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereIdShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereJamMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereJamSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereNamaShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereUpdatedAt($value) + * @mixin \Eloquent + */ + class ShiftKerja extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id_status + * @property string $nama_status + * @property string|null $created_at + * @property string|null $updated_at + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan query() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereNamaStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereUpdatedAt($value) + * @mixin \Eloquent + */ + class StatusPengajuan extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property string $id_surat + * @property string $id_izin + * @property int $id_user + * @property string $nomor_surat + * @property string $isi_surat + * @property string|null $id_ttd_pengaju + * @property string $status_surat + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\ApprovalSurat|null $approvalHrd + * @property-read \App\Models\ApprovalSurat|null $approvalManajer + * @property-read \Illuminate\Database\Eloquent\Collection $approvals + * @property-read int|null $approvals_count + * @property-read \App\Models\PengajuanIzin $pengajuanIzin + * @property-read \App\Models\TandaTangan|null $tandaTanganPengaju + * @property-read \App\Models\User $user + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdTtdPengaju($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIsiSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereNomorSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereStatusSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ + class SuratIzin extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property string $id_tanda_tangan + * @property int $id_user + * @property string $file_ttd + * @property int $is_active + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \App\Models\User $user + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan active() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan query() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereFileTtd($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIdTandaTangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIsActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereUpdatedAt($value) + * @mixin \Eloquent + */ + class TandaTangan extends \Eloquent {} +} + +namespace App\Models{ +/** + * @property int $id + * @property string|null $nik + * @property string $nama_lengkap + * @property string $email + * @property string|null $no_telp + * @property string|null $alamat + * @property string|null $email_verified_at + * @property string $password + * @property int $sisa_cuti + * @property string|null $foto + * @property string|null $remember_token + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property int|null $id_kantor + * @property int|null $id_divisi + * @property int|null $id_jabatan + * @property int $status_aktif + * @property string|null $tgl_bergabung + * @property int $is_face_registered + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read \App\Models\DataWajah|null $dataWajah + * @property-read \App\Models\Divisi|null $divisi + * @property-read \App\Models\Jabatan|null $jabatan + * @property-read \Illuminate\Database\Eloquent\Collection $jadwalKerja + * @property-read int|null $jadwal_kerja_count + * @property-read \App\Models\Kantor|null $kantor + * @property-read \Illuminate\Database\Eloquent\Collection $lemburs + * @property-read int|null $lemburs_count + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read int|null $notifications_count + * @property-read \Illuminate\Database\Eloquent\Collection $roles + * @property-read int|null $roles_count + * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read int|null $tokens_count + * @method static \Database\Factories\UserFactory factory($count = null, $state = []) + * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|User query() + * @method static \Illuminate\Database\Eloquent\Builder|User whereAlamat($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerifiedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereFoto($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIsFaceRegistered($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNamaLengkap($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNik($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNoTelp($value) + * @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereSisaCuti($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereStatusAktif($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereTglBergabung($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User withTrashed(bool $withTrashed = true) + * @method static \Illuminate\Database\Eloquent\Builder|User withoutTrashed() + * @mixin \Eloquent + */ + class User extends \Eloquent {} +} + diff --git a/app/Console/Commands/AccrueAnnualLeave.php b/app/Console/Commands/AccrueAnnualLeave.php new file mode 100644 index 00000000..d7f7952d --- /dev/null +++ b/app/Console/Commands/AccrueAnnualLeave.php @@ -0,0 +1,38 @@ +info('Starting annual leave accrual process...'); + + $activeUsers = \App\Models\User::where('status_aktif', '=', 1)->get(); + $count = 0; + + foreach ($activeUsers as $user) { + + $user->increment('sisa_cuti', 12); + $count++; + } + + $this->success("Successfully accrued leave for {$count} active employees."); + } +} diff --git a/app/Console/Commands/AutoPresensiJob.php b/app/Console/Commands/AutoPresensiJob.php new file mode 100644 index 00000000..66cdaf07 --- /dev/null +++ b/app/Console/Commands/AutoPresensiJob.php @@ -0,0 +1,79 @@ +format('Y-m-d'); + + $usersWithJadwal = JadwalKerja::where('tanggal', $hariIni) + ->with('user') + ->get() + ->pluck('id_user'); + + $usersPresensi = Presensi::where('tanggal', $hariIni)->pluck('id_user'); + + $usersAlpha = $usersWithJadwal->diff($usersPresensi); + + $idAlpha = DB::table('status_presensi')->where('nama_status', 'Alpha')->value('id_status') ?? 0; + + foreach ($usersAlpha as $userId) { + Presensi::create([ + 'id_user' => $userId, + 'tanggal' => $hariIni, + 'id_status' => $idAlpha, + 'id_validasi' => 1, + 'alasan_telat' => 'Auto Alpha by System' + ]); + } + + $this->info('Auto Alpha processed for ' . $usersAlpha->count() . ' users.'); + + $dateLimit = Carbon::now()->subMonths(3); + + $oldPresensi = Presensi::where('tanggal', '<', $dateLimit)->get(); + + $countDeleted = 0; + foreach ($oldPresensi as $p) { + + if ($p->foto_wajah_masuk && Storage::disk('public')->exists($p->foto_wajah_masuk)) { + Storage::disk('public')->delete($p->foto_wajah_masuk); + $p->foto_wajah_masuk = null; + } + + if ($p->foto_wajah_pulang && Storage::disk('public')->exists($p->foto_wajah_pulang)) { + Storage::disk('public')->delete($p->foto_wajah_pulang); + $p->foto_wajah_pulang = null; + } + + $p->save(); + $countDeleted++; + } + + $this->info("Cleanup photos completed. Processed $countDeleted records."); + } +} diff --git a/app/Exports/LaporanLemburExport.php b/app/Exports/LaporanLemburExport.php new file mode 100644 index 00000000..69474dbe --- /dev/null +++ b/app/Exports/LaporanLemburExport.php @@ -0,0 +1,86 @@ +pegawai = $pegawai; + $this->rekap = $rekap; + $this->bulan = $bulan; + $this->tahun = $tahun; + } + + public function collection() + { + return collect($this->pegawai); + } + + public function headings(): array + { + return [ + ['LAPORAN REKAPITULASI LEMBUR PEGAWAI'], + ['Periode: ' . \Carbon\Carbon::create()->month($this->bulan)->translatedFormat('F') . ' ' . $this->tahun], + [], + [ + 'No', + 'NIK', + 'Nama Lengkap', + 'Divisi / Dept', + 'Jabatan', + 'Jumlah Hari Lembur', + 'Total Waktu (Menit)', + 'Durasi Jam', + 'Poin Lembur Diperoleh' + ] + ]; + } + + public function map($row): array + { + static $no = 0; + $no++; + + $dataRekap = $this->rekap[$row->id] ?? [ + 'total_menit' => 0, + 'format_jam' => '0j 0m', + 'jumlah_hari' => 0, + 'poin_diperoleh' => 0, + ]; + + return [ + $no, + $row->nik ?? '-', + $row->nama_lengkap, + $row->divisi->nama_divisi ?? '-', + $row->jabatan->nama_jabatan ?? '-', + $dataRekap['jumlah_hari'] . ' Hari', + $dataRekap['total_menit'] . ' Menit', + $dataRekap['format_jam'], + $dataRekap['poin_diperoleh'] . ' Poin' + ]; + } + + public function styles(Worksheet $sheet) + { + return [ + + 1 => ['font' => ['bold' => true, 'size' => 14]], + 2 => ['font' => ['italic' => true]], + 4 => ['font' => ['bold' => true], 'fill' => ['fillType' => 'solid', 'color' => ['rgb' => 'E6F4EA']]], + ]; + } +} diff --git a/app/Exports/LaporanPresensiExport.php b/app/Exports/LaporanPresensiExport.php new file mode 100644 index 00000000..74cf2a2c --- /dev/null +++ b/app/Exports/LaporanPresensiExport.php @@ -0,0 +1,81 @@ +rekap = $rekap; + $this->bulan = $bulan; + $this->tahun = $tahun; + } + + public function collection() + { + return collect($this->rekap); + } + + public function headings(): array + { + return [ + ['LAPORAN REKAPITULASI PRESENSI PEGAWAI'], + ['Periode: ' . \Carbon\Carbon::createFromDate($this->tahun, (int) $this->bulan, 1)->translatedFormat('F') . ' ' . $this->tahun], + [], + [ + 'No', + 'NIK', + 'Nama Lengkap', + 'Jabatan', + 'Divisi', + 'Hadir', + 'Izin / Cuti', + 'Sakit', + 'Alpha (Mangkir)', + 'Terlambat', + 'Poin Lembur Diperoleh' + ] + ]; + } + + public function map($row): array + { + static $no = 0; + $no++; + + return [ + $no, + $row['user']->nik ?? '-', + $row['user']->nama_lengkap, + $row['user']->jabatan->nama_jabatan ?? '-', + $row['user']->divisi->nama_divisi ?? '-', + $row['hadir'] . ' Hari', + $row['izin'] . ' Hari', + $row['sakit'] . ' Hari', + $row['alpha'] . ' Hari', + $row['terlambat'] . ' Kali', + $row['poin_lembur'] . ' Poin' + ]; + } + + public function styles(Worksheet $sheet) + { + return [ + + 1 => ['font' => ['bold' => true, 'size' => 14]], + 2 => ['font' => ['italic' => true]], + 4 => ['font' => ['bold' => true], 'fill' => ['fillType' => 'solid', 'color' => ['rgb' => 'E2E8F0']]], + ]; + } +} diff --git a/app/Helpers/ApiResponse.php b/app/Helpers/ApiResponse.php new file mode 100644 index 00000000..adc393ee --- /dev/null +++ b/app/Helpers/ApiResponse.php @@ -0,0 +1,45 @@ +json([ + 'success' => true, + 'message' => $message, + 'data' => $data, + ], $code); + } + + public static function error(string $message = 'Terjadi kesalahan', int $code = 400, $errors = null) + { + $response = [ + 'success' => false, + 'message' => $message, + ]; + + if ($errors) { + $response['errors'] = $errors; + } + + return response()->json($response, $code); + } + + public static function validationError($errors, string $message = 'Validasi gagal') + { + return self::error($message, 422, $errors); + } + + public static function notFound(string $message = 'Data tidak ditemukan') + { + return self::error($message, 404); + } + + public static function unauthorized(string $message = 'Tidak memiliki akses') + { + return self::error($message, 401); + } +} diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php new file mode 100644 index 00000000..00566270 --- /dev/null +++ b/app/Http/Controllers/Api/AuthController.php @@ -0,0 +1,148 @@ +all(), [ + 'email' => 'required|email', + 'password' => 'required', + ]); + + if ($validate->fails()) { + return ApiResponse::validationError($validate->errors()); + } + + if (!Auth::attempt($request->only('email', 'password'))) { + return ApiResponse::unauthorized('Email atau Password salah'); + } + + $user = User::where('email', $request->email) + ->with(['divisi', 'jabatan', 'kantor', 'roles']) + ->first(); + + if ($user->status_aktif != 1) { + return ApiResponse::error('Akun Anda dinonaktifkan. Hubungi admin.', 403); + } + + $token = $user->createToken('auth_token')->plainTextToken; + + return ApiResponse::success([ + 'token' => $token, + 'user' => $user + ], 'Login berhasil'); + + } catch (\Exception $e) { + \Illuminate\Support\Facades\Log::error('Login Error: ' . $e->getMessage()); + return ApiResponse::error('Terjadi kesalahan server: ' . $e->getMessage(), 500); + } + } + + public function logout(Request $request) + { + try { + /** @var \Laravel\Sanctum\PersonalAccessToken $token */ + $token = $request->user()->currentAccessToken(); + $token->delete(); + return ApiResponse::success(null, 'Logout berhasil'); + } catch (\Exception $e) { + return ApiResponse::error('Gagal logout: ' . $e->getMessage(), 500); + } + } + + public function updateProfile(Request $request) + { + try { + $user = $request->user(); + + $validate = Validator::make($request->all(), [ + 'nama_lengkap' => 'required|string|max:255', + 'no_telp' => 'nullable|string|max:15', + 'password' => 'nullable|min:6', + 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', + ]); + + if ($validate->fails()) { + return ApiResponse::validationError($validate->errors()); + } + + $data = [ + 'nama_lengkap' => $request->nama_lengkap, + 'no_telp' => $request->no_telp, + ]; + + if ($request->filled('password')) { + $data['password'] = Hash::make($request->password); + } + + if ($request->hasFile('foto')) { + + if ($user->foto) { + Storage::disk('public')->delete($user->foto); + } + $data['foto'] = $request->file('foto')->store('foto-profil', 'public'); + } + + $user->update($data); + + return ApiResponse::success($user, 'Profil berhasil diperbarui'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal update profil: ' . $e->getMessage(), 500); + } + } + + public function user(Request $request) + { + $user = $request->user()->load(['roles', 'divisi', 'jabatan', 'kantor']); + return ApiResponse::success($user); + } +} diff --git a/app/Http/Controllers/Api/DashboardController.php b/app/Http/Controllers/Api/DashboardController.php new file mode 100644 index 00000000..aa65b191 --- /dev/null +++ b/app/Http/Controllers/Api/DashboardController.php @@ -0,0 +1,141 @@ +load(['divisi', 'jabatan', 'kantor']); + + $today = Carbon::now('Asia/Jakarta')->toDateString(); + + $jadwal = JadwalKerja::with(['shift']) + ->where('id_user', $user->id) + ->where('tanggal', $today) + ->first(); + + $presensi = Presensi::where('id_user', $user->id) + ->where('tanggal', $today) + ->first(); + + $totalPoin = (new PoinService())->getActivePoints($user->id); + + $jamMasuk = $jadwal ? $jadwal->shift->jam_mulai : '-'; + $jamPulang = $jadwal ? $jadwal->shift->jam_selesai : '-'; + $isAdjusted = false; + $adjustmentNote = null; + $statusJadwal = 'Normal'; + + if ($jadwal) { + + $poinOverrides = PenggunaanPoin::where('id_user', $user->id) + ->where('tanggal_penggunaan', $today) + ->where('id_status', 2) + ->get(); + + foreach ($poinOverrides as $poin) { + + if ($poin->id_pengurangan == 4 && $poin->jam_masuk_custom) { + $jamMasuk = substr($poin->jam_masuk_custom, 0, 5) . ':00'; + $isAdjusted = true; + $adjustmentNote = "Datang Siang (Via Poin)"; + $statusJadwal = 'Masuk Siang'; + } + + if ($poin->id_pengurangan == 5 && $poin->jam_pulang_custom) { + $jamPulang = substr($poin->jam_pulang_custom, 0, 5) . ':00'; + $isAdjusted = true; + $adjustmentNote = $adjustmentNote ? "Masuk Siang & Pulang Cepat" : "Pulang Cepat (Via Poin)"; + $statusJadwal = $statusJadwal == 'Masuk Siang' ? 'Full Custom' : 'Pulang Cepat'; + } + } + } + + $ketMasuk = 'Belum Absen'; + $ketPulang = null; + + if ($presensi) { + if ($presensi->waktu_terlambat) { + $ketMasuk = 'Terlambat'; + } else if ($presensi->waktu_masuk_awal) { + $ketMasuk = (str_contains($statusJadwal, 'Poin')) ? 'Masuk Siang (Poin) - Datang Awal' : 'Datang Awal'; + } else { + $ketMasuk = (str_contains($statusJadwal, 'Poin')) ? 'Masuk Siang (Poin)' : 'Tepat Waktu'; + } + + if ($presensi->jam_pulang) { + if ($presensi->waktu_pulang_awal) { + $ketPulang = 'Pulang Awal'; + } else if ($presensi->waktu_pulang_akhir) { + $ketPulang = (str_contains($statusJadwal, 'Poin')) ? 'Pulang Cepat (Poin) - Lembur' : 'Lembur / Pulang Akhir'; + } else { + $ketPulang = (str_contains($statusJadwal, 'Poin')) ? 'Pulang Cepat (Poin)' : 'Tepat Waktu'; + } + } + } + + $dashboardData = [ + 'user' => [ + 'id' => $user->id, + 'nama_lengkap' => $user->nama_lengkap, + 'email' => $user->email, + 'foto' => $user->foto, + 'jabatan' => $user->jabatan->nama_jabatan ?? 'N/A', + 'divisi' => $user->divisi->nama_divisi ?? 'N/A', + 'sisa_cuti' => (int) ($user->sisa_cuti ?? 0), + 'status_aktif' => $user->status_aktif ?? 'Aktif', + ], + 'poin' => (int) $totalPoin, + 'statistik_absensi' => [ + 'izin' => Presensi::where('id_user', $user->id) + ->whereMonth('tanggal', now()->month) + ->whereYear('tanggal', now()->year) + ->where('id_status', 3) + ->count(), + 'alpha' => Presensi::where('id_user', $user->id) + ->whereMonth('tanggal', now()->month) + ->whereYear('tanggal', now()->year) + ->where('id_status', 5) + ->count(), + ], + 'jadwal_hari_ini' => $jadwal ? [ + 'hari' => Carbon::parse($today, 'Asia/Jakarta')->translatedFormat('l, d F Y'), + 'shift' => $jadwal->shift->nama_shift ?? '-', + 'jam_masuk' => $jamMasuk, + 'jam_pulang' => $jamPulang, + 'is_adjusted' => $isAdjusted, + 'note' => $adjustmentNote, + 'status_jadwal' => $statusJadwal, + 'kantor_nama' => $user->kantor->nama_kantor ?? '-', + 'kantor_lat' => (float) ($user->kantor->latitude ?? 0), + 'kantor_lon' => (float) ($user->kantor->longitude ?? 0), + 'kantor_radius' => (float) ($user->kantor->radius ?? 200), + ] : null, + 'presensi_hari_ini' => $presensi ? [ + 'jam_masuk' => $presensi->jam_masuk, + 'jam_pulang' => $presensi->jam_pulang, + 'status' => $presensi->id_status, + 'keterangan' => $ketMasuk, + 'keterangan_pulang' => $ketPulang, + ] : null, + ]; + + return ApiResponse::success($dashboardData, 'Dashboard data berhasil dimuat'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat dashboard: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/FaceEnrollmentController.php b/app/Http/Controllers/Api/FaceEnrollmentController.php new file mode 100644 index 00000000..d7f78c69 --- /dev/null +++ b/app/Http/Controllers/Api/FaceEnrollmentController.php @@ -0,0 +1,115 @@ +faceService = $faceService; + } + + public function enrollFace(Request $request) + { + + $validator = \Validator::make($request->all(), [ + 'foto_depan' => 'required|image|mimes:jpeg,png,jpg|max:5120', + 'foto_kanan' => 'required|image|mimes:jpeg,png,jpg|max:5120', + 'foto_kiri' => 'required|image|mimes:jpeg,png,jpg|max:5120', + 'foto_bawah' => 'required|image|mimes:jpeg,png,jpg|max:5120', + ]); + + if ($validator->fails()) { + return response()->json([ + 'success' => false, + 'message' => 'Validasi gagal', + 'errors' => $validator->errors() + ], 422); + } + + try { + $user = $request->user(); + + $photos = [ + 'depan' => $request->file('foto_depan'), + 'kanan' => $request->file('foto_kanan'), + 'kiri' => $request->file('foto_kiri'), + 'bawah' => $request->file('foto_bawah'), + ]; + + $result = $this->faceService->enrollFace($user->id, $photos); + + return response()->json([ + 'success' => true, + 'message' => $result['message'], + 'data' => null + ], 200); + + } catch (\Exception $e) { + Log::error('Face Enrollment Error: ' . $e->getMessage()); + return response()->json([ + 'success' => false, + 'message' => 'Terjadi kesalahan sistem: ' . $e->getMessage() + ], 500); + } + } + + public function getFaceStatus(Request $request) + { + $user = $request->user(); + + $dataWajah = \DB::table('data_wajah')->where('id_user', $user->id)->first(); + + $status = 'not_registered'; + if ($user->is_face_registered) { + $status = 'pending'; + if ($dataWajah && $dataWajah->is_verified == 1) { + $status = 'verified'; + } elseif ($dataWajah && $dataWajah->is_verified == 2) { + $status = 'rejected'; + } + } + + return response()->json([ + 'success' => true, + 'data' => [ + 'status' => $status, + 'is_registered' => (bool) $user->is_face_registered + ] + ]); + } + + public function verifyFace(Request $request) + { + $request->validate([ + 'foto' => 'required|image|max:5120', + ]); + + try { + $user = $request->user(); + $file = $request->file('foto'); + + $result = $this->faceService->verifyFace($user->id, $file); + + return response()->json([ + 'success' => true, + 'message' => 'Wajah terverifikasi', + 'data' => $result + ]); + + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => 'Verifikasi Gagal: ' . $e->getMessage() + ], 400); + } + } +} diff --git a/app/Http/Controllers/Api/KompensasiController.php b/app/Http/Controllers/Api/KompensasiController.php new file mode 100644 index 00000000..98411ce4 --- /dev/null +++ b/app/Http/Controllers/Api/KompensasiController.php @@ -0,0 +1,21 @@ +getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/LemburController.php b/app/Http/Controllers/Api/LemburController.php new file mode 100644 index 00000000..eb9c0760 --- /dev/null +++ b/app/Http/Controllers/Api/LemburController.php @@ -0,0 +1,120 @@ +lemburService = $lemburService; + } + + public function store(StoreLemburRequest $request) + { + try { + $user = Auth::user(); + + $this->lemburService->createLembur($user, $request->validated()); + + return ApiResponse::success(null, 'Pengajuan lembur berhasil dikirim.', 201); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal mengajukan lembur: ' . $e->getMessage(), 500); + } + } + + /** + * @OA\Put( + * path="/api/lembur/{id}", + * tags={"Lembur"}, + * summary="Update Overtime Request", + * security={{"bearerAuth":{}}}, + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * required={"tanggal_lembur", "jam_mulai", "jam_selesai"}, + * @OA\Property(property="tanggal_lembur", type="string", format="date", example="2023-10-25"), + * @OA\Property(property="jam_mulai", type="string", format="time", example="17:00"), + * @OA\Property(property="jam_selesai", type="string", format="time", example="20:00"), + * @OA\Property(property="keterangan", type="string", example="Updated reason"), + * @OA\Property(property="id_kompensasi", type="integer", example=1) + * ) + * ), + * @OA\Response( + * response=200, + * description="Overtime request updated successfully", + * @OA\JsonContent( + * @OA\Property(property="success", type="boolean", example=true), + * @OA\Property(property="message", type="string", example="Pengajuan lembur berhasil diperbarui.") + * ) + * ) + * ) + */ + public function update(\App\Http\Requests\UpdateLemburRequest $request, $id) + { + try { + + $lembur = Lembur::where('id_user', Auth::id()) + ->where('id_lembur', $id) + ->where('id_status', 1) + ->first(); + + if (!$lembur) { + return ApiResponse::error('Data lembur tidak ditemukan atau sudah diproses.', 404); + } + + $lembur->update($request->validated()); + + return ApiResponse::success(null, 'Pengajuan lembur berhasil diperbarui.'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memperbarui lembur: ' . $e->getMessage(), 500); + } + } + + public function history(Request $request) + { + try { + $query = Lembur::with(['status', 'kompensasi']) + ->where('id_user', Auth::id()); + + if ($request->has('status')) { + $statusParam = $request->get('status'); + + $statusId = match ($statusParam) { + 'pending' => 1, + 'approved' => 2, + 'rejected' => 3, + default => null + }; + + if ($statusId) { + $query->where('id_status', $statusId); + } + } + + $history = $query->orderBy('created_at', 'desc')->get(); + + return ApiResponse::success(['data' => $history], 'Riwayat lembur berhasil dimuat'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat riwayat lembur: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/PengumumanApiController.php b/app/Http/Controllers/Api/PengumumanApiController.php new file mode 100644 index 00000000..ec6bc9fd --- /dev/null +++ b/app/Http/Controllers/Api/PengumumanApiController.php @@ -0,0 +1,34 @@ +orderBy('tanggal', 'desc') + ->limit(5) + ->get() + ->map(function ($item) { + return [ + 'id' => $item->id_pengumuman, + 'title' => $item->judul, + 'description' => $item->isi, + 'tanggal' => $item->tanggal?->format('Y-m-d'), + 'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'Admin', + 'avatar_url' => $item->pembuat?->foto, + ]; + }); + + return ApiResponse::success($pengumuman, 'Pengumuman berhasil dimuat'); + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat pengumuman: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/PoinController.php b/app/Http/Controllers/Api/PoinController.php new file mode 100644 index 00000000..7093e307 --- /dev/null +++ b/app/Http/Controllers/Api/PoinController.php @@ -0,0 +1,105 @@ +user()->id; + + $poinService = new PoinService(); + $poinExpiring = $poinService->getExpiringPoints($userId); + $activePoints = $poinService->getActivePoints($userId); + + $firstExpiring = $poinExpiring->first(); + $formattedExpiring = null; + + if ($firstExpiring) { + $formattedExpiring = [ + 'poin' => (int) $firstExpiring->sisa_poin, + 'tanggal_kadaluarsa' => $firstExpiring->expired_at->format('Y-m-d'), + ]; + } + + return ApiResponse::success([ + 'expiring_points' => $formattedExpiring, + 'total_poin' => (int) $activePoints + ]); + + } catch (\Exception $e) { + return ApiResponse::error('Terjadi kesalahan: ' . $e->getMessage(), 500); + } + } + + public function getPointHistory() + { + try { + $userId = auth()->user()->id; + + $poinService = new PoinService(); + $history = $poinService->getPointHistory($userId); + + return ApiResponse::success(['history' => $history]); + + } catch (\Exception $e) { + return ApiResponse::error('Terjadi kesalahan: ' . $e->getMessage(), 500); + } + } + + public function redeem(Request $request) + { + $validator = Validator::make($request->all(), [ + 'jumlah' => 'required|integer|min:1', + 'keterangan' => 'required|string', + 'id_pengurangan' => 'required|integer', + 'jam_masuk_custom' => 'required_if:id_pengurangan,4|date_format:H:i|nullable', + 'jam_pulang_custom' => 'required_if:id_pengurangan,5|date_format:H:i|nullable', + ]); + + if ($validator->fails()) { + return ApiResponse::validationError($validator->errors()); + } + + try { + $userId = auth()->user()->id; + $jumlah = $request->jumlah; + $keterangan = $request->keterangan; + + $poinService = new PoinService(); + $totalPoin = $poinService->getActivePoints($userId); + + if ($totalPoin < $jumlah) { + return ApiResponse::error('Saldo poin tidak mencukupi', 400); + } + + preg_match('/\[(\d{4}-\d{2}-\d{2})\]/', $keterangan, $matches); + $tanggalPenggunaan = isset($matches[1]) ? $matches[1] : now()->format('Y-m-d'); + + PenggunaanPoin::create([ + 'id_user' => $userId, + 'tanggal_penggunaan' => $tanggalPenggunaan, + 'jumlah_poin' => $jumlah, + 'id_pengurangan' => $request->id_pengurangan, + 'jam_masuk_custom' => $request->jam_masuk_custom, + 'jam_pulang_custom' => $request->jam_pulang_custom, + 'id_status' => 1, + 'tanggal_diajukan' => now(), + ]); + + return ApiResponse::success(null, 'Pengajuan penukaran poin berhasil disimpan'); + } catch (\Exception $e) { + return ApiResponse::error($e->getMessage(), 400); + } + } +} diff --git a/app/Http/Controllers/Api/PresensiController.php b/app/Http/Controllers/Api/PresensiController.php new file mode 100644 index 00000000..37550242 --- /dev/null +++ b/app/Http/Controllers/Api/PresensiController.php @@ -0,0 +1,257 @@ +presensiService = $presensiService; + } + + public function index() + { + $user = Auth::user(); + $today = Carbon::now('Asia/Jakarta')->toDateString(); + + $jadwal = \App\Models\JadwalKerja::with(['shift', 'user']) + ->where('id_user', $user->id) + ->where('tanggal', $today) + ->first(); + + if (!$jadwal) { + return response()->json([ + 'success' => false, + 'message' => 'Tidak ada jadwal hari ini' + ], 404); + } + + $jamMasuk = $jadwal->shift->jam_mulai; + $jamPulang = $jadwal->shift->jam_selesai; + $infoJadwal = $jadwal->shift->nama_shift; + + $poinOverrides = PenggunaanPoin::where('id_user', $user->id) + ->whereDate('tanggal_penggunaan', $today) + ->where('id_status', 2) + ->get(); + + foreach ($poinOverrides as $poin) { + if ($poin->id_pengurangan == 4 && $poin->jam_masuk_custom) { + $jamMasuk = substr($poin->jam_masuk_custom, 0, 5) . ':00'; + $infoJadwal .= ' (Masuk Siang)'; + } + if ($poin->id_pengurangan == 5 && $poin->jam_pulang_custom) { + $jamPulang = substr($poin->jam_pulang_custom, 0, 5) . ':00'; + $infoJadwal .= ' (Pulang Cepat)'; + } + } + + $presensi = Presensi::where('id_user', $user->id) + ->where('tanggal', $today) + ->first(); + + $ketMasuk = 'Belum Absen'; + $ketPulang = null; + + if ($presensi) { + if ($presensi->waktu_terlambat) { + $ketMasuk = 'Terlambat'; + } else if ($presensi->waktu_masuk_awal) { + $ketMasuk = (str_contains($infoJadwal, 'Poin')) ? 'Masuk Siang (Poin) - Datang Awal' : 'Datang Awal'; + } else { + $ketMasuk = (str_contains($infoJadwal, 'Poin')) ? 'Masuk Siang (Poin)' : 'Tepat Waktu'; + } + + if ($presensi->jam_pulang) { + if ($presensi->waktu_pulang_awal) { + $ketPulang = 'Pulang Awal'; + } else if ($presensi->waktu_pulang_akhir) { + $ketPulang = (str_contains($infoJadwal, 'Poin')) ? 'Pulang Cepat (Poin) - Lembur' : 'Lembur / Pulang Akhir'; + } else { + $ketPulang = (str_contains($infoJadwal, 'Poin')) ? 'Pulang Cepat (Poin)' : 'Tepat Waktu'; + } + } + } + + return response()->json([ + 'success' => true, + 'data' => [ + 'jadwal' => [ + 'nama_shift' => $infoJadwal, + 'jam_masuk' => $jamMasuk, + 'jam_pulang' => $jamPulang, + 'tanggal' => Carbon::parse($today)->translatedFormat('l, d F Y'), + ], + 'presensi' => [ + 'jam_masuk' => $presensi ? $presensi->jam_masuk : null, + 'jam_pulang' => $presensi ? $presensi->jam_pulang : null, + 'foto_masuk' => $presensi ? $presensi->foto_masuk : null, + 'status_validasi' => $presensi ? $presensi->id_validasi : null, + 'keterangan' => $presensi ? $ketMasuk : null, + 'keterangan_pulang' => $presensi ? $ketPulang : null, + ] + ] + ]); + } + + public function store(Request $request) + { + try { + $request->validate([ + 'latitude' => 'required|numeric', + 'longitude' => 'required|numeric', + 'foto' => 'required|image|max:2048', + 'status' => 'required|in:masuk,pulang', + 'keterangan_luar_radius' => 'nullable|string|max:500', + ]); + + $user = Auth::user(); + + if ($request->status == 'masuk') { + $data = $this->presensiService->absenMasuk($user, $request); + return ApiResponse::success($data, 'Absen masuk berhasil'); + } else { + $data = $this->presensiService->absenPulang($user, $request); + return ApiResponse::success($data, 'Absen pulang berhasil'); + } + + } catch (\Exception $e) { + $code = (int) $e->getCode(); + if ($code < 100 || $code > 599) + $code = 500; + return ApiResponse::error($e->getMessage(), $code); + } + } + + public function history(Request $request) + { + try { + $userId = Auth::id(); + $history = Presensi::where('id_user', $userId) + ->latest('tanggal') + ->paginate(10); + + $enriched = $history->getCollection()->map(function ($item) use ($userId) { + $jadwal = \App\Models\JadwalKerja::with('shift') + ->where('id_user', $userId) + ->where('tanggal', $item->tanggal) + ->first(); + + $namaShift = $jadwal?->shift?->nama_shift ?? '-'; + + $statusMasuk = '-'; + if ($item->jam_masuk) { + if ($item->waktu_terlambat) { + $statusMasuk = 'Terlambat'; + } elseif ($item->waktu_masuk_awal) { + $statusMasuk = 'Datang Awal'; + } else { + $statusMasuk = 'Tepat Waktu'; + } + } else { + if ($item->alasan_telat && str_contains($item->alasan_telat, 'Cuti')) { + $statusMasuk = 'Cuti'; + } elseif ($item->id_status == 3) { + $statusMasuk = 'Izin'; + } elseif ($item->id_status == 4) { + $statusMasuk = 'Sakit'; + } elseif ($item->id_status != null && $item->id_status != 1 && $item->id_status != 2) { + + $statusStatus = \DB::table('status_presensi')->where('id_status', $item->id_status)->value('nama_status'); + if ($statusStatus) { + $statusMasuk = $statusStatus; + } + } + } + + $statusPulang = null; + if ($item->jam_pulang) { + if ($item->waktu_pulang_awal) { + $statusPulang = 'Pulang Awal'; + } elseif ($item->waktu_pulang_akhir) { + $statusPulang = 'Lembur'; + } else { + $statusPulang = 'Tepat Waktu'; + } + } + + $totalJam = '-'; + if ($item->jam_masuk && $item->jam_pulang) { + try { + $masuk = Carbon::parse($item->tanggal . ' ' . $item->jam_masuk); + $pulang = Carbon::parse($item->tanggal . ' ' . $item->jam_pulang); + $diff = $masuk->diff($pulang); + $totalJam = $diff->h . 'j ' . $diff->i . 'm'; + } catch (\Exception $e) { + $totalJam = '-'; + } + } + + return [ + 'id_presensi' => $item->id_presensi, + 'tanggal' => Carbon::parse($item->tanggal)->translatedFormat('l, d F Y'), + 'tanggal_raw' => $item->tanggal, + 'jam_masuk' => $item->jam_masuk, + 'jam_pulang' => $item->jam_pulang, + 'shift' => $namaShift, + 'status_masuk' => $statusMasuk, + 'status_pulang' => $statusPulang, + 'total_jam' => $totalJam, + 'waktu_terlambat' => $item->waktu_terlambat, + 'keterangan_luar_radius' => $item->keterangan_luar_radius, + 'verifikasi_wajah' => (bool) $item->verifikasi_wajah, + 'status_validasi' => $item->id_validasi, + 'alasan_penolakan' => $item->alasan_penolakan, + ]; + }); + + $history->setCollection($enriched); + + return ApiResponse::success($history); + + } catch (\Exception $e) { + return ApiResponse::error($e->getMessage(), 500); + } + } + + public function resubmit(Request $request, $id) + { + try { + $request->validate([ + 'keterangan' => 'required|string|max:500', + ]); + + $presensi = Presensi::where('id_presensi', $id) + ->where('id_user', Auth::id()) + ->firstOrFail(); + + if ($presensi->id_validasi != 3) { + return ApiResponse::error('Hanya presensi yang ditolak yang bisa diajukan ulang.', 422); + } + + $presensi->update([ + 'id_validasi' => 2, + 'alasan_penolakan' => null, + 'keterangan_luar_radius' => $request->keterangan, + ]); + + return ApiResponse::success(null, 'Presensi berhasil diajukan ulang. Menunggu validasi admin.'); + + } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { + return ApiResponse::error('Data presensi tidak ditemukan.', 404); + } catch (\Exception $e) { + return ApiResponse::error($e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/ProfileController.php b/app/Http/Controllers/Api/ProfileController.php new file mode 100644 index 00000000..5546c51c --- /dev/null +++ b/app/Http/Controllers/Api/ProfileController.php @@ -0,0 +1,75 @@ +validate([ + 'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048', + 'no_telp' => 'nullable|string|max:20', + 'alamat' => 'nullable|string|max:500', + ]); + + if ($request->has('no_telp')) { + $user->no_telp = $request->no_telp; + } + + if ($request->has('alamat')) { + $user->alamat = $request->alamat; + } + + if ($request->hasFile('foto')) { + if ($user->foto && Storage::disk('public')->exists($user->foto)) { + Storage::disk('public')->delete($user->foto); + } + + $path = $request->file('foto')->store('uploads/profil', 'public'); + $user->foto = $path; + } + + $user->save(); + + return ApiResponse::success($user->load(['divisi', 'jabatan', 'kantor']), 'Profil berhasil diperbarui.'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal update profil: ' . $e->getMessage(), 500); + } + } + + public function password(Request $request) + { + try { + $request->validate([ + 'current_password' => 'required', + 'new_password' => 'required|min:8|confirmed', + ]); + + $user = Auth::user(); + + if (!Hash::check($request->current_password, $user->password)) { + return ApiResponse::error('Password lama salah.', 400); + } + + $user->password = Hash::make($request->new_password); + $user->save(); + + return ApiResponse::success(null, 'Kata sandi berhasil diubah.'); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal ubah kata sandi: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/ScheduleController.php b/app/Http/Controllers/Api/ScheduleController.php new file mode 100644 index 00000000..3b140e6d --- /dev/null +++ b/app/Http/Controllers/Api/ScheduleController.php @@ -0,0 +1,255 @@ +toDateString(); + + $jadwal = JadwalKerja::with(['shift', 'user.kantor']) + ->where('id_user', $user->id) + ->where('tanggal', $today) + ->first(); + + if (!$jadwal) { + return ApiResponse::success(null, 'Tidak ada jadwal hari ini'); + } + + $shift = $jadwal->shift; + $jamMasuk = $shift->jam_mulai ?? '00:00:00'; + $jamPulang = $shift->jam_selesai ?? '00:00:00'; + $namaShift = $shift->nama_shift ?? 'Unknown Shift'; + + $statusJadwal = 'Normal'; + $note = null; + + $poinList = PenggunaanPoin::where('id_user', $user->id) + ->whereDate('tanggal_penggunaan', $today) + ->where('id_status', 2) + ->get(); + + foreach ($poinList as $poin) { + + if ($poin->id_pengurangan == 4 && $poin->jam_masuk_custom) { + $jamMasuk = substr($poin->jam_masuk_custom, 0, 5) . ':00'; + $statusJadwal = 'Masuk Siang (Poin)'; + $note = 'Jadwal disesuaikan poin'; + } + + if ($poin->id_pengurangan == 5 && $poin->jam_pulang_custom) { + $jamPulang = substr($poin->jam_pulang_custom, 0, 5) . ':00'; + + if ($statusJadwal != 'Normal') { + $statusJadwal = 'Full Custom (Poin)'; + } else { + $statusJadwal = 'Pulang Cepat (Poin)'; + } + } + } + + return ApiResponse::success([ + 'hari' => Carbon::parse($today, 'Asia/Jakarta')->translatedFormat('l, d F Y'), + 'shift' => $namaShift, + 'jam_masuk' => $jamMasuk, + 'jam_pulang' => $jamPulang, + 'status_jadwal' => $statusJadwal, + 'note' => $note, + 'kantor' => $jadwal->user->kantor->nama_kantor ?? '-', + 'lokasi_kantor' => [ + 'lat' => $jadwal->user->kantor->latitude ?? 0, + 'lon' => $jadwal->user->kantor->longitude ?? 0, + ], + ], 'Jadwal hari ini ditemukan'); + + } catch (\Exception $e) { + return ApiResponse::error('Terjadi kesalahan: ' . $e->getMessage(), 500); + } + } + + public function checkScheduleByDate(Request $request) + { + $request->validate([ + 'tanggal' => 'required|date_format:Y-m-d', + ]); + + try { + $user = Auth::user(); + $tanggal = $request->tanggal; + + $jadwal = JadwalKerja::with(['shift']) + ->where('id_user', $user->id) + ->where('tanggal', $tanggal) + ->first(); + + if (!$jadwal) { + return ApiResponse::error('Tidak ada jadwal kerja pada tanggal ini', 404); + } + + $shift = $jadwal->shift; + $jamMasuk = $shift->jam_mulai ?? '00:00:00'; + $jamPulang = $shift->jam_selesai ?? '00:00:00'; + + $statusJadwal = 'Normal'; + $note = null; + + $poinList = PenggunaanPoin::where('id_user', $user->id) + ->whereDate('tanggal_penggunaan', $tanggal) + ->where('id_status', 2) + ->get(); + + foreach ($poinList as $poin) { + if ($poin->id_pengurangan == 4 && $poin->jam_masuk_custom) { + $jamMasuk = substr($poin->jam_masuk_custom, 0, 5) . ':00'; + $statusJadwal = 'Masuk Siang (Poin)'; + } + + if ($poin->id_pengurangan == 5 && $poin->jam_pulang_custom) { + $jamPulang = substr($poin->jam_pulang_custom, 0, 5) . ':00'; + $statusJadwal = ($statusJadwal == 'Masuk Siang (Poin)') ? 'Full Custom' : 'Pulang Cepat (Poin)'; + } + } + + return ApiResponse::success([ + 'tanggal' => Carbon::parse($tanggal, 'Asia/Jakarta')->translatedFormat('l, d F Y'), + 'jam_masuk' => $jamMasuk, + 'jam_pulang' => $jamPulang, + + 'jam_mulai' => $jamMasuk, + 'jam_selesai' => $jamPulang, + + 'status_jadwal' => $statusJadwal, + 'note' => $note, + ], 'Jadwal ditemukan'); + + } catch (\Exception $e) { + return ApiResponse::error('Terjadi kesalahan: ' . $e->getMessage(), 500); + } + } + + public function getMonthlySchedule(Request $request) + { + + $month = $request->month ?? Carbon::now()->month; + $year = $request->year ?? Carbon::now()->year; + + try { + $user = Auth::user(); + + $startDate = Carbon::createFromDate($year, $month, 1)->startOfMonth(); + $endDate = Carbon::createFromDate($year, $month, 1)->endOfMonth(); + + $jadwals = JadwalKerja::with(['shift']) + ->where('id_user', $user->id) + ->whereBetween('tanggal', [$startDate->toDateString(), $endDate->toDateString()]) + ->get() + ->keyBy('tanggal'); + + $poinOverrides = PenggunaanPoin::where('id_user', $user->id) + ->whereBetween('tanggal_penggunaan', [$startDate->toDateString(), $endDate->toDateString()]) + ->where('id_status', 2) + ->get() + ->groupBy('tanggal_penggunaan'); + + $presensis = Presensi::where('id_user', $user->id) + ->whereBetween('tanggal', [$startDate->toDateString(), $endDate->toDateString()]) + ->get() + ->keyBy('tanggal'); + + $hariLiburs = \App\Models\HariLibur::whereBetween('tanggal', [$startDate->toDateString(), $endDate->toDateString()]) + ->where(function($query) use ($user) { + $query->whereNull('id_kantor') + ->orWhere('id_kantor', $user->id_kantor); + }) + ->get() + ->keyBy(function ($item) { + return Carbon::parse($item->tanggal)->toDateString(); + }); + + $results = []; + $period = CarbonPeriod::create($startDate, $endDate); + + foreach ($period as $date) { + $dateStr = $date->toDateString(); + $jadwalItem = $jadwals[$dateStr] ?? null; + $presensiItem = $presensis[$dateStr] ?? null; + $hariLiburItem = $hariLiburs[$dateStr] ?? null; + + $dataHari = [ + 'tanggal' => $dateStr, + 'hari' => $date->translatedFormat('l'), + 'is_hari_kerja' => false, + 'is_hari_libur' => $hariLiburItem ? true : false, + 'keterangan_libur' => $hariLiburItem ? $hariLiburItem->keterangan : null, + 'shift_nama' => $hariLiburItem ? $hariLiburItem->keterangan : 'Libur / Off', + 'jam_masuk' => '-', + 'jam_pulang' => '-', + 'status_poin' => null, + 'status_presensi' => $presensiItem ? 'Hadir' : 'Belum Absen', + 'warna_kalender' => $hariLiburItem ? '#EF4444' : '#E0E0E0' + ]; + + if ($jadwalItem) { + $dataHari['is_hari_kerja'] = true; + + $shift = $jadwalItem->shift; + $dataHari['shift_nama'] = $shift->nama_shift ?? 'Unknown Shift'; + $dataHari['jam_masuk'] = $shift->jam_mulai ?? '00:00:00'; + $dataHari['jam_pulang'] = $shift->jam_selesai ?? '00:00:00'; + + $dataHari['warna_kalender'] = '#3B82F6'; + + $poinFound = null; + + foreach ($poinOverrides as $keyDate => $poins) { + if (Carbon::parse($keyDate)->toDateString() == $dateStr) { + $poinFound = $poins; + break; + } + } + + if ($poinFound) { + foreach ($poinFound as $poin) { + if ($poin->id_pengurangan == 4 && $poin->jam_masuk_custom) { + $dataHari['jam_masuk'] = substr($poin->jam_masuk_custom, 0, 5) . ':00'; + $dataHari['status_poin'] = 'Masuk Siang'; + $dataHari['warna_kalender'] = '#F59E0B'; + } + if ($poin->id_pengurangan == 5 && $poin->jam_pulang_custom) { + $dataHari['jam_pulang'] = substr($poin->jam_pulang_custom, 0, 5) . ':00'; + $dataHari['status_poin'] = $dataHari['status_poin'] ? 'Full Custom' : 'Pulang Cepat'; + $dataHari['warna_kalender'] = '#F59E0B'; + } + } + } + + if ($presensiItem) { + $dataHari['warna_kalender'] = '#10B981'; + } + } + + $results[] = $dataHari; + } + + return ApiResponse::success($results, 'Data kalender berhasil dimuat'); + + } catch (\Exception $e) { + return ApiResponse::error('Terjadi kesalahan: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/SignatureApiController.php b/app/Http/Controllers/Api/SignatureApiController.php new file mode 100644 index 00000000..b6afd220 --- /dev/null +++ b/app/Http/Controllers/Api/SignatureApiController.php @@ -0,0 +1,85 @@ +active() + ->first(); + + if (!$ttd) { + return ApiResponse::notFound('Belum ada tanda tangan.'); + } + + return ApiResponse::success([ + 'id_tanda_tangan' => $ttd->id_tanda_tangan, + 'file_ttd' => asset('storage/' . $ttd->file_ttd), + 'is_active' => $ttd->is_active, + 'created_at' => $ttd->created_at, + ]); + } + + public function store(StoreSignatureRequest $request) + { + try { + $base64 = $request->signature_data; + + if (str_contains($base64, ',')) { + $base64 = explode(',', $base64)[1]; + } + + $imageData = base64_decode($base64); + if ($imageData === false) { + return ApiResponse::error('Data tanda tangan tidak valid.', 400); + } + + $fileName = 'uploads/ttd/' . Auth::id() . '_' . time() . '.png'; + Storage::disk('public')->put($fileName, $imageData); + + TandaTangan::where('id_user', Auth::id())->update(['is_active' => false]); + + $ttd = TandaTangan::create([ + 'id_user' => Auth::id(), + 'file_ttd' => $fileName, + 'is_active' => true, + ]); + + return ApiResponse::success([ + 'id_tanda_tangan' => $ttd->id_tanda_tangan, + 'file_ttd' => asset('storage/' . $ttd->file_ttd), + ], 'Tanda tangan berhasil disimpan.', 201); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal menyimpan tanda tangan: ' . $e->getMessage(), 500); + } + } + + public function destroy($id) + { + $ttd = TandaTangan::where('id_user', Auth::id()) + ->where('id_tanda_tangan', $id) + ->first(); + + if (!$ttd) { + return ApiResponse::notFound('Tanda tangan tidak ditemukan.'); + } + + if ($ttd->file_ttd && Storage::disk('public')->exists($ttd->file_ttd)) { + Storage::disk('public')->delete($ttd->file_ttd); + } + + $ttd->delete(); + + return ApiResponse::success(null, 'Tanda tangan berhasil dihapus.'); + } +} diff --git a/app/Http/Controllers/Api/SubmissionController.php b/app/Http/Controllers/Api/SubmissionController.php new file mode 100644 index 00000000..9f965f9a --- /dev/null +++ b/app/Http/Controllers/Api/SubmissionController.php @@ -0,0 +1,213 @@ +id_jenis_izin); + if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { + $tanggalMulai = Carbon::parse($request->tanggal_mulai); + $minDate = Carbon::now()->addDays(7)->startOfDay(); + + if ($tanggalMulai->lt($minDate)) { + return ApiResponse::error('Pengajuan Cuti minimal H-7!', 400); + } + } + + DB::beginTransaction(); + + $path = null; + if ($request->hasFile('bukti_file')) { + $path = $request->file('bukti_file')->store('uploads/izin', 'public'); + } + + $submission = PengajuanIzin::create([ + 'id_user' => $user->id, + 'id_jenis_izin' => $request->id_jenis_izin, + 'tanggal_mulai' => $request->tanggal_mulai, + 'tanggal_selesai' => $request->tanggal_selesai, + 'alasan' => $request->alasan, + 'bukti_file' => $path, + 'id_status' => 1 + ]); + + $ttdAktif = TandaTangan::where('id_user', $user->id)->active()->first(); + + $namaJenisIzin = $jenisIzin->nama_izin ?? 'Izin'; + $tglMulai = Carbon::parse($request->tanggal_mulai)->translatedFormat('d F Y'); + $tglSelesai = Carbon::parse($request->tanggal_selesai)->translatedFormat('d F Y'); + + $isiSurat = "Dengan hormat,\n\n" + . "Saya yang bertanda tangan di bawah ini:\n" + . "Nama: {$user->nama_lengkap}\n" + . "NIK: {$user->nik}\n" + . "Jabatan: " . ($user->jabatan->nama_jabatan ?? '-') . "\n" + . "Divisi: " . ($user->divisi->nama_divisi ?? '-') . "\n\n" + . "Dengan ini mengajukan {$namaJenisIzin} mulai tanggal {$tglMulai} sampai dengan {$tglSelesai}.\n\n" + . "Alasan: {$request->alasan}\n\n" + . "Demikian surat ini saya buat dengan sebenar-benarnya. " + . "Atas perhatian dan persetujuannya, saya ucapkan terima kasih."; + + $suratIzin = null; + if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { + $suratIzin = SuratIzin::create([ + 'id_izin' => $submission->id_izin, + 'id_user' => $user->id, + 'id_ttd_pengaju' => $ttdAktif?->id_tanda_tangan, + 'isi_surat' => $isiSurat, + 'status_surat' => 'menunggu_manajer', + ]); + } + + DB::commit(); + + $responseData = $submission->toArray(); + if ($suratIzin) { + $responseData['surat_izin'] = [ + 'id_surat' => $suratIzin->id_surat, + 'nomor_surat' => $suratIzin->nomor_surat, + 'status_surat' => $suratIzin->status_surat, + 'has_ttd' => $ttdAktif !== null, + ]; + } else { + $responseData['surat_izin'] = null; + } + + $message = 'Pengajuan & surat izin berhasil dibuat.'; + if (!$ttdAktif) { + $message .= ' (TTD belum tersedia, silakan buat di menu Profil)'; + } + + return ApiResponse::success($responseData, $message, 201); + + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::error('Gagal mengirim pengajuan: ' . $e->getMessage(), 500); + } + } + + /** + * @OA\Put( + * path="/api/submission/{id}", + * tags={"Submission"}, + * summary="Update Submission", + * security={{"bearerAuth":{}}}, + * @OA\Parameter( + * name="id", + * in="path", + * required=true, + * @OA\Schema(type="integer") + * ), + * @OA\RequestBody( + * required=true, + * @OA\MediaType( + * mediaType="multipart/form-data", + * @OA\Schema( + * required={"id_jenis_izin", "tanggal_mulai", "tanggal_selesai", "alasan"}, + * @OA\Property(property="id_jenis_izin", type="integer", example=1), + * @OA\Property(property="tanggal_mulai", type="string", format="date"), + * @OA\Property(property="tanggal_selesai", type="string", format="date"), + * @OA\Property(property="alasan", type="string"), + * @OA\Property(property="bukti_file", type="string", format="binary") + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="Submission updated", + * @OA\JsonContent( + * @OA\Property(property="success", type="boolean", example=true), + * @OA\Property(property="message", type="string") + * ) + * ) + * ) + */ + public function update(\App\Http\Requests\UpdatePengajuanIzinRequest $request, $id) + { + try { + $submission = PengajuanIzin::where('id_user', Auth::id()) + ->where('id_izin', $id) + ->where('id_status', 1) + ->first(); + + if (!$submission) { + return ApiResponse::error('Pengajuan tidak ditemukan atau sudah diproses.', 404); + } + + DB::beginTransaction(); + + $data = $request->validated(); + + if ($request->hasFile('bukti_file')) { + + if ($submission->bukti_file && \Illuminate\Support\Facades\Storage::disk('public')->exists($submission->bukti_file)) { + \Illuminate\Support\Facades\Storage::disk('public')->delete($submission->bukti_file); + } + $data['bukti_file'] = $request->file('bukti_file')->store('uploads/izin', 'public'); + } + + $submission->update($data); + + DB::commit(); + + return ApiResponse::success(null, 'Pengajuan berhasil diperbarui.'); + + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::error('Gagal memperbarui pengajuan: ' . $e->getMessage(), 500); + } + } + + public function history(Request $request) + { + try { + $query = PengajuanIzin::with(['jenisIzin', 'statusPengajuan']) + ->where('id_user', Auth::id()); + + if ($request->has('status')) { + $statusParam = $request->get('status'); + + $statusId = match ($statusParam) { + 'pending' => 1, + 'approved' => 2, + 'rejected' => 3, + default => null + }; + + if ($statusId) { + $query->where('id_status', $statusId); + } + } + + $history = $query->orderBy('created_at', 'desc')->paginate(10); + + return ApiResponse::success($history); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat riwayat: ' . $e->getMessage(), 500); + } + } +} diff --git a/app/Http/Controllers/Api/SuratIzinApiController.php b/app/Http/Controllers/Api/SuratIzinApiController.php new file mode 100644 index 00000000..6f75cb12 --- /dev/null +++ b/app/Http/Controllers/Api/SuratIzinApiController.php @@ -0,0 +1,157 @@ +where('id_user', Auth::id()) + ->orderBy('created_at', 'desc') + ->paginate(10); + + $data = $surat->through(function ($item) { + return $this->formatSurat($item); + }); + + return ApiResponse::success($data); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat daftar surat: ' . $e->getMessage(), 500); + } + } + + public function store(StoreSuratIzinRequest $request) + { + try { + $user = Auth::user(); + + $pengajuan = PengajuanIzin::with('jenisIzin') + ->where('id_izin', $request->id_izin) + ->where('id_user', $user->id) + ->first(); + + if (!$pengajuan) { + return ApiResponse::notFound('Pengajuan izin tidak ditemukan.'); + } + + $existing = SuratIzin::where('id_izin', $request->id_izin)->first(); + if ($existing) { + return ApiResponse::error('Surat izin untuk pengajuan ini sudah ada.', 400); + } + + $ttd = TandaTangan::where('id_user', $user->id)->active()->first(); + if (!$ttd) { + return ApiResponse::error('Anda belum memiliki tanda tangan digital. Silakan buat terlebih dahulu.', 400); + } + + DB::beginTransaction(); + + $surat = SuratIzin::create([ + 'id_izin' => $pengajuan->id_izin, + 'id_user' => $user->id, + 'isi_surat' => $request->isi_surat, + 'id_ttd_pengaju' => $ttd->id_tanda_tangan, + 'status_surat' => 'menunggu_manajer', + ]); + + DB::commit(); + + $surat->load(['tandaTanganPengaju', 'approvals']); + + return ApiResponse::success( + $this->formatSurat($surat), + 'Surat izin berhasil dibuat.', + 201 + ); + + } catch (\Exception $e) { + DB::rollBack(); + return ApiResponse::error('Gagal membuat surat izin: ' . $e->getMessage(), 500); + } + } + + public function show($id) + { + try { + $surat = SuratIzin::with([ + 'pengajuanIzin.jenisIzin', + 'user.jabatan', + 'user.divisi', + 'tandaTanganPengaju', + 'approvals.approver.jabatan', + 'approvals.tandaTanganApprover', + ]) + ->where('id_user', Auth::id()) + ->where('id_surat', $id) + ->first(); + + if (!$surat) { + return ApiResponse::notFound('Surat izin tidak ditemukan.'); + } + + return ApiResponse::success($this->formatSurat($surat, true)); + + } catch (\Exception $e) { + return ApiResponse::error('Gagal memuat detail surat: ' . $e->getMessage(), 500); + } + } + + private function formatSurat(SuratIzin $surat, bool $detail = false): array + { + $data = [ + 'id_surat' => $surat->id_surat, + 'nomor_surat' => $surat->nomor_surat, + 'status_surat' => $surat->status_surat, + 'jenis_izin' => $surat->pengajuanIzin?->jenisIzin?->nama_izin ?? 'N/A', + 'created_at' => $surat->created_at?->toISOString(), + 'ttd_pengaju' => $surat->tandaTanganPengaju ? asset('storage/' . $surat->tandaTanganPengaju->file_ttd) : null, + 'approvals' => $surat->approvals->map(function ($approval) { + return [ + 'tahap' => $approval->tahap, + 'tahap_label' => $approval->tahap == 1 ? 'Manajer' : 'HRD', + 'status' => $approval->status, + 'approver_nama' => $approval->approver?->nama_lengkap ?? 'N/A', + 'approver_jabatan' => $approval->approver?->jabatan?->nama_jabatan ?? 'N/A', + 'ttd_approver' => $approval->tandaTanganApprover ? asset('storage/' . $approval->tandaTanganApprover->file_ttd) : null, + 'catatan' => $approval->catatan, + 'created_at' => $approval->created_at?->toISOString(), + ]; + })->toArray(), + ]; + + if ($detail) { + $data['isi_surat'] = $surat->isi_surat; + $data['pengaju'] = [ + 'nama' => $surat->user?->nama_lengkap, + 'nik' => $surat->user?->nik, + 'jabatan' => $surat->user?->jabatan?->nama_jabatan ?? 'N/A', + 'divisi' => $surat->user?->divisi?->nama_divisi ?? 'N/A', + ]; + $data['pengajuan'] = [ + 'id_izin' => $surat->pengajuanIzin?->id_izin, + 'tanggal_mulai' => $surat->pengajuanIzin?->tanggal_mulai, + 'tanggal_selesai' => $surat->pengajuanIzin?->tanggal_selesai, + 'alasan' => $surat->pengajuanIzin?->alasan, + ]; + } + + return $data; + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php new file mode 100644 index 00000000..844313da --- /dev/null +++ b/app/Http/Controllers/AuthController.php @@ -0,0 +1,72 @@ +route('dashboard'); + } + return view('auth.login'); + } + + public function login(Request $request) + { + $credentials = $request->validate([ + 'email' => 'required|email', + 'password' => 'required', + ]); + + if (Auth::attempt($credentials)) { + $user = Auth::user(); + if ($user->status_aktif == 0) { + Auth::logout(); + if ($request->ajax() || $request->wantsJson()) { + return response()->json([ + 'success' => false, + 'message' => 'Akun Anda telah dinonaktifkan.' + ], 403); + } + return back()->with('error', 'Akun Anda telah dinonaktifkan.'); + } + + $request->session()->regenerate(); + + if ($request->ajax() || $request->wantsJson()) { + return response()->json([ + 'success' => true, + 'message' => 'Login berhasil!', + 'redirect' => route('dashboard') + ]); + } + + return redirect()->route('dashboard'); + } + + if ($request->ajax() || $request->wantsJson()) { + return response()->json([ + 'success' => false, + 'message' => 'Email atau Password yang Anda masukkan salah.' + ], 401); + } + + return back() + ->withInput($request->only('email')) + ->with('error', 'Email atau Password yang Anda masukkan salah.') + ->withErrors(['email' => 'Email atau Password yang Anda masukkan salah.']); + } + + public function logout(Request $request) + { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + return redirect('/login'); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 00000000..5099091e --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,19 @@ +input('search'); + + $query = User::with(['jabatan', 'divisi'])->whereHas('roles', function($q) { + $q->where('nama_role', '!=', 'super_admin'); + }); + + if ($search) { + $query->where('nama_lengkap', 'like', '%' . $search . '%') + ->orWhere('nik', 'like', '%' . $search . '%'); + } + + $pegawai = $query->orderBy('nama_lengkap', 'asc')->paginate(15); + + return view('cuti.index', compact('pegawai', 'search')); + } + + public function update(Request $request, $id) + { + $request->validate([ + 'sisa_cuti' => 'required|integer|min:0', + 'alasan' => 'nullable|string|max:255' + ]); + + $user = User::findOrFail($id); + + $sisaLama = $user->sisa_cuti; + $user->update([ + 'sisa_cuti' => $request->sisa_cuti + ]); + + return redirect()->back()->with('success', 'Sisa cuti ' . $user->nama_lengkap . ' berhasil diperbarui.'); + } + + public function resetMassal(Request $request) + { + $request->validate([ + 'jumlah_hari' => 'required|integer|min:0' + ]); + + User::query()->update([ + 'sisa_cuti' => $request->jumlah_hari + ]); + + return redirect()->back()->with('success', 'Sisa cuti semua pegawai berhasil di-reset menjadi ' . $request->jumlah_hari . ' hari.'); + } +} diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php new file mode 100644 index 00000000..8134aa54 --- /dev/null +++ b/app/Http/Controllers/DashboardController.php @@ -0,0 +1,77 @@ +format('Y-m-d'); + $totalPegawai = User::where('status_aktif', 1)->count(); + + $rekapRaw = Presensi::whereDate('tanggal', $hariIni) + ->selectRaw(" + SUM(CASE WHEN id_status IN (1,2) THEN 1 ELSE 0 END) as hadir, + SUM(CASE WHEN id_status = 2 THEN 1 ELSE 0 END) as terlambat, + SUM(CASE WHEN id_status = 3 THEN 1 ELSE 0 END) as izin, + SUM(CASE WHEN id_status = 4 THEN 1 ELSE 0 END) as sakit, + SUM(CASE WHEN id_status = 5 THEN 1 ELSE 0 END) as alpha + ")->first(); + + $rekap = [ + 'hadir' => $rekapRaw->hadir ?? 0, + 'izin' => $rekapRaw->izin ?? 0, + 'sakit' => $rekapRaw->sakit ?? 0, + 'alpha' => $rekapRaw->alpha ?? 0, + 'terlambat' => $rekapRaw->terlambat ?? 0 + ]; + + $recentActivities = Presensi::with(['user.jabatan']) + ->whereDate('tanggal', $hariIni) + ->whereNotNull('jam_masuk') + ->orderBy('jam_masuk', 'desc') + ->take(5) + ->get(); + + $pendingIzin = \App\Models\PengajuanIzin::with('user')->where('id_status', 1)->orderBy('created_at', 'desc')->take(5)->get(); + $pendingSuratIzin = \App\Models\SuratIzin::with('user')->whereIn('status_surat', ['menunggu_manajer', 'menunggu_hrd'])->orderBy('created_at', 'desc')->take(5)->get(); + $pendingLembur = \App\Models\Lembur::with('user')->where('id_status', 1)->orderBy('created_at', 'desc')->take(5)->get(); + $pendingPoin = \App\Models\PenggunaanPoin::with('user')->where('id_status', 1)->orderBy('created_at', 'desc')->take(5)->get(); + $pendingPresensi = Presensi::with('user')->where('id_validasi', 2)->orderBy('created_at', 'desc')->take(5)->get(); + + $pengajuanPending = [ + 'izin' => $pendingIzin, + 'surat_izin' => $pendingSuratIzin, + 'lembur' => $pendingLembur, + 'poin' => $pendingPoin, + 'presensi' => $pendingPresensi + ]; + + $startDate = Carbon::today()->subDays(6)->format('Y-m-d'); + $endDate = $hariIni; + + $chartRaw = Presensi::whereBetween('tanggal', [$startDate, $endDate]) + ->whereIn('id_status', [1, 2]) + ->selectRaw("DATE(tanggal) as tgl, COUNT(*) as total") + ->groupBy('tgl') + ->orderBy('tgl') + ->pluck('total', 'tgl'); + + $chartLabels = []; + $chartData = []; + + for ($i = 6; $i >= 0; $i--) { + $date = Carbon::today()->subDays($i); + $chartLabels[] = $date->format('d/m'); + $chartData[] = $chartRaw->get($date->format('Y-m-d'), 0); + } + + return view('dashboard.index', compact('rekap', 'recentActivities', 'pengajuanPending', 'chartLabels', 'chartData', 'totalPegawai')); + } +} diff --git a/app/Http/Controllers/DivisiController.php b/app/Http/Controllers/DivisiController.php new file mode 100644 index 00000000..b2ebec3b --- /dev/null +++ b/app/Http/Controllers/DivisiController.php @@ -0,0 +1,46 @@ +paginate(10); + return view('divisi.index', compact('divisi')); + } + + public function store(StoreDivisiRequest $request) + { + + Divisi::create($request->validated()); + + return redirect()->back()->with('success', 'Divisi berhasil ditambahkan.'); + } + + public function update(UpdateDivisiRequest $request, $id) + { + $divisi = Divisi::findOrFail($id); + + $divisi->update($request->validated()); + + return redirect()->back()->with('success', 'Divisi berhasil diperbarui.'); + } + + public function destroy($id) + { + try { + Divisi::findOrFail($id)->delete(); + return redirect()->back()->with('success', 'Divisi berhasil dihapus.'); + } catch (\Illuminate\Database\QueryException $e) { + if ($e->getCode() === '23000') { + return redirect()->back()->with('error', 'Divisi tidak bisa dihapus karena masih digunakan oleh data pegawai.'); + } + throw $e; + } + } +} diff --git a/app/Http/Controllers/FaceApprovalController.php b/app/Http/Controllers/FaceApprovalController.php new file mode 100644 index 00000000..b906a029 --- /dev/null +++ b/app/Http/Controllers/FaceApprovalController.php @@ -0,0 +1,83 @@ +where('is_verified', 0); + }) + ->with(['jabatan', 'divisi']) + ->get(); + + $users->each(function ($user) { + $storagePath = "face_datasets/{$user->id}"; + $poses = ['depan', 'kanan', 'kiri', 'bawah']; + $fotoList = []; + + foreach ($poses as $pose) { + $extensions = ['jpg', 'jpeg', 'png']; + foreach ($extensions as $ext) { + $filePath = "{$storagePath}/user_{$user->id}_{$pose}.{$ext}"; + if (Storage::disk('local')->exists($filePath)) { + $fotoList[$pose] = $filePath; + break; + } + } + } + + $user->face_photos = $fotoList; + }); + + return view('face-approval.index', compact('users')); + } + + public function approve($id) + { + $user = User::findOrFail($id); + + if ($user->dataWajah) { + $user->dataWajah->update(['is_verified' => 1]); + } + + return redirect()->back()->with('success', 'Wajah karyawan berhasil diverifikasi. Karyawan kini bisa melakukan presensi.'); + } + + public function reject($id) + { + $user = User::findOrFail($id); + + if ($user->dataWajah) { + $user->dataWajah->delete(); + } + + $user->update(['is_face_registered' => 0]); + + $storagePath = "face_datasets/{$user->id}"; + if (Storage::disk('local')->exists($storagePath)) { + Storage::disk('local')->deleteDirectory($storagePath); + } + + return redirect()->back()->with('success', 'Wajah ditolak. Karyawan diminta melakukan registrasi ulang.'); + } + + public function showPhoto($userId, $pose) + { + $extensions = ['jpg', 'jpeg', 'png']; + foreach ($extensions as $ext) { + $filePath = "face_datasets/{$userId}/user_{$userId}_{$pose}.{$ext}"; + if (Storage::disk('local')->exists($filePath)) { + return response()->file(Storage::disk('local')->path($filePath)); + } + } + + abort(404); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/HariLiburController.php b/app/Http/Controllers/HariLiburController.php new file mode 100644 index 00000000..c02bc163 --- /dev/null +++ b/app/Http/Controllers/HariLiburController.php @@ -0,0 +1,105 @@ +filled('search')) { + $query->where('keterangan', 'like', '%' . $request->search . '%') + ->orWhere('tanggal', 'like', '%' . $request->search . '%'); + } + + if ($request->filled('id_kantor')) { + $query->where('id_kantor', $request->id_kantor); + } + + $hariLiburs = $query->orderBy('tanggal', 'desc')->paginate(10)->withQueryString(); + $kantors = \App\Models\Kantor::all(); + + return view('hari_libur.index', compact('hariLiburs', 'kantors')); + } + + public function store(Request $request) + { + $request->validate([ + 'tanggal' => 'required|date', + 'keterangan' => 'required|string|max:255', + 'id_kantor' => 'nullable|exists:kantor,id_kantor', + ]); + + HariLibur::create($request->all()); + + return redirect()->route('hari-libur.index') + ->with('success', 'Hari libur berhasil ditambahkan.'); + } + + public function update(Request $request, $id) + { + $request->validate([ + 'tanggal' => 'required|date', + 'keterangan' => 'required|string|max:255', + 'id_kantor' => 'nullable|exists:kantor,id_kantor', + ]); + + $hariLibur = HariLibur::where('id', '=', $id)->first() ?? HariLibur::where('id_hari_libur', '=', $id)->firstOrFail(); + $hariLibur->update($request->all()); + + return redirect()->route('hari-libur.index') + ->with('success', 'Hari libur berhasil diperbarui.'); + } + + public function destroy($id) + { + $hariLibur = HariLibur::where('id', '=', $id)->first() ?? HariLibur::where('id_hari_libur', '=', $id)->firstOrFail(); + $hariLibur->delete(); + + return redirect()->route('hari-libur.index') + ->with('success', 'Hari libur berhasil dihapus.'); + } + + public function syncHolidays(Request $request) + { + $year = $request->get('year', date('Y')); + + try { + $response = \Illuminate\Support\Facades\Http::withoutVerifying()->get("https://api-hari-libur.vercel.app/api?year={$year}"); + + if ($response->successful()) { + $body = $response->json(); + $holidays = $body['data'] ?? []; + $count = 0; + + foreach ($holidays as $h) { + if (isset($h['date'])) { + $tanggal = $h['date']; + $keterangan = $h['description']; + + $exists = HariLibur::where('tanggal', '=', $tanggal)->exists(); + if (!$exists) { + HariLibur::create(['tanggal' => $tanggal, 'keterangan' => $keterangan]); + $count++; + } + } + } + + return redirect()->route('hari-libur.index') + ->with('success', "Berhasil sinkronisasi {$count} hari libur baru untuk tahun {$year}."); + } + + return redirect()->route('hari-libur.index') + ->with('error', 'Gagal mengambil data dari API Hari Libur.'); + + } catch (\Exception $e) { + return redirect()->route('hari-libur.index') + ->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + } + } +} diff --git a/app/Http/Controllers/JabatanController.php b/app/Http/Controllers/JabatanController.php new file mode 100644 index 00000000..5c391c9b --- /dev/null +++ b/app/Http/Controllers/JabatanController.php @@ -0,0 +1,42 @@ +paginate(10); + return view('jabatan.index', compact('jabatan')); + } + + public function store(StoreJabatanRequest $request) + { + Jabatan::create($request->validated()); + return redirect()->back()->with('success', 'Jabatan berhasil ditambahkan.'); + } + + public function update(UpdateJabatanRequest $request, $id) + { + $jabatan = Jabatan::findOrFail($id); + $jabatan->update($request->validated()); + return redirect()->back()->with('success', 'Jabatan berhasil diperbarui.'); + } + + public function destroy($id) + { + try { + Jabatan::findOrFail($id)->delete(); + return redirect()->back()->with('success', 'Jabatan berhasil dihapus.'); + } catch (\Illuminate\Database\QueryException $e) { + if ($e->getCode() === '23000') { + return redirect()->back()->with('error', 'Jabatan tidak bisa dihapus karena masih digunakan oleh data pegawai.'); + } + throw $e; + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/JadwalController.php b/app/Http/Controllers/JadwalController.php new file mode 100644 index 00000000..485d7fd5 --- /dev/null +++ b/app/Http/Controllers/JadwalController.php @@ -0,0 +1,352 @@ +map(function ($s) { + $s->color = $this->getShiftColor($s->nama_shift); + return $s; + }); + + $pegawai = User::where('status_aktif', 1)->orderBy('nama_lengkap', 'asc')->get(); + + return view('jadwal.index', compact('kantor', 'divisi', 'shifts', 'pegawai')); + } + + public function getEvents(Request $request) + { + $start = Carbon::parse($request->start)->format('Y-m-d'); + $end = Carbon::parse($request->end)->format('Y-m-d'); + + $query = JadwalKerja::with(['user.kantor', 'user.jabatan', 'shift']) + ->whereDate('tanggal', '>=', $start) + ->whereDate('tanggal', '<=', $end); + + if ($request->filled('filter_kantor') && $request->filter_kantor != "") { + $query->whereHas('user', function ($q) use ($request) { + $q->where('id_kantor', $request->filter_kantor); + }); + } + + if ($request->filled('filter_divisi') && $request->filter_divisi != "") { + $query->whereHas('user', function ($q) use ($request) { + $q->where('id_divisi', $request->filter_divisi); + }); + } + + $jadwals = $query->get(); + + $poinUsed = PenggunaanPoin::with('jenisPengurangan') + ->whereBetween('tanggal_penggunaan', [$start, $end]) + ->where('id_status', 2) + ->get() + ->groupBy(function ($item) { + return $item->id_user . '-' . $item->tanggal_penggunaan->format('Y-m-d'); + }); + + $events = []; + + foreach ($jadwals as $jadwalItem) { + $key = $jadwalItem->id_user . '-' . $jadwalItem->tanggal; + + $shiftName = $jadwalItem->shift?->nama_shift ?? 'Shift ?'; + $jamMulai = $jadwalItem->shift?->jam_mulai ?? '00:00:00'; + $jamSelesai = $jadwalItem->shift?->jam_selesai ?? '23:59:00'; + + $originalJamMulai = $jamMulai; + $originalJamSelesai = $jamSelesai; + + $color = $this->getShiftColor($shiftName); + + $title = ($jadwalItem->user?->nama_lengkap ?? 'Unknown') . "\n(" . $shiftName . ")"; + $description = "Jam: " . substr($jamMulai, 0, 5) . " - " . substr($jamSelesai, 0, 5); + $isPoin = false; + $poinInfo = []; + + if (isset($poinUsed[$key])) { + $isPoin = true; + $listPenggunaan = $poinUsed[$key]; + + $color = '#f6c23e'; + + foreach ($listPenggunaan as $penggunaan) { + $jenis = $penggunaan->jenisPengurangan?->nama_pengurangan ?? 'Unknown'; + if ($penggunaan->id_pengurangan == 4 && $penggunaan->jam_masuk_custom) { + $jamMulai = substr($penggunaan->jam_masuk_custom, 0, 5) . ':00'; + $description .= "\n\n⚠️ DATANG TELAT (Poin)\nJam Masuk Baru: $jamMulai"; + $poinInfo[] = "⚠️ Datang Terlambat (Menjadi $jamMulai)"; + } elseif ($penggunaan->id_pengurangan == 5 && $penggunaan->jam_pulang_custom) { + $jamSelesai = substr($penggunaan->jam_pulang_custom, 0, 5) . ':00'; + $description .= "\n\n⚠️ PULANG CEPAT (Poin)\nJam Pulang Baru: $jamSelesai"; + $poinInfo[] = "⚠️ Pulang Cepat (Menjadi $jamSelesai)"; + } else { + $description .= "\n[INFO] " . $jenis; + $poinInfo[] = $jenis; + } + } + } + + $events[] = [ + 'id' => $jadwalItem->id_jadwal, + 'title' => $title, + 'start' => $jadwalItem->tanggal, + 'backgroundColor' => $color, + 'borderColor' => $color, + 'extendedProps' => [ + 'description' => $description, + 'is_poin' => $isPoin, + 'is_holiday' => false, + 'poin_info' => $poinInfo, + 'nama_user' => $jadwalItem->user?->nama_lengkap ?? '-', + 'nama_shift' => $shiftName, + 'jam_mulai' => $jamMulai, + 'jam_selesai' => $jamSelesai, + 'original_jam_mulai' => $originalJamMulai, + 'original_jam_selesai' => $originalJamSelesai, + 'kantor' => $jadwalItem->user?->kantor?->nama_kantor ?? '-', + 'jabatan' => $jadwalItem->user?->jabatan?->nama_jabatan ?? '-', + 'id_shift' => $jadwalItem->id_shift, + 'id_user' => $jadwalItem->id_user, + ] + ]; + } + + $hariLibursQuery = \App\Models\HariLibur::whereBetween('tanggal', [$start, $end]); + if ($request->filled('filter_kantor') && $request->filter_kantor != "") { + $hariLibursQuery->where(function($q) use ($request) { + $q->whereNull('id_kantor')->orWhere('id_kantor', $request->filter_kantor); + }); + } + $hariLiburs = $hariLibursQuery->get(); + + foreach ($hariLiburs as $hl) { + $events[] = [ + 'id' => 'libur-' . $hl->id, + 'title' => '🔴 ' . $hl->keterangan, + 'start' => $hl->tanggal, + 'backgroundColor' => '#ef4444', + 'borderColor' => '#ef4444', + 'display' => 'block', + 'extendedProps' => [ + 'is_holiday' => true, + 'is_poin' => false, + 'keterangan' => $hl->keterangan, + 'nama_user' => $hl->keterangan, + 'nama_shift' => 'Hari Libur', + 'jam_mulai' => '00:00', + 'jam_selesai' => '00:00', + ] + ]; + } + + return response()->json($events); + } + + private function getShiftColor($namaShift) + { + if (stripos($namaShift, 'Pagi') !== false) + return '#10b981'; + if (stripos($namaShift, 'Siang') !== false) + return '#3b82f6'; + if (stripos($namaShift, 'Sore') !== false) + return '#f59e0b'; + if (stripos($namaShift, 'Malam') !== false) + return '#6b7280'; + return '#6366f1'; + } + + public function create() + { + $shifts = ShiftKerja::all(); + $kantor = Kantor::all(); + $divisi = Divisi::all(); + + $pegawai = User::where('status_aktif', 1) + ->with(['kantor', 'jabatan']) + ->orderBy('nama_lengkap', 'asc') + ->get(); + + return view('jadwal.create', compact('shifts', 'pegawai', 'kantor', 'divisi')); + } + + public function store(Request $request) + { + $request->validate([ + 'tanggal_mulai' => 'required|date', + 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', + 'id_shift' => 'required|exists:shift_kerja,id_shift', + 'user_ids' => 'required|array', + 'user_ids.*' => 'exists:users,id', + ]); + + $startDate = Carbon::parse($request->tanggal_mulai); + $endDate = Carbon::parse($request->tanggal_selesai); + $period = \Carbon\CarbonPeriod::create($startDate, $endDate); + + $shiftId = $request->id_shift; + $userIds = $request->user_ids; + + $count = 0; + $refundCount = 0; + $poinService = new PoinService(); + + $usersData = \App\Models\User::whereIn('id', $userIds)->get(['id', 'id_kantor'])->keyBy('id'); + + try { + DB::transaction(function () use ($period, $userIds, $shiftId, &$count, &$refundCount, $poinService, $usersData) { + foreach ($period as $date) { + $dateStr = $date->format('Y-m-d'); + $libursDate = \App\Models\HariLibur::where('tanggal', $dateStr)->get(); + + foreach ($userIds as $userId) { + $user = $usersData->get($userId); + $isLibur = $libursDate->contains(function($libur) use ($user) { + return is_null($libur->id_kantor) || ($user && $libur->id_kantor == $user->id_kantor); + }); + + if ($isLibur) { + continue; + } + $conflictPoin = PenggunaanPoin::where('id_user', $userId) + ->where('tanggal_penggunaan', $dateStr) + ->where('id_status', 2) + ->first(); + + if ($conflictPoin) { + $poinService->refundPoin($conflictPoin->id_penggunaan); + $refundCount++; + } + + JadwalKerja::updateOrCreate( + ['id_user' => $userId, 'tanggal' => $dateStr], + ['id_shift' => $shiftId] + ); + $count++; + } + } + }); + + $message = "Berhasil memproses $count jadwal kerja!"; + if ($refundCount > 0) { + $message .= " ⚠️ PERINGATAN: $refundCount penggunaan poin dibatalkan otomatis & saldo dikembalikan karena konflik jadwal."; + return redirect()->route('jadwal.index')->with('warning', $message); + } + + return redirect()->route('jadwal.index')->with('success', $message); + + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Terjadi kesalahan: ' . $e->getMessage()); + } + } + + public function update(Request $request, $id) + { + $request->validate([ + 'id_shift' => 'required|exists:shift_kerja,id_shift', + ]); + + $jadwal = JadwalKerja::findOrFail($id); + $warningMsg = null; + + try { + DB::transaction(function () use ($jadwal, $request, &$warningMsg) { + $conflictPoin = PenggunaanPoin::where('id_user', $jadwal->id_user) + ->where('tanggal_penggunaan', $jadwal->tanggal) + ->where('id_status', 2) + ->first(); + + if ($conflictPoin) { + $poinService = new PoinService(); + $poinService->refundPoin($conflictPoin->id_penggunaan); + $warningMsg = "Jadwal diperbarui, namun penggunaan poin user pada tanggal ini telah DIBATALKAN otomatis."; + } + + $jadwal->update(['id_shift' => $request->id_shift]); + }); + + return response()->json([ + 'success' => true, + 'message' => $warningMsg ?? 'Jadwal berhasil diperbarui' + ]); + + } catch (\Exception $e) { + return response()->json(['success' => false, 'message' => $e->getMessage()], 500); + } + } + + public function destroy($id) + { + $jadwal = JadwalKerja::findOrFail($id); + $jadwal->delete(); + + return response()->json(['success' => true, 'message' => 'Jadwal berhasil dihapus']); + } + + public function bulkDestroy(Request $request) + { + $request->validate([ + 'tanggal_mulai' => 'required|date', + 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', + 'user_ids' => 'required|array', + 'user_ids.*' => 'exists:users,id', + ]); + + try { + DB::transaction(function () use ($request) { + JadwalKerja::whereIn('id_user', $request->user_ids) + ->whereBetween('tanggal', [$request->tanggal_mulai, $request->tanggal_selesai]) + ->delete(); + }); + + return redirect()->back()->with('success', 'Jadwal kerja berhasil dihapus pada rentang tanggal tersebut.'); + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Terjadi kesalahan saat menghapus jadwal: ' . $e->getMessage()); + } + } + + public function checkConflicts(Request $request) + { + $userIds = $request->user_ids; + $startDate = $request->tanggal_mulai; + $endDate = $request->tanggal_selesai; + + if (empty($userIds) || !$startDate || !$endDate) { + return response()->json(['has_conflict' => false]); + } + + $conflicts = PenggunaanPoin::with('user') + ->whereIn('id_user', $userIds) + ->whereBetween('tanggal_penggunaan', [$startDate, $endDate]) + ->where('id_status', 2) + ->get(); + + if ($conflicts->count() > 0) { + $details = $conflicts->map(function ($item) { + return "- " . $item->user->nama_lengkap . " (" . date('d M Y', strtotime($item->tanggal_penggunaan)) . ")"; + })->join('
'); + + return response()->json([ + 'has_conflict' => true, + 'message' => "Ditemukan {$conflicts->count()} karyawan yang sedang menggunakan Poin (Cuti/Pulang Cepat) pada tanggal ini:

$details

Jika dilanjutkan, Penggunaan Poin akan DIBATALKAN otomatis dan saldo dikembalikan." + ]); + } + + return response()->json(['has_conflict' => false]); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/KantorController.php b/app/Http/Controllers/KantorController.php new file mode 100644 index 00000000..a60d0453 --- /dev/null +++ b/app/Http/Controllers/KantorController.php @@ -0,0 +1,42 @@ +paginate(10); + return view('kantor.index', compact('kantor')); + } + + public function store(StoreKantorRequest $request) + { + Kantor::create($request->validated()); + return redirect()->back()->with('success', 'Lokasi kantor berhasil ditambahkan.'); + } + + public function update(UpdateKantorRequest $request, $id) + { + $kantor = Kantor::findOrFail($id); + $kantor->update($request->validated()); + return redirect()->back()->with('success', 'Data kantor berhasil diperbarui.'); + } + + public function destroy($id) + { + try { + Kantor::findOrFail($id)->delete(); + return redirect()->back()->with('success', 'Data kantor berhasil dihapus.'); + } catch (\Illuminate\Database\QueryException $e) { + if ($e->getCode() === '23000') { + return redirect()->back()->with('error', 'Kantor tidak bisa dihapus karena masih digunakan oleh data pegawai.'); + } + throw $e; + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/LaporanController.php b/app/Http/Controllers/LaporanController.php new file mode 100644 index 00000000..eb3b6549 --- /dev/null +++ b/app/Http/Controllers/LaporanController.php @@ -0,0 +1,144 @@ +input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $divisiId = $request->input('id_divisi'); + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + $divisiList = \App\Models\Divisi::all(); + + return view('laporan.index', compact('rekap', 'bulan', 'tahun', 'divisiList', 'divisiId')); + } + + public function cuti(Request $request) + { + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $search = $request->input('search'); + $divisiId = $request->input('id_divisi'); + + $query = \App\Models\PengajuanIzin::with(['user.divisi', 'jenisIzin']) + ->whereYear('tanggal_mulai', $tahun) + ->whereMonth('tanggal_mulai', $bulan); + + if ($search) { + $query->whereHas('user', function($q) use ($search) { + $q->where('nama_lengkap', 'like', "%{$search}%") + ->orWhere('nik', 'like', "%{$search}%"); + }); + } + + if ($divisiId) { + $query->whereHas('user', function($q) use ($divisiId) { + $q->where('id_divisi', $divisiId); + }); + } + + $izinList = $query->orderBy('tanggal_mulai', 'desc')->paginate(15)->withQueryString(); + $divisiList = \App\Models\Divisi::all(); + + return view('laporan.izin', compact('izinList', 'bulan', 'tahun', 'search', 'divisiId', 'divisiList')); + } + + public function exportExcel(Request $request) + { + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $divisiId = $request->input('id_divisi'); + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + + $filename = "Laporan_Presensi_{$bulan}_{$tahun}.xlsx"; + + return Excel::download(new LaporanPresensiExport($rekap, $bulan, $tahun), $filename); + } + + public function exportPdf(Request $request) + { + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $divisiId = $request->input('id_divisi'); + + $rekap = $this->buildRekap($bulan, $tahun, $divisiId); + + $pdf = Pdf::loadView('laporan.pdf', compact('rekap', 'bulan', 'tahun')); + $pdf->setPaper('a4', 'landscape'); + + return $pdf->download("Laporan_Presensi_{$bulan}_{$tahun}.pdf"); + } + + private function buildRekap(int $bulan, int $tahun, $divisiId = null): array + { + + $idTepatWaktu = 1; + $idTerlambat = 2; + $idIzin = 3; + $idSakit = 4; + $idAlpha = 5; + + $pegawaiQuery = User::with(['jabatan', 'divisi']) + ->where('status_aktif', 1) + ->select('id', 'nama_lengkap', 'nik', 'id_jabatan', 'id_divisi') + ->orderBy('nama_lengkap', 'asc'); + + if ($divisiId) { + $pegawaiQuery->where('id_divisi', $divisiId); + } + + $pegawai = $pegawaiQuery->get(); + + $presensiStats = Presensi::whereMonth('tanggal', $bulan) + ->whereYear('tanggal', $tahun) + ->selectRaw(" + id_user, + SUM(CASE WHEN id_status IN (?, ?) THEN 1 ELSE 0 END) as hadir, + SUM(CASE WHEN id_status = ? THEN 1 ELSE 0 END) as terlambat, + SUM(CASE WHEN id_status = ? THEN 1 ELSE 0 END) as alpha, + SUM(CASE WHEN id_status = ? THEN 1 ELSE 0 END) as izin, + SUM(CASE WHEN id_status = ? THEN 1 ELSE 0 END) as sakit + ", [$idTepatWaktu, $idTerlambat, $idTerlambat, $idAlpha, $idIzin, $idSakit]) + ->groupBy('id_user') + ->get() + ->keyBy('id_user'); + + $poinStats = DB::table('poin_lembur') + ->whereMonth('tanggal', $bulan) + ->whereYear('tanggal', $tahun) + ->selectRaw('id_user, SUM(jumlah_poin) as total_poin') + ->groupBy('id_user') + ->pluck('total_poin', 'id_user'); + + $rekap = []; + + foreach ($pegawai as $p) { + $stats = $presensiStats->get($p->id); + + $rekap[] = [ + 'user' => $p, + 'hadir' => $stats->hadir ?? 0, + 'terlambat' => $stats->terlambat ?? 0, + 'alpha' => $stats->alpha ?? 0, + 'izin' => $stats->izin ?? 0, + 'sakit' => $stats->sakit ?? 0, + 'poin_lembur' => $poinStats->get($p->id, 0) + ]; + } + + return $rekap; + } +} diff --git a/app/Http/Controllers/LaporanLemburController.php b/app/Http/Controllers/LaporanLemburController.php new file mode 100644 index 00000000..ff0347a6 --- /dev/null +++ b/app/Http/Controllers/LaporanLemburController.php @@ -0,0 +1,102 @@ +input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $search = $request->input('search'); + $divisiId = $request->input('id_divisi'); + + $query = User::with(['divisi', 'jabatan', 'lemburs' => function($q) use ($bulan, $tahun) { + $q->whereYear('tanggal_lembur', $tahun) + ->whereMonth('tanggal_lembur', $bulan) + ->where('id_status', 2); + }]) + ->whereHas('roles', function($q) { + $q->where('nama_role', '!=', 'super_admin'); + }); + + if ($search) { + $query->where(function($q) use ($search) { + $q->where('nama_lengkap', 'like', '%' . $search . '%') + ->orWhere('nik', 'like', '%' . $search . '%'); + }); + } + + if ($divisiId) { + $query->where('id_divisi', $divisiId); + } + + $pegawai = $query->orderBy('nama_lengkap', 'asc')->paginate(15)->withQueryString(); + $divisiList = \App\Models\Divisi::all(); + + $rekap = []; + foreach ($pegawai as $p) { + $totalMenit = $p->lemburs->sum('durasi_menit'); + $jam = floor($totalMenit / 60); + $menit = $totalMenit % 60; + + $rekap[$p->id] = [ + 'total_menit' => $totalMenit, + 'format_jam' => "{$jam}j {$menit}m", + 'jumlah_hari' => $p->lemburs->count(), + 'poin_diperoleh' => $totalMenit, + ]; + } + + return view('laporan-lembur.index', compact('pegawai', 'rekap', 'bulan', 'tahun', 'search', 'divisiId', 'divisiList')); + } + + public function exportExcel(Request $request) + { + $bulan = $request->input('bulan', date('m')); + $tahun = $request->input('tahun', date('Y')); + $divisiId = $request->input('id_divisi'); + + $query = User::with(['divisi', 'jabatan', 'lemburs' => function($q) use ($bulan, $tahun) { + $q->whereYear('tanggal_lembur', $tahun) + ->whereMonth('tanggal_lembur', $bulan) + ->where('id_status', 2); + }]) + ->whereHas('roles', function($q) { + $q->where('nama_role', '!=', 'super_admin'); + }); + + if ($divisiId) { + $query->where('id_divisi', $divisiId); + } + + $pegawai = $query->orderBy('nama_lengkap', 'asc')->get(); + + $rekap = []; + foreach ($pegawai as $p) { + $totalMenit = $p->lemburs->sum('durasi_menit'); + $jam = floor($totalMenit / 60); + $menit = $totalMenit % 60; + + $rekap[$p->id] = [ + 'total_menit' => $totalMenit, + 'format_jam' => "{$jam}j {$menit}m", + 'jumlah_hari' => $p->lemburs->count(), + 'poin_diperoleh' => $totalMenit, + ]; + } + + $filename = "Laporan_Lembur_{$bulan}_{$tahun}.xlsx"; + + return Excel::download(new LaporanLemburExport($pegawai, $rekap, $bulan, $tahun), $filename); + } +} diff --git a/app/Http/Controllers/LemburController.php b/app/Http/Controllers/LemburController.php new file mode 100644 index 00000000..83208233 --- /dev/null +++ b/app/Http/Controllers/LemburController.php @@ -0,0 +1,87 @@ +lemburService = $lemburService; + } + + public function index() + { + $lembur = Lembur::with(['user', 'status', 'kompensasi']) + ->orderByRaw("CASE WHEN id_status = 1 THEN 0 ELSE 1 END") + ->orderBy('created_at', 'desc') + ->paginate(10); + + return view('lembur.index', compact('lembur')); + } + + public function create() + { + $pegawai = User::where('status_aktif', 1)->orderBy('nama_lengkap', 'asc')->get(); + $kompensasi = JenisKompensasi::all(); + return view('lembur.create', compact('pegawai', 'kompensasi')); + } + + public function store(Request $request) + { + $request->validate([ + 'id_user' => 'required|exists:users,id', + 'tanggal_lembur' => 'required|date', + 'jam_mulai' => 'required', + 'jam_selesai' => 'required', + 'id_kompensasi' => 'required|exists:jenis_kompensasi,id_kompensasi', + 'keterangan' => 'nullable|string' + ]); + + try { + DB::transaction(function () use ($request) { + $user = User::findOrFail($request->id_user); + $lembur = $this->lemburService->createLembur($user, $request->all()); + $this->lemburService->approve($lembur); + }); + + return redirect()->route('lembur.index')->with('success', 'Data lembur manual berhasil ditambahkan dan disetujui otomatis.'); + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Gagal menambahkan lembur: ' . $e->getMessage())->withInput(); + } + } + + public function update(Request $request, Lembur $lembur) + { + $request->validate([ + 'action' => 'required|in:approve,reject', + 'alasan_penolakan' => 'required_if:action,reject|nullable|string' + ]); + + try { + if ($request->action == 'approve') { + $this->lemburService->approve($lembur); + $message = 'Pengajuan lembur berhasil disetujui.'; + + if ($lembur->id_kompensasi == 2) { + $message .= ' Poin telah ditambahkan ke karyawan.'; + } + } else { + $this->lemburService->reject($lembur, $request->alasan_penolakan); + $message = 'Pengajuan lembur berhasil ditolak.'; + } + + return redirect()->back()->with('success', $message); + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Gagal memproses pengajuan: ' . $e->getMessage()); + } + } +} \ No newline at end of file diff --git a/app/Http/Controllers/PegawaiController.php b/app/Http/Controllers/PegawaiController.php new file mode 100644 index 00000000..2715c44d --- /dev/null +++ b/app/Http/Controllers/PegawaiController.php @@ -0,0 +1,186 @@ +whereDoesntHave('roles', function ($q) { + $q->where('nama_role', 'admin'); + }); + + if ($request->filled('filter_jabatan')) { + $query->where('id_jabatan', $request->filter_jabatan); + } + + if ($request->filled('filter_kantor')) { + $query->where('id_kantor', $request->filter_kantor); + } + + if ($request->filled('filter_status')) { + $status = $request->filter_status == 'active' ? 1 : 0; + $query->where('status_aktif', $status); + } + + if ($request->filled('search')) { + $search = $request->search; + $query->where(function ($q) use ($search) { + $q->where('nama_lengkap', 'like', "%{$search}%") + ->orWhere('email', 'like', "%{$search}%") + ->orWhere('nik', 'like', "%{$search}%"); + }); + } + + $pegawai = $query->latest()->paginate(10)->withQueryString(); + + $allKantor = Kantor::withCount([ + 'users as total_pegawai' => function ($q) { + $q->whereDoesntHave('roles', function ($sq) { + $sq->where('nama_role', 'admin'); + }); + } + ])->get(); + + $stats = [ + 'kantor_list' => $allKantor, + 'total' => User::whereDoesntHave('roles', function ($q) { + $q->where('nama_role', 'admin'); + })->count(), + 'active' => User::where('status_aktif', 1) + ->whereDoesntHave('roles', function ($q) { + $q->where('nama_role', 'admin'); + })->count(), + ]; + + $allJabatan = Jabatan::all(); + + return view('pegawai.index', compact('pegawai', 'stats', 'allJabatan', 'allKantor')); + } + + public function store(StorePegawaiRequest $request) + { + $data = $request->validated(); + + if ($request->hasFile('foto')) { + $data['foto'] = $request->file('foto')->store('foto-profil', 'public'); + } + + $data['password'] = Hash::make('Mpg123!'); + $data['status_aktif'] = 1; + $data['sisa_cuti'] = 12; + + $user = User::create($data); + + if ($request->filled('id_role')) { + $user->roles()->attach($request->id_role); + } + + return redirect()->route('pegawai.index')->with('success', 'Pegawai baru berhasil ditambahkan.'); + } + + public function update(UpdatePegawaiRequest $request, $id) + { + $pegawai = User::findOrFail($id); + $data = $request->validated(); + + if ($request->hasFile('foto')) { + if ($pegawai->foto) { + Storage::disk('public')->delete($pegawai->foto); + } + $data['foto'] = $request->file('foto')->store('foto-profil', 'public'); + } + + $pegawai->update($data); + + if ($request->filled('id_role')) { + $pegawai->roles()->sync([$request->id_role]); + } + + return redirect()->route('pegawai.index')->with('success', 'Data pegawai berhasil diperbarui.'); + } + + public function create() + { + $divisi = Divisi::all(); + $jabatan = Jabatan::all(); + $kantor = Kantor::all(); + $roles = Role::all(); + return view('pegawai.create', compact('divisi', 'jabatan', 'kantor', 'roles')); + } + + public function show($id) + { + $pegawai = User::with(['divisi', 'jabatan', 'kantor', 'roles'])->findOrFail($id); + + $sisaPoin = \App\Models\PoinLembur::where('id_user', $id) + ->where('is_fully_used', false) + ->whereDate('expired_at', '>=', now()->toDateString()) + ->sum('sisa_poin'); + + $historyTambah = \App\Models\PoinLembur::where('id_user', $id) + ->select('id_poin as id', 'tanggal', 'jumlah_poin as jumlah', 'keterangan', \DB::raw("'penambahan' as tipe")) + ->orderBy('tanggal', 'desc') + ->take(10) + ->get(); + + $historyKurang = \App\Models\PenggunaanPoin::where('id_user', $id) + ->where('id_status', 1) + ->select('id_penggunaan as id', 'tanggal_penggunaan as tanggal', 'jumlah_poin as jumlah', \DB::raw("'Penggunaan Poin' as keterangan"), \DB::raw("'pengurangan' as tipe")) + ->orderBy('tanggal_penggunaan', 'desc') + ->take(10) + ->get(); + + $historyPoin = $historyTambah->concat($historyKurang)->sortByDesc('tanggal')->take(10); + + $riwayatPresensi = \App\Models\Presensi::with(['status', 'jadwal.shift']) + ->where('id_user', $id) + ->whereDate('tanggal', '>=', now()->subDays(30)->toDateString()) + ->orderBy('tanggal', 'desc') + ->get(); + + $riwayatIzin = \App\Models\PengajuanIzin::with('jenisIzin') + ->where('id_user', $id) + ->whereDate('tanggal_mulai', '>=', now()->subDays(30)->toDateString()) + ->orderBy('tanggal_mulai', 'desc') + ->get(); + + return view('pegawai.show', compact('pegawai', 'sisaPoin', 'historyPoin', 'riwayatPresensi', 'riwayatIzin')); + } + + public function edit($id) + { + $pegawai = User::findOrFail($id); + $divisi = Divisi::all(); + $jabatan = Jabatan::all(); + $kantor = Kantor::all(); + $roles = Role::all(); + return view('pegawai.edit', compact('pegawai', 'divisi', 'jabatan', 'kantor', 'roles')); + } + + public function destroy($id) + { + $pegawai = User::findOrFail($id); + + if ($pegawai->status_aktif == 0) { + $pegawai->delete(); + return redirect()->route('pegawai.index')->with('success', 'Data pegawai berhasil dihapus (Soft Delete).'); + } + + $pegawai->update(['status_aktif' => 0]); + + return redirect()->route('pegawai.index')->with('success', 'Status pegawai telah diubah menjadi Non-Aktif (Resigned). Histori data tetap tersimpan.'); + } +} diff --git a/app/Http/Controllers/PengajuanIzinController.php b/app/Http/Controllers/PengajuanIzinController.php new file mode 100644 index 00000000..63a34cd0 --- /dev/null +++ b/app/Http/Controllers/PengajuanIzinController.php @@ -0,0 +1,197 @@ +get('status'); + + $query = PengajuanIzin::with(['user', 'jenisIzin', 'statusPengajuan', 'suratIzin']); + + if ($statusId) { + $query->where('id_status', $statusId); + } + + if ($request->has('search') && $request->search != '') { + $query->whereHas('user', function ($q) use ($request) { + $q->where('nama_lengkap', 'like', '%' . $request->search . '%'); + }); + } + + $izin = $query->orderBy('created_at', 'desc')->paginate(15); + + $statuses = \App\Models\StatusPengajuan::all(); + $jenisIzin = \App\Models\JenisIzin::all(); + $users = \App\Models\User::with(['kantor', 'divisi', 'jabatan'])->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan')->orderBy('nama_lengkap', 'asc')->get(); + $kantor = \App\Models\Kantor::all(); + $divisi = \App\Models\Divisi::all(); + + return view('izin.index', compact('izin', 'statusId', 'statuses', 'jenisIzin', 'users', 'kantor', 'divisi')); + } + + public function store(Request $request) + { + $request->validate([ + 'id_user' => 'required|exists:users,id', + 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', + 'tanggal_mulai' => 'required|date', + 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', + 'alasan' => 'required|string', + 'bukti_file' => 'required|image|mimes:jpeg,png,jpg,pdf|max:2048' + ]); + + $jenisIzin = JenisIzin::find($request->id_jenis_izin); + if ($jenisIzin && $jenisIzin->nama_izin == 'Cuti') { + $diffParams = Carbon::parse($request->tanggal_mulai, 'Asia/Jakarta')->diffInDays(Carbon::now('Asia/Jakarta')); + + if (Carbon::parse($request->tanggal_mulai, 'Asia/Jakarta')->diffInDays(Carbon::now('Asia/Jakarta')) < 7) { + return back()->with('error', 'Pengajuan Cuti minimal H-7!'); + } + } + + try { + DB::beginTransaction(); + + $path = null; + if ($request->hasFile('bukti_file')) { + $path = $request->file('bukti_file')->store('uploads/izin', 'public'); + } + + $izin = PengajuanIzin::create([ + 'id_user' => $request->id_user, + 'id_jenis_izin' => $request->id_jenis_izin, + 'tanggal_mulai' => $request->tanggal_mulai, + 'tanggal_selesai' => $request->tanggal_selesai, + 'alasan' => $request->alasan, + 'bukti_file' => $path, + 'id_status' => 1 + ]); + + if ($izin->id_jenis_izin == 2) { + $user = User::with(['jabatan', 'divisi'])->find($request->id_user); + $ttdAktif = TandaTangan::where('id_user', $user->id)->active()->first(); + $tglMulai = Carbon::parse($request->tanggal_mulai)->translatedFormat('d F Y'); + $tglSelesai = Carbon::parse($request->tanggal_selesai)->translatedFormat('d F Y'); + + $isiSurat = "Dengan hormat,\n\n" + . "Saya yang bertanda tangan di bawah ini:\n" + . "Nama: {$user->nama_lengkap}\n" + . "NIK: {$user->nik}\n" + . "Jabatan: " . ($user->jabatan->nama_jabatan ?? '-') . "\n" + . "Divisi: " . ($user->divisi->nama_divisi ?? '-') . "\n\n" + . "Dengan ini mengajukan Cuti mulai tanggal {$tglMulai} sampai dengan {$tglSelesai}.\n\n" + . "Alasan: {$request->alasan}\n\n" + . "Demikian surat ini saya buat dengan sebenar-benarnya. " + . "Atas perhatian dan persetujuannya, saya ucapkan terima kasih."; + + SuratIzin::create([ + 'id_izin' => $izin->id_izin, + 'id_user' => $user->id, + 'id_ttd_pengaju' => $ttdAktif?->id_tanda_tangan, + 'isi_surat' => $isiSurat, + 'status_surat' => 'menunggu_manajer', + ]); + } + + DB::commit(); + return redirect()->back()->with('success', 'Pengajuan izin berhasil dibuat.'); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', 'Gagal membuat izin: ' . $e->getMessage()); + } + } + + public function approve($id) + { + try { + DB::beginTransaction(); + + $izin = PengajuanIzin::with('jenisIzin')->findOrFail($id); + + if ($izin->id_status !== 1) { + return redirect()->back()->with('error', 'Pengajuan ini sudah diproses sebelumnya.'); + } + + if ($izin->id_jenis_izin == 2) { + return redirect()->back()->with('error', 'Pengajuan Cuti harus disetujui melalui menu Surat Izin (Manajer → HRD).'); + } + + $izin->update(['id_status' => 2]); + + $statusPresensiId = 3; + if ($izin->id_jenis_izin == 1) { + $statusPresensiId = 4; + } + + $startDate = Carbon::parse($izin->tanggal_mulai, 'Asia/Jakarta'); + $endDate = Carbon::parse($izin->tanggal_selesai, 'Asia/Jakarta'); + + while ($startDate->lte($endDate)) { + $tanggal = $startDate->format('Y-m-d'); + + Presensi::updateOrCreate( + [ + 'id_user' => $izin->id_user, + 'tanggal' => $tanggal + ], + [ + 'id_status' => $statusPresensiId, + 'jam_masuk' => null, + 'jam_pulang' => null, + 'id_validasi' => 1, + 'alasan_telat' => $izin->jenisIzin->nama_izin . ': ' . $izin->alasan + ] + ); + + $startDate->addDay(); + } + + if ($izin->id_jenis_izin == 2) { + $user = \App\Models\User::find($izin->id_user); + $jumlahHari = Carbon::parse($izin->tanggal_mulai)->diffInDays(Carbon::parse($izin->tanggal_selesai)) + 1; + $user->decrement('sisa_cuti', $jumlahHari); + } + + DB::commit(); + return redirect()->back()->with('success', 'Izin disetujui & data presensi diperbarui.'); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', 'Gagal: ' . $e->getMessage()); + } + } + + public function reject(Request $request, $id) + { + $izin = PengajuanIzin::findOrFail($id); + + if ($izin->id_status !== 1) { + return redirect()->back()->with('error', 'Pengajuan ini sudah diproses sebelumnya.'); + } + + if ($izin->id_jenis_izin == 2) { + return redirect()->back()->with('error', 'Pengajuan Cuti harus ditolak melalui menu Surat Izin.'); + } + + $izin->update([ + 'id_status' => 3 + ]); + + return redirect()->back()->with('success', 'Pengajuan izin berhasil ditolak.'); + } +} diff --git a/app/Http/Controllers/PenggunaanPoinController.php b/app/Http/Controllers/PenggunaanPoinController.php new file mode 100644 index 00000000..150fc9e2 --- /dev/null +++ b/app/Http/Controllers/PenggunaanPoinController.php @@ -0,0 +1,66 @@ +poinService = $poinService; + } + + public function index() + { + $penggunaan = PenggunaanPoin::with(['user', 'jenisPengurangan', 'status']) + ->orderByRaw("CASE WHEN id_status = 1 THEN 0 ELSE 1 END") + ->orderBy('tanggal_diajukan', 'desc') + ->paginate(10); + + return view('penggunaan-poin.index', compact('penggunaan')); + } + + public function update(Request $request, $id) + { + $request->validate([ + 'action' => 'required|in:approve,reject', + 'alasan_penolakan' => 'required_if:action,reject|nullable|string' + ]); + + $penggunaan = PenggunaanPoin::findOrFail($id); + + try { + if ($request->action == 'approve') { + DB::transaction(function () use ($penggunaan) { + $penggunaan->update(['id_status' => 2]); + + $this->poinService->deductPoin( + $penggunaan->id_user, + $penggunaan->jumlah_poin, + $penggunaan->id_penggunaan + ); + }); + + $message = 'Pengajuan berhasil disetujui dan poin telah dipotong.'; + } else { + $penggunaan->update([ + 'id_status' => 3, + 'alasan_penolakan' => $request->alasan_penolakan + ]); + + $message = 'Pengajuan berhasil ditolak.'; + } + + return redirect()->back()->with('success', $message); + + } catch (\Exception $e) { + return redirect()->back()->with('error', 'Gagal memproses: ' . $e->getMessage()); + } + } +} diff --git a/app/Http/Controllers/PengumumanController.php b/app/Http/Controllers/PengumumanController.php new file mode 100644 index 00000000..330ae7dc --- /dev/null +++ b/app/Http/Controllers/PengumumanController.php @@ -0,0 +1,61 @@ +orderBy('tanggal', 'desc') + ->paginate(10); + + $title = 'Pengumuman'; + + return view('pengumuman.index', compact('pengumuman', 'title')); + } + + public function create() + { + $title = 'Buat Pengumuman'; + return view('pengumuman.create', compact('title')); + } + + public function store(StorePengumumanRequest $request) + { + $data = $request->validated(); + $data['dibuat_oleh'] = Auth::id(); + + Pengumuman::create($data); + + return redirect()->route('pengumuman.index') + ->with('success', 'Pengumuman berhasil ditambahkan.'); + } + + public function edit(Pengumuman $pengumuman) + { + $title = 'Edit Pengumuman'; + return view('pengumuman.edit', compact('pengumuman', 'title')); + } + + public function update(UpdatePengumumanRequest $request, Pengumuman $pengumuman) + { + $pengumuman->update($request->validated()); + + return redirect()->route('pengumuman.index') + ->with('success', 'Pengumuman berhasil diperbarui.'); + } + + public function destroy(Pengumuman $pengumuman) + { + $pengumuman->delete(); + + return redirect()->route('pengumuman.index') + ->with('success', 'Pengumuman berhasil dihapus.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php new file mode 100644 index 00000000..bb9612e7 --- /dev/null +++ b/app/Http/Controllers/PermissionController.php @@ -0,0 +1,39 @@ +get(); + return view('permission.index', compact('permissions', 'roles')); + } + + public function store(Request $request) + { + $request->validate([ + 'nama_permission' => 'required|unique:permissions,nama_permission', + ]); + + Permission::create([ + 'nama_permission' => $request->nama_permission, + 'slug' => \Illuminate\Support\Str::slug($request->nama_permission) + ]); + + return back()->with('success', 'Permission berhasil dibuat.'); + } + + public function sync(Request $request, $id_role) + { + $role = Role::findOrFail($id_role); + $role->permissions()->sync($request->permissions); + + return back()->with('success', 'Hak akses role berhasil diperbarui.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/PresensiController.php b/app/Http/Controllers/PresensiController.php new file mode 100644 index 00000000..b4995d67 --- /dev/null +++ b/app/Http/Controllers/PresensiController.php @@ -0,0 +1,471 @@ +get('tanggal', Carbon::today()->format('Y-m-d')); + $divisiId = $request->get('divisi_id'); + + $pendingDates = Presensi::where('id_validasi', 2) + ->selectRaw('DATE(tanggal) as tgl, COUNT(*) as jumlah') + ->groupBy('tgl') + ->orderBy('tgl', 'desc') + ->get(); + + $query = Presensi::with(['user.jabatan', 'user.kantor']) + ->whereDate('tanggal', $tanggal); + + if ($divisiId) { + $query->whereHas('user', function ($q) use ($divisiId) { + $q->where('id_divisi', $divisiId); + }); + } + + $filterStatus = $request->get('filter_status'); + if ($filterStatus == 'tepat_waktu') { + $query->where('id_status', 1); + } elseif ($filterStatus == 'terlambat') { + $query->where('id_status', 2); + } elseif ($filterStatus == 'pending') { + $query->where('id_validasi', 2); + } + + $presensi = $query->orderBy('jam_masuk', 'desc')->paginate(15); + + $divisi = \App\Models\Divisi::all(); + + $userIds = $presensi->pluck('id_user')->unique(); + $jadwalCollection = JadwalKerja::with('shift') + ->whereIn('id_user', $userIds) + ->where('tanggal', $tanggal) + ->get() + ->keyBy('id_user'); + + $poinCollection = \App\Models\PenggunaanPoin::with('jenisPengurangan') + ->whereIn('id_user', $userIds) + ->where('tanggal_penggunaan', $tanggal) + ->where('id_status', 2) + ->get() + ->keyBy('id_user'); + + $presensi->map(function ($item) use ($jadwalCollection, $poinCollection) { + $jadwal = $jadwalCollection->get($item->id_user); + $poinUsed = $poinCollection->get($item->id_user); + + $jamMasuk = $jadwal && $jadwal->shift ? $jadwal->shift->jam_mulai : '-'; + $jamPulang = $jadwal && $jadwal->shift ? $jadwal->shift->jam_selesai : '-'; + + $item->jam_jadwal_masuk_original = $jamMasuk; + $item->jam_jadwal_pulang_original = $jamPulang; + $item->is_adjusted = false; + $item->adjustment_note = null; + + if ($poinUsed) { + $menit = $poinUsed->jumlah_poin * 30; + $jenis = $poinUsed->jenisPengurangan->nama_pengurangan ?? ''; + + if ($jenis == 'Pulang Cepat' && $jamPulang != '-') { + $jamPulang = Carbon::parse($jamPulang)->subMinutes($menit)->format('H:i:s'); + $item->is_adjusted = true; + $item->adjustment_note = "Pulang Cepat -{$menit}m"; + } elseif ($jenis == 'Datang Terlambat' && $jamMasuk != '-') { + $jamMasuk = Carbon::parse($jamMasuk)->addMinutes($menit)->format('H:i:s'); + $item->is_adjusted = true; + $item->adjustment_note = "Datang Telat +{$menit}m"; + } + } + + $item->jam_jadwal_masuk = $jamMasuk; + $item->jam_jadwal_pulang = $jamPulang; + $item->nama_shift = $jadwal && $jadwal->shift ? $jadwal->shift->nama_shift : 'Non-Shift'; + + $item->status_keterlambatan = 'Tepat Waktu'; + $item->badge_color = 'success'; + + if ($item->id_status == 2) { + $item->status_keterlambatan = $item->alasan_telat ?? 'Terlambat'; + $item->badge_color = 'rose'; + } elseif ($item->id_status == 1) { + $item->status_keterlambatan = 'Tepat Waktu'; + $item->badge_color = 'emerald'; + } + + $item->validasi_label = $item->id_validasi == 1 ? 'Disetujui' : ($item->id_validasi == 3 ? 'Ditolak' : 'Pending'); + $item->validasi_color = $item->id_validasi == 1 ? 'emerald' : ($item->id_validasi == 3 ? 'rose' : 'amber'); + + $item->jarak_masuk = null; + if ($item->user && $item->user->kantor && $item->lat_masuk && $item->lon_masuk) { + $item->jarak_masuk = app(self::class)->calculateDistance( + $item->lat_masuk, + $item->lon_masuk, + $item->user->kantor->latitude, + $item->user->kantor->longitude + ); + $item->jarak_masuk = round($item->jarak_masuk, 2); + } + + $item->dalam_radius = false; + if ($item->jarak_masuk !== null && $item->user && $item->user->kantor) { + $item->dalam_radius = $item->jarak_masuk <= $item->user->kantor->radius; + } + + return $item; + }); + + return view('presensi.index', compact('presensi', 'tanggal', 'divisi', 'pendingDates')); + } + + public function approve($id) + { + $presensi = Presensi::findOrFail($id); + $presensi->update(['id_validasi' => 1]); + + return redirect()->back()->with('success', 'Presensi berhasil disetujui.'); + } + + public function reject($id) + { + $presensi = Presensi::findOrFail($id); + $presensi->update(['id_validasi' => 3]); + + return redirect()->back()->with('success', 'Presensi berhasil ditolak.'); + } + + public function bulkAction(Request $request) + { + $request->validate([ + 'presensi_ids' => 'required|array', + 'action' => 'required|in:approve,reject' + ]); + + $statusValidasi = $request->action == 'approve' ? 1 : 3; + + Presensi::whereIn('id_presensi', $request->presensi_ids) + ->where('id_validasi', 2) + ->update(['id_validasi' => $statusValidasi]); + + $statusText = $request->action == 'approve' ? 'disetujui' : 'ditolak'; + return redirect()->back()->with('success', count($request->presensi_ids) . " pengajuan presensi berhasil {$statusText}."); + } + + public function create() + { + $pegawai = \App\Models\User::with('divisi') + ->orderBy('nama_lengkap', 'asc') + ->get(); + + $statuses = \Illuminate\Support\Facades\DB::table('status_presensi')->get(); + + return view('presensi.create', compact('pegawai', 'statuses')); + } + + public function storeManual(StoreManualPresensiRequest $request) + { + $existing = Presensi::where('id_user', $request->id_user) + ->where('tanggal', $request->tanggal) + ->first(); + + if ($existing) { + return redirect()->back() + ->withInput() + ->with('error', 'Pegawai bersangkutan sudah memiliki record presensi pada tanggal tersebut. Gunakan fitur Edit (Koreksi) jika ingin mengubah data.'); + } + + $jamMasuk = $request->jam_masuk ? $request->jam_masuk . ':00' : null; + $jamPulang = $request->jam_pulang ? $request->jam_pulang . ':00' : null; + + Presensi::create([ + 'id_user' => $request->id_user, + 'tanggal' => $request->tanggal, + 'id_status' => $request->id_status, + 'jam_masuk' => in_array($request->id_status, [1, 2]) ? $jamMasuk : null, + 'jam_pulang' => in_array($request->id_status, [1, 2]) ? $jamPulang : null, + 'alasan_telat' => $request->alasan_telat ?? 'Input Manual oleh Admin', + 'id_validasi' => 1, + 'verifikasi_wajah' => 1, + 'lat_masuk' => null, + 'lon_masuk' => null, + 'lat_pulang' => null, + 'lon_pulang' => null, + 'keterangan_luar_radius' => 'Input Manual oleh Admin', + 'foto_wajah_masuk' => null, + 'foto_wajah_pulang' => null, + ]); + + return redirect()->route('presensi.index', ['tanggal' => $request->tanggal]) + ->with('success', 'Presensi manual berhasil ditambahkan.'); + } + + public function edit($id) + { + $presensi = Presensi::with('user.divisi')->findOrFail($id); + $statuses = \Illuminate\Support\Facades\DB::table('status_presensi')->get(); + + return view('presensi.edit', compact('presensi', 'statuses')); + } + + public function updateManual(Request $request, $id) + { + $request->validate([ + 'id_status' => 'required|exists:status_presensi,id_status', + 'jam_masuk' => [ + \Illuminate\Validation\Rule::requiredIf(fn () => in_array($request->id_status, [1, 2])), + 'nullable', + 'date_format:H:i' + ], + 'jam_pulang' => 'nullable|date_format:H:i', + 'alasan_telat' => 'required|string|max:255' + ], [ + 'alasan_telat.required' => 'Catatan koreksi wajib diisi sebagai dokumentasi log.', + 'jam_masuk.required' => 'Jam masuk wajib diisi untuk kehadiran Hadir/Terlambat.' + ]); + + $presensi = Presensi::findOrFail($id); + + $jamMasuk = $request->jam_masuk ? $request->jam_masuk . ':00' : null; + $jamPulang = $request->jam_pulang ? $request->jam_pulang . ':00' : null; + + $isAdjusted = false; + $adjustmentNotes = []; + + if ($presensi->jam_masuk != $jamMasuk && in_array($request->id_status, [1, 2])) { + $isAdjusted = true; + $adjustmentNotes[] = "Datang dari " . substr($presensi->jam_masuk ?? '-', 0, 5) . " ke " . substr($jamMasuk, 0, 5); + } + + if ($presensi->jam_pulang != $jamPulang && in_array($request->id_status, [1, 2])) { + $isAdjusted = true; + $adjustmentNotes[] = "Pulang dari " . substr($presensi->jam_pulang ?? '-', 0, 5) . " ke " . substr($jamPulang, 0, 5); + } + + $presensi->update([ + 'id_status' => $request->id_status, + 'jam_masuk' => in_array($request->id_status, [1, 2]) ? $jamMasuk : null, + 'jam_pulang' => in_array($request->id_status, [1, 2]) ? $jamPulang : null, + 'alasan_telat' => $request->alasan_telat . ($isAdjusted ? " | [Koreksi Admin: " . implode(', ', $adjustmentNotes) . "]" : ""), + 'id_validasi' => 1, + ]); + + return redirect()->route('presensi.index', ['tanggal' => $presensi->tanggal]) + ->with('success', 'Data presensi berhasil dikoreksi.'); + } + + public function store(StorePresensiRequest $request) + { + $user = Auth::user(); + $hariIni = Carbon::today()->toDateString(); + $jamSekarang = Carbon::now(); + $status = $request->status; + + if ($status === 'pulang') { + return $this->handlePulang($request, $user, $hariIni, $jamSekarang); + } + + $jadwal = JadwalKerja::with('shift') + ->where('id_user', $user->id) + ->where('tanggal', $hariIni) + ->first(); + + if (!$jadwal) { + return response()->json([ + 'success' => false, + 'message' => 'Anda tidak memiliki jadwal hari ini.' + ], 403); + } + + $cekPresensi = Presensi::where('id_user', $user->id) + ->where('tanggal', $hariIni) + ->first(); + + if ($cekPresensi) { + return response()->json([ + 'success' => false, + 'message' => 'Anda sudah melakukan absen masuk hari ini.' + ], 400); + } + + $kantor = $user->kantor; + if (!$kantor) { + return response()->json([ + 'success' => false, + 'message' => 'Data kantor belum diatur untuk akun Anda.' + ], 400); + } + + $jarak = $this->calculateDistance( + $request->latitude, + $request->longitude, + $kantor->latitude, + $kantor->longitude + ); + + $radiusKantor = $kantor->radius; + $idValidasi = ($jarak <= $radiusKantor) ? 1 : 2; + + $presensiService = new \App\Services\PresensiService(); + $dynamicSchedule = $presensiService->getDynamicSchedule($user->id, $hariIni); + + $jamMasukEfektif = $dynamicSchedule + ? $dynamicSchedule['jam_masuk'] + : $jadwal->shift->jam_mulai; + $statusJadwal = $dynamicSchedule['status_jadwal'] ?? 'Normal'; + + $jamMasukParsed = Carbon::parse($jamMasukEfektif, 'Asia/Jakarta'); + $toleransi = 15; + $batasToleransi = $jamMasukParsed->copy()->addMinutes($toleransi); + + $idStatus = 1; + $alasanTelat = null; + + if ($jamSekarang->greaterThan($batasToleransi)) { + $idStatus = 2; + $selisihMenit = $jamSekarang->diffInMinutes($jamMasukParsed); + $alasanTelat = "Terlambat {$selisihMenit} menit"; + } + + $fotoFile = $request->file('foto'); + $imageName = 'masuk_' . $user->id . '_' . time() . '.' . $fotoFile->getClientOriginalExtension(); + $fotoFile->storeAs('uploads/absensi', $imageName, 'public'); + + $verifikasiWajah = 0; + $faceMessage = null; + + try { + $faceService = new \App\Services\FaceRecognitionService(); + $result = $faceService->verifyFace($user->id, $fotoFile); + + if (isset($result['match']) && $result['match'] === true) { + $verifikasiWajah = 1; + } + + $faceMessage = $result['message'] ?? null; + } catch (\Exception $e) { + } + + $presensi = Presensi::create([ + 'id_user' => $user->id, + 'tanggal' => $hariIni, + 'jam_masuk' => $jamSekarang->format('H:i:s'), + 'lat_masuk' => $request->latitude, + 'lon_masuk' => $request->longitude, + 'foto_wajah_masuk' => 'uploads/absensi/' . $imageName, + 'id_status' => $idStatus, + 'alasan_telat' => $alasanTelat, + 'id_validasi' => $idValidasi, + 'keterangan_luar_radius' => $request->keterangan_luar_radius, + 'verifikasi_wajah' => $verifikasiWajah, + ]); + + return response()->json([ + 'success' => true, + 'status' => 'success', + 'message' => 'Absen masuk berhasil!', + 'data' => [ + 'jam_masuk' => $jamSekarang->format('H:i'), + 'status' => ($idStatus == 1) ? 'Tepat Waktu' : 'Terlambat', + 'jarak' => round($jarak, 2) . ' meter', + 'validasi_lokasi' => ($idValidasi == 1) ? 'Dalam Radius' : 'Di Luar Radius (Pending)', + 'verifikasi_wajah' => $verifikasiWajah == 1 ? 'Terverifikasi' : 'Pending', + 'face_message' => $faceMessage, + ] + ], 200); + } + + private function handlePulang($request, $user, $hariIni, $jamSekarang) + { + $presensi = Presensi::where('id_user', $user->id) + ->whereNull('jam_pulang') + ->latest() + ->first(); + + if (!$presensi) { + return response()->json([ + 'success' => false, + 'message' => 'Anda belum absen masuk atau sudah absen pulang.' + ], 404); + } + + $fotoFile = $request->file('foto'); + $imageName = 'pulang_' . $user->id . '_' . time() . '.' . $fotoFile->getClientOriginalExtension(); + $fotoFile->storeAs('uploads/absensi', $imageName, 'public'); + + $verifikasiPulang = 0; + try { + $faceService = new \App\Services\FaceRecognitionService(); + $result = $faceService->verifyFace($user->id, $fotoFile); + if (isset($result['match']) && $result['match'] === true) { + $verifikasiPulang = 1; + } + } catch (\Exception $e) { + } + + $presensiService = new \App\Services\PresensiService(); + $dynamicSchedule = $presensiService->getDynamicSchedule($user->id, $hariIni); + + $jamPulangEfektif = $dynamicSchedule + ? $dynamicSchedule['jam_pulang'] + : null; + + $waktuPulangAwal = null; + $waktuPulangAkhir = null; + + if ($jamPulangEfektif) { + $jamPulangTarget = Carbon::parse($jamPulangEfektif, 'Asia/Jakarta'); + + if ($jamSekarang->lessThan($jamPulangTarget)) { + $waktuPulangAwal = $jamSekarang->diff($jamPulangTarget)->format('%H:%I:%S'); + } else if ($jamSekarang->greaterThan($jamPulangTarget)) { + $waktuPulangAkhir = $jamSekarang->diff($jamPulangTarget)->format('%H:%I:%S'); + } + } + + $presensi->update([ + 'jam_pulang' => $jamSekarang->format('H:i:s'), + 'lat_pulang' => $request->latitude, + 'lon_pulang' => $request->longitude, + 'foto_wajah_pulang' => 'uploads/absensi/' . $imageName, + 'waktu_pulang_awal' => $waktuPulangAwal, + 'waktu_pulang_akhir' => $waktuPulangAkhir, + ]); + + return response()->json([ + 'success' => true, + 'status' => 'success', + 'message' => 'Absen pulang berhasil. Hati-hati di jalan!', + 'data' => [ + 'jam_pulang' => $jamSekarang->format('H:i'), + 'verifikasi_wajah' => $verifikasiPulang == 1 ? 'Terverifikasi' : 'Pending', + ] + ], 200); + } + + private function calculateDistance($lat1, $lon1, $lat2, $lon2) + { + $earthRadius = 6371000; + + $dLat = deg2rad($lat2 - $lat1); + $dLon = deg2rad($lon2 - $lon1); + + $a = sin($dLat / 2) * sin($dLat / 2) + + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * + sin($dLon / 2) * sin($dLon / 2); + + $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); + + return $earthRadius * $c; + } +} diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100644 index 00000000..90c4af1f --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,50 @@ +validate([ + 'nama_lengkap' => 'required|string|max:255', + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], + 'foto' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', + 'password' => 'nullable|string|min:8|confirmed', + ]); + + $data = [ + 'nama_lengkap' => $request->nama_lengkap, + 'email' => $request->email, + ]; + + if ($request->filled('password')) { + $data['password'] = Hash::make($request->password); + } + + if ($request->hasFile('foto')) { + if ($user->foto) { + Storage::disk('public')->delete($user->foto); + } + $data['foto'] = $request->file('foto')->store('foto-profil', 'public'); + } + + $user->update($data); + + return redirect()->route('profile.edit')->with('success', 'Profil berhasil diperbarui.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php new file mode 100644 index 00000000..158cc364 --- /dev/null +++ b/app/Http/Controllers/RoleController.php @@ -0,0 +1,67 @@ +filled('search')) { + $query->where('nama_role', 'like', '%' . $request->search . '%'); + } + + $roles = $query->latest('id_role')->paginate(10)->withQueryString(); + $allPermissions = \App\Models\Permission::orderBy('nama_permission', 'asc')->get(); + + return view('role.index', compact('roles', 'allPermissions')); + } + + public function store(StoreRoleRequest $request) + { + $role = Role::create($request->validated()); + + if ($request->has('id_permissions')) { + $role->permissions()->sync($request->id_permissions); + } + + return redirect()->route('role.index') + ->with('success', 'Role baru berhasil ditambahkan.'); + } + + public function update(UpdateRoleRequest $request, $id) + { + $role = Role::findOrFail($id); + $role->update($request->validated()); + + if ($request->has('id_permissions')) { + $role->permissions()->sync($request->id_permissions); + } else { + $role->permissions()->detach(); + } + + return redirect()->route('role.index') + ->with('success', 'Role berhasil diperbarui.'); + } + + public function destroy($id) + { + $role = Role::findOrFail($id); + + if ($role->users()->exists()) { + return redirect()->back()->with('error', 'Role tidak bisa dihapus karena masih digunakan user.'); + } + + $role->delete(); + + return redirect()->route('role.index') + ->with('success', 'Role berhasil dihapus.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ShiftController.php b/app/Http/Controllers/ShiftController.php new file mode 100644 index 00000000..5b8abeb6 --- /dev/null +++ b/app/Http/Controllers/ShiftController.php @@ -0,0 +1,56 @@ +filled('search')) { + $query->where('nama_shift', 'like', '%' . $request->search . '%'); + } + + $shifts = $query->latest('id_shift')->paginate(10)->withQueryString(); + + return view('shift.index', compact('shifts')); + } + + public function store(StoreShiftRequest $request) + { + ShiftKerja::create($request->validated()); + + return redirect()->route('shift.index') + ->with('success', 'Shift kerja berhasil ditambahkan.'); + } + + public function update(UpdateShiftRequest $request, $id) + { + $shift = ShiftKerja::findOrFail($id); + $shift->update($request->validated()); + + return redirect()->route('shift.index') + ->with('success', 'Shift kerja berhasil diperbarui.'); + } + + public function destroy($id) + { + $shift = ShiftKerja::findOrFail($id); + + if ($shift->jadwalKerja()->exists()) { + return redirect()->back() + ->with('error', 'Shift tidak bisa dihapus karena masih digunakan di jadwal.'); + } + + $shift->delete(); + + return redirect()->route('shift.index') + ->with('success', 'Shift kerja berhasil dihapus.'); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/SignatureController.php b/app/Http/Controllers/SignatureController.php new file mode 100644 index 00000000..30aa127e --- /dev/null +++ b/app/Http/Controllers/SignatureController.php @@ -0,0 +1,49 @@ +active()->first(); + return view('signature.show', compact('ttd')); + } + + public function store(StoreSignatureRequest $request) + { + try { + $base64 = $request->signature_data; + + if (str_contains($base64, ',')) { + $base64 = explode(',', $base64)[1]; + } + + $imageData = base64_decode($base64); + if ($imageData === false) { + return back()->with('error', 'Data tanda tangan tidak valid.'); + } + + $fileName = 'uploads/ttd/' . Auth::id() . '_' . time() . '.png'; + Storage::disk('public')->put($fileName, $imageData); + + TandaTangan::where('id_user', Auth::id())->update(['is_active' => false]); + + TandaTangan::create([ + 'id_user' => Auth::id(), + 'file_ttd' => $fileName, + 'is_active' => true, + ]); + + return back()->with('success', 'Tanda tangan berhasil disimpan.'); + + } catch (\Exception $e) { + return back()->with('error', 'Gagal menyimpan tanda tangan: ' . $e->getMessage()); + } + } +} diff --git a/app/Http/Controllers/SuratIzinController.php b/app/Http/Controllers/SuratIzinController.php new file mode 100644 index 00000000..49f04487 --- /dev/null +++ b/app/Http/Controllers/SuratIzinController.php @@ -0,0 +1,218 @@ +filled('status')) { + $query->where('status_surat', $request->status); + } + + if ($request->filled('search')) { + $query->whereHas('user', function ($q) use ($request) { + $q->where('nama_lengkap', 'like', '%' . $request->search . '%'); + }); + } + + $suratList = $query->orderBy('created_at', 'desc')->paginate(15); + + return view('surat-izin.index', compact('suratList')); + } + + public function show($id) + { + $surat = SuratIzin::with([ + 'user.jabatan', + 'user.divisi', + 'pengajuanIzin.jenisIzin', + 'tandaTanganPengaju', + 'approvals.approver.jabatan', + 'approvals.tandaTanganApprover', + ])->findOrFail($id); + + $user = Auth::user(); + $canApprove = false; + $tahapApproval = null; + + $isSuperAdmin = $user->roles->contains(function ($role) { + return in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']); + }); + + if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isSuperAdmin)) { + $canApprove = true; + $tahapApproval = 1; + } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isSuperAdmin)) { + $canApprove = true; + $tahapApproval = 2; + } + + $ttdApprover = TandaTangan::where('id_user', $user->id)->active()->first(); + + return view('surat-izin.show', compact('surat', 'canApprove', 'tahapApproval', 'ttdApprover')); + } + + public function approve(Request $request, $id) + { + try { + $surat = SuratIzin::findOrFail($id); + $user = Auth::user(); + + if (in_array($surat->status_surat, ['disetujui', 'ditolak'])) { + return redirect()->back()->with('error', 'Surat ini sudah diproses sebelumnya.'); + } + + $tahap = null; + $isSuperAdmin = $user->roles->contains(function ($role) { + return in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']); + }); + + if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isSuperAdmin)) { + $tahap = 1; + } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isSuperAdmin)) { + $tahap = 2; + } + + if (!$tahap) { + return redirect()->back()->with('error', 'Anda tidak memiliki akses untuk menyetujui surat ini.'); + } + + $ttd = TandaTangan::where('id_user', $user->id)->active()->first(); + if (!$ttd) { + return redirect()->back()->with('error', 'Anda belum memiliki tanda tangan digital.'); + } + + DB::beginTransaction(); + + ApprovalSurat::create([ + 'id_surat' => $surat->id_surat, + 'id_approver' => $user->id, + 'id_ttd_approver' => $ttd->id_tanda_tangan, + 'tahap' => $tahap, + 'status' => 'disetujui', + 'catatan' => $request->catatan, + ]); + + if ($tahap === 1) { + $surat->update(['status_surat' => 'menunggu_hrd']); + } elseif ($tahap === 2) { + $surat->update(['status_surat' => 'disetujui']); + $this->prosesPersetujuanFinal($surat); + } + + DB::commit(); + + $statusLabel = $tahap === 1 ? 'Menunggu HRD' : 'Disetujui'; + return redirect()->back()->with('success', "Surat berhasil disetujui. Status: {$statusLabel}"); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', 'Gagal: ' . $e->getMessage()); + } + } + + public function reject(Request $request, $id) + { + try { + $surat = SuratIzin::findOrFail($id); + $user = Auth::user(); + + if (in_array($surat->status_surat, ['disetujui', 'ditolak'])) { + return redirect()->back()->with('error', 'Surat ini sudah diproses sebelumnya.'); + } + + $tahap = null; + $isSuperAdmin = $user->roles->contains(function ($role) { + return in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']); + }); + + if ($surat->status_surat === 'menunggu_manajer' && ($user->roles->contains('nama_role', 'manajer') || $isSuperAdmin)) { + $tahap = 1; + } elseif ($surat->status_surat === 'menunggu_hrd' && ($user->roles->contains('nama_role', 'hrd') || $isSuperAdmin)) { + $tahap = 2; + } + + if (!$tahap) { + return redirect()->back()->with('error', 'Anda tidak memiliki akses.'); + } + + DB::beginTransaction(); + + ApprovalSurat::create([ + 'id_surat' => $surat->id_surat, + 'id_approver' => $user->id, + 'id_ttd_approver' => null, + 'tahap' => $tahap, + 'status' => 'ditolak', + 'catatan' => $request->catatan, + ]); + + $surat->update(['status_surat' => 'ditolak']); + + if ($surat->pengajuanIzin) { + $surat->pengajuanIzin->update(['id_status' => 3]); + } + + DB::commit(); + + return redirect()->back()->with('success', 'Surat izin berhasil ditolak.'); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back()->with('error', 'Gagal: ' . $e->getMessage()); + } + } + + private function prosesPersetujuanFinal(SuratIzin $surat) + { + $izin = $surat->pengajuanIzin; + if (!$izin) + return; + + $izin->load('jenisIzin'); + $izin->update(['id_status' => 2]); + + $statusPresensiId = 3; + if ($izin->id_jenis_izin == 1) { + $statusPresensiId = 4; + } + + $startDate = Carbon::parse($izin->tanggal_mulai, 'Asia/Jakarta'); + $endDate = Carbon::parse($izin->tanggal_selesai, 'Asia/Jakarta'); + + while ($startDate->lte($endDate)) { + Presensi::updateOrCreate( + [ + 'id_user' => $izin->id_user, + 'tanggal' => $startDate->format('Y-m-d'), + ], + [ + 'id_status' => $statusPresensiId, + 'jam_masuk' => null, + 'jam_pulang' => null, + 'id_validasi' => 1, + 'alasan_telat' => $izin->jenisIzin->nama_izin . ': ' . $izin->alasan, + ] + ); + $startDate->addDay(); + } + + if ($izin->id_jenis_izin == 2) { + $user = \App\Models\User::find($izin->id_user, ['*']); + $jumlahHari = Carbon::parse($izin->tanggal_mulai)->diffInDays(Carbon::parse($izin->tanggal_selesai)) + 1; + $user->decrement('sisa_cuti', $jumlahHari); + } + } +} diff --git a/app/Http/Controllers/TukarShiftController.php b/app/Http/Controllers/TukarShiftController.php new file mode 100644 index 00000000..0445f9a5 --- /dev/null +++ b/app/Http/Controllers/TukarShiftController.php @@ -0,0 +1,91 @@ +latest() + ->get(); + + return view('tukar-shift.index', compact('riwayat')); + } + + public function create() + { + $pegawai = User::with('kantor')->whereDoesntHave('roles', function ($q) { + $q->where('nama_role', 'admin')->orWhere('nama_role', 'Admin'); + })->get(); + + $kantor = \App\Models\Kantor::orderBy('nama_kantor', 'asc')->get(); + + return view('tukar-shift.create', compact('pegawai', 'kantor')); + } + + public function store(StoreTukarShiftRequest $request) + { + DB::beginTransaction(); + + try { + $jadwal1 = JadwalKerja::findOrFail($request->id_jadwal_1); + $jadwal2 = JadwalKerja::findOrFail($request->id_jadwal_2); + + $tempUser1 = $jadwal1->id_user; + $jadwal1->id_user = $jadwal2->id_user; + $jadwal2->id_user = $tempUser1; + + $jadwal1->save(); + $jadwal2->save(); + + RiwayatTukarShift::create([ + 'id_user_1' => $request->id_user_1, + 'id_jadwal_1' => $jadwal1->id_jadwal, + 'id_user_2' => $request->id_user_2, + 'id_jadwal_2' => $jadwal2->id_jadwal, + 'keterangan' => $request->keterangan, + 'created_by' => Auth::id() ?? 1, + ]); + + DB::commit(); + + return redirect()->route('tukar-shift.index') + ->with('success', 'Berhasil menukar shift kerja untuk kedua pegawai tersebut.'); + + } catch (\Exception $e) { + DB::rollBack(); + return redirect()->back() + ->withInput() + ->with('error', 'Gagal menukar shift: ' . $e->getMessage()); + } + } + + public function getJadwalUser(Request $request) + { + $request->validate([ + 'id_user' => 'required|exists:users,id' + ]); + + $startOfMonth = Carbon::now()->startOfMonth()->toDateString(); + $endOfMonth = Carbon::now()->addMonth()->endOfMonth()->toDateString(); + + $jadwal = JadwalKerja::with('shift') + ->where('id_user', $request->id_user) + ->whereBetween('tanggal', [$startOfMonth, $endOfMonth]) + ->orderBy('tanggal', 'asc') + ->get(); + + return response()->json($jadwal); + } +} diff --git a/app/Http/Middleware/CheckRole.php b/app/Http/Middleware/CheckRole.php new file mode 100644 index 00000000..f1845ebb --- /dev/null +++ b/app/Http/Middleware/CheckRole.php @@ -0,0 +1,33 @@ +roles()->where('nama_role', $role)->exists()) { + return $next($request); + } + } + + abort(403, 'Unauthorized action.'); + } +} diff --git a/app/Http/Requests/StoreDivisiRequest.php b/app/Http/Requests/StoreDivisiRequest.php new file mode 100644 index 00000000..9d06accd --- /dev/null +++ b/app/Http/Requests/StoreDivisiRequest.php @@ -0,0 +1,20 @@ + ['required', 'string', 'max:255', 'unique:divisi,nama_divisi'], + ]; + } +} diff --git a/app/Http/Requests/StoreHariLiburRequest.php b/app/Http/Requests/StoreHariLiburRequest.php new file mode 100644 index 00000000..7e4786a6 --- /dev/null +++ b/app/Http/Requests/StoreHariLiburRequest.php @@ -0,0 +1,26 @@ +|string> + */ + public function rules(): array + { + return [ + + ]; + } +} diff --git a/app/Http/Requests/StoreJabatanRequest.php b/app/Http/Requests/StoreJabatanRequest.php new file mode 100644 index 00000000..02e4cc77 --- /dev/null +++ b/app/Http/Requests/StoreJabatanRequest.php @@ -0,0 +1,20 @@ + ['required', 'string', 'max:255', 'unique:jabatan,nama_jabatan'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/StoreKantorRequest.php b/app/Http/Requests/StoreKantorRequest.php new file mode 100644 index 00000000..4a486acd --- /dev/null +++ b/app/Http/Requests/StoreKantorRequest.php @@ -0,0 +1,25 @@ + ['required', 'string', 'max:255'], + 'alamat' => ['nullable', 'string'], + + 'latitude' => ['required', 'numeric', 'between:-90,90'], + 'longitude' => ['required', 'numeric', 'between:-180,180'], + 'radius' => ['required', 'integer', 'min:10'], + ]; + } +} diff --git a/app/Http/Requests/StoreLemburRequest.php b/app/Http/Requests/StoreLemburRequest.php new file mode 100644 index 00000000..0b43c37d --- /dev/null +++ b/app/Http/Requests/StoreLemburRequest.php @@ -0,0 +1,57 @@ +|string> + */ + public function rules(): array + { + return [ + 'tanggal_lembur' => 'required|date', + 'jam_mulai' => 'required|date_format:H:i', + 'jam_selesai' => 'required|date_format:H:i|after:jam_mulai', + 'keterangan' => 'nullable|string|max:255', + 'id_kompensasi' => 'nullable|exists:jenis_kompensasi,id_kompensasi', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return [ + 'tanggal_lembur' => 'tanggal lembur', + 'jam_mulai' => 'jam mulai', + 'jam_selesai' => 'jam selesai', + 'id_kompensasi' => 'jenis kompensasi', + ]; + } + + /** + * Get custom messages for validator errors. + * + * @return array + */ + public function messages(): array + { + return [ + 'jam_selesai.after' => 'Jam selesai harus lebih dari jam mulai.', + ]; + } +} diff --git a/app/Http/Requests/StoreManualPresensiRequest.php b/app/Http/Requests/StoreManualPresensiRequest.php new file mode 100644 index 00000000..ba2e7b6b --- /dev/null +++ b/app/Http/Requests/StoreManualPresensiRequest.php @@ -0,0 +1,48 @@ +user()->roles->contains(function ($role) { + return in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']); + }); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'id_user' => 'required|exists:users,id', + 'tanggal' => 'required|date', + 'id_status' => 'required|exists:status_presensi,id_status', + 'jam_masuk' => [ + Rule::requiredIf(fn () => in_array($this->id_status, [1, 2])), + 'nullable', + 'date_format:H:i' + ], + 'jam_pulang' => 'nullable|date_format:H:i', + 'alasan_telat' => 'nullable|string|max:255' + ]; + } + + public function messages(): array + { + return [ + 'id_user.required' => 'Pegawai harus dipilih.', + 'id_status.required' => 'Status kehadiran harus dipilih.', + 'jam_masuk.required' => 'Jam masuk wajib diisi untuk status Hadir/Terlambat.', + ]; + } +} diff --git a/app/Http/Requests/StorePegawaiRequest.php b/app/Http/Requests/StorePegawaiRequest.php new file mode 100644 index 00000000..056fd57e --- /dev/null +++ b/app/Http/Requests/StorePegawaiRequest.php @@ -0,0 +1,41 @@ + ['required', 'numeric', 'unique:users,nik'], + 'nama_lengkap' => ['required', 'string', 'max:255'], + 'email' => ['required', 'email', 'unique:users,email'], + 'no_telp' => ['nullable', 'string', 'max:20'], + 'alamat' => ['nullable', 'string'], + 'id_divisi' => ['required', 'exists:divisi,id_divisi'], + 'id_jabatan' => ['required', 'exists:jabatan,id_jabatan'], + 'id_kantor' => ['required', 'exists:kantor,id_kantor'], + 'tgl_bergabung' => ['required', 'date'], + 'foto' => ['nullable', 'image', 'max:5120'], + 'sisa_cuti' => ['nullable', 'integer', 'min:0'], + 'id_role' => ['required', 'exists:roles,id_role'], + ]; + } + + public function messages(): array + { + return [ + 'nik.unique' => 'NIK sudah terdaftar di sistem.', + 'email.unique' => 'Email ini sudah digunakan oleh pegawai lain.', + 'id_divisi.exists' => 'Divisi yang dipilih tidak valid.', + 'id_role.exists' => 'Role yang dipilih tidak valid.', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/StorePengajuanIzinRequest.php b/app/Http/Requests/StorePengajuanIzinRequest.php new file mode 100644 index 00000000..dc8acf0c --- /dev/null +++ b/app/Http/Requests/StorePengajuanIzinRequest.php @@ -0,0 +1,59 @@ + 'required|date', + 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', + 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', + 'alasan' => 'required|string|max:150', + 'bukti_file' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:2048', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return [ + 'tanggal_mulai' => 'tanggal mulai', + 'tanggal_selesai' => 'tanggal selesai', + 'id_jenis_izin' => 'jenis izin', + 'bukti_file' => 'bukti file', + ]; + } + + /** + * Get custom messages for validator errors. + * + * @return array + */ + public function messages(): array + { + return [ + 'tanggal_selesai.after_or_equal' => 'Tanggal selesai harus sama atau setelah tanggal mulai.', + 'bukti_file.max' => 'Ukuran file maksimal 2MB.', + 'bukti_file.mimes' => 'Format file harus PDF, JPG, JPEG, atau PNG.', + ]; + } +} diff --git a/app/Http/Requests/StorePengumumanRequest.php b/app/Http/Requests/StorePengumumanRequest.php new file mode 100644 index 00000000..6c02bc98 --- /dev/null +++ b/app/Http/Requests/StorePengumumanRequest.php @@ -0,0 +1,32 @@ + 'required|string|max:80', + 'isi' => 'required|string|max:500', + 'tanggal' => 'required|date', + ]; + } + + public function messages(): array + { + return [ + 'judul.required' => 'Judul pengumuman wajib diisi.', + 'judul.max' => 'Judul maksimal 80 karakter.', + 'isi.required' => 'Isi pengumuman wajib diisi.', + 'tanggal.required' => 'Tanggal tayang wajib diisi.', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/StorePresensiRequest.php b/app/Http/Requests/StorePresensiRequest.php new file mode 100644 index 00000000..7fc0d277 --- /dev/null +++ b/app/Http/Requests/StorePresensiRequest.php @@ -0,0 +1,36 @@ + 'required|numeric', + 'longitude' => 'required|numeric', + 'foto' => 'required|file|mimes:jpg,jpeg,png|max:5120', + 'status' => 'required|in:masuk,pulang', + 'keterangan_luar_radius' => 'nullable|string|max:500', + ]; + } + + public function messages(): array + { + return [ + 'foto.required' => 'Foto wajah wajib diambil.', + 'foto.file' => 'Format foto tidak valid.', + 'foto.mimes' => 'Foto harus berformat JPG atau PNG.', + 'foto.max' => 'Ukuran foto maksimal 5MB.', + 'status.required' => 'Status presensi wajib diisi.', + 'status.in' => 'Status presensi harus masuk atau pulang.', + ]; + } +} diff --git a/app/Http/Requests/StoreRoleRequest.php b/app/Http/Requests/StoreRoleRequest.php new file mode 100644 index 00000000..8b6b1019 --- /dev/null +++ b/app/Http/Requests/StoreRoleRequest.php @@ -0,0 +1,30 @@ + ['required', 'string', 'max:50', 'unique:roles,nama_role'], + 'id_permissions' => ['nullable', 'array'], + 'id_permissions.*' => ['exists:permissions,id_permission'], + ]; + } + + public function messages(): array + { + return [ + 'nama_role.required' => 'Nama role wajib diisi.', + 'nama_role.unique' => 'Nama role ini sudah ada.', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/StoreShiftRequest.php b/app/Http/Requests/StoreShiftRequest.php new file mode 100644 index 00000000..4714506c --- /dev/null +++ b/app/Http/Requests/StoreShiftRequest.php @@ -0,0 +1,33 @@ + ['required', 'string', 'max:50', 'unique:shift_kerja,nama_shift'], + 'jam_mulai' => ['required', 'date_format:H:i'], + 'jam_selesai' => ['required', 'date_format:H:i', 'after:jam_mulai'], + ]; + } + + public function messages(): array + { + return [ + 'nama_shift.required' => 'Nama shift wajib diisi.', + 'nama_shift.unique' => 'Nama shift ini sudah ada.', + 'jam_mulai.required' => 'Jam mulai wajib diisi.', + 'jam_selesai.required' => 'Jam selesai wajib diisi.', + 'jam_selesai.after' => 'Jam selesai harus setelah jam mulai.', + ]; + } +} diff --git a/app/Http/Requests/StoreSignatureRequest.php b/app/Http/Requests/StoreSignatureRequest.php new file mode 100644 index 00000000..38739354 --- /dev/null +++ b/app/Http/Requests/StoreSignatureRequest.php @@ -0,0 +1,27 @@ + 'required|string', + ]; + } + + public function messages(): array + { + return [ + 'signature_data.required' => 'Data tanda tangan wajib diisi.', + ]; + } +} diff --git a/app/Http/Requests/StoreSuratIzinRequest.php b/app/Http/Requests/StoreSuratIzinRequest.php new file mode 100644 index 00000000..39c5907e --- /dev/null +++ b/app/Http/Requests/StoreSuratIzinRequest.php @@ -0,0 +1,30 @@ + 'required|string|exists:pengajuan_izin,id_izin', + 'isi_surat' => 'required|string', + ]; + } + + public function messages(): array + { + return [ + 'id_izin.required' => 'ID pengajuan izin wajib diisi.', + 'id_izin.exists' => 'Pengajuan izin tidak ditemukan.', + 'isi_surat.required' => 'Isi surat wajib diisi.', + ]; + } +} diff --git a/app/Http/Requests/StoreTukarShiftRequest.php b/app/Http/Requests/StoreTukarShiftRequest.php new file mode 100644 index 00000000..2bbb69fb --- /dev/null +++ b/app/Http/Requests/StoreTukarShiftRequest.php @@ -0,0 +1,47 @@ +|string> + */ + public function rules(): array + { + return [ + 'id_user_1' => 'required|exists:users,id', + 'id_jadwal_1' => 'required|exists:jadwal_kerja,id_jadwal', + 'id_user_2' => 'required|exists:users,id|different:id_user_1', + 'id_jadwal_2' => 'required|exists:jadwal_kerja,id_jadwal|different:id_jadwal_1', + 'keterangan' => 'nullable|string|max:1000' + ]; + } + + public function messages(): array + { + return [ + 'id_user_1.required' => 'Pegawai pertama harus dipilih.', + 'id_user_1.exists' => 'Pegawai pertama tidak valid.', + 'id_jadwal_1.required' => 'Jadwal pegawai pertama harus dipilih.', + 'id_jadwal_1.exists' => 'Jadwal pegawai pertama tidak valid.', + 'id_user_2.required' => 'Pegawai kedua harus dipilih.', + 'id_user_2.exists' => 'Pegawai kedua tidak valid.', + 'id_user_2.different' => 'Pegawai kedua tidak boleh sama dengan pegawai pertama.', + 'id_jadwal_2.required' => 'Jadwal pegawai kedua harus dipilih.', + 'id_jadwal_2.exists' => 'Jadwal pegawai kedua tidak valid.', + 'id_jadwal_2.different' => 'Jadwal pegawai kedua tidak boleh sama dengan jadwal pegawai pertama.', + ]; + } +} diff --git a/app/Http/Requests/UpdateDivisiRequest.php b/app/Http/Requests/UpdateDivisiRequest.php new file mode 100644 index 00000000..48d425f2 --- /dev/null +++ b/app/Http/Requests/UpdateDivisiRequest.php @@ -0,0 +1,24 @@ +route('divisi'); + + return [ + + 'nama_divisi' => ['required', 'string', 'max:255', 'unique:divisi,nama_divisi,'.$id.',id_divisi'], + ]; + } +} diff --git a/app/Http/Requests/UpdateJabatanRequest.php b/app/Http/Requests/UpdateJabatanRequest.php new file mode 100644 index 00000000..30d34bb2 --- /dev/null +++ b/app/Http/Requests/UpdateJabatanRequest.php @@ -0,0 +1,22 @@ +route('jabatan'); + + return [ + 'nama_jabatan' => ['required', 'string', 'max:255', 'unique:jabatan,nama_jabatan,'.$id.',id_jabatan'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/UpdateKantorRequest.php b/app/Http/Requests/UpdateKantorRequest.php new file mode 100644 index 00000000..f34f1b77 --- /dev/null +++ b/app/Http/Requests/UpdateKantorRequest.php @@ -0,0 +1,25 @@ + ['required', 'string', 'max:255'], + 'alamat' => ['nullable', 'string'], + + 'latitude' => ['required', 'numeric', 'between:-90,90'], + 'longitude' => ['required', 'numeric', 'between:-180,180'], + 'radius' => ['required', 'integer', 'min:10'], + ]; + } +} diff --git a/app/Http/Requests/UpdateLemburRequest.php b/app/Http/Requests/UpdateLemburRequest.php new file mode 100644 index 00000000..4e104986 --- /dev/null +++ b/app/Http/Requests/UpdateLemburRequest.php @@ -0,0 +1,57 @@ +|string> + */ + public function rules(): array + { + return [ + 'tanggal_lembur' => 'required|date', + 'jam_mulai' => 'required|date_format:H:i', + 'jam_selesai' => 'required|date_format:H:i|after:jam_mulai', + 'keterangan' => 'nullable|string|max:255', + 'id_kompensasi' => 'nullable|exists:jenis_kompensasi,id_kompensasi', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return [ + 'tanggal_lembur' => 'tanggal lembur', + 'jam_mulai' => 'jam mulai', + 'jam_selesai' => 'jam selesai', + 'id_kompensasi' => 'jenis kompensasi', + ]; + } + + /** + * Get custom messages for validator errors. + * + * @return array + */ + public function messages(): array + { + return [ + 'jam_selesai.after' => 'Jam selesai harus lebih dari jam mulai.', + ]; + } +} diff --git a/app/Http/Requests/UpdatePegawaiRequest.php b/app/Http/Requests/UpdatePegawaiRequest.php new file mode 100644 index 00000000..68b5fc05 --- /dev/null +++ b/app/Http/Requests/UpdatePegawaiRequest.php @@ -0,0 +1,38 @@ +route('pegawai'); + + return [ + 'nama_lengkap' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'email', + 'unique:users,email,' . $this->route('pegawai') + ], + 'no_telp' => ['nullable', 'string', 'max:20'], + 'alamat' => ['nullable', 'string'], + 'id_divisi' => ['required', 'exists:divisi,id_divisi'], + 'id_jabatan' => ['required', 'exists:jabatan,id_jabatan'], + 'id_kantor' => ['required', 'exists:kantor,id_kantor'], + 'status_aktif' => ['required', 'in:0,1'], + 'tgl_bergabung' => ['required', 'date'], + 'foto' => ['nullable', 'image', 'max:5120'], + 'sisa_cuti' => ['nullable', 'integer', 'min:0'], + 'id_role' => ['required', 'exists:roles,id_role'], + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/UpdatePengajuanIzinRequest.php b/app/Http/Requests/UpdatePengajuanIzinRequest.php new file mode 100644 index 00000000..5accbd27 --- /dev/null +++ b/app/Http/Requests/UpdatePengajuanIzinRequest.php @@ -0,0 +1,59 @@ + 'required|date', + 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', + 'id_jenis_izin' => 'required|exists:jenis_izin,id_jenis_izin', + 'alasan' => 'required|string|max:150', + 'bukti_file' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:2048', + ]; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes() + { + return [ + 'tanggal_mulai' => 'tanggal mulai', + 'tanggal_selesai' => 'tanggal selesai', + 'id_jenis_izin' => 'jenis izin', + 'bukti_file' => 'bukti file', + ]; + } + + /** + * Get custom messages for validator errors. + * + * @return array + */ + public function messages(): array + { + return [ + 'tanggal_selesai.after_or_equal' => 'Tanggal selesai harus sama atau setelah tanggal mulai.', + 'bukti_file.max' => 'Ukuran file maksimal 2MB.', + 'bukti_file.mimes' => 'Format file harus PDF, JPG, JPEG, atau PNG.', + ]; + } +} diff --git a/app/Http/Requests/UpdatePengumumanRequest.php b/app/Http/Requests/UpdatePengumumanRequest.php new file mode 100644 index 00000000..6a1f50e0 --- /dev/null +++ b/app/Http/Requests/UpdatePengumumanRequest.php @@ -0,0 +1,32 @@ + 'required|string|max:80', + 'isi' => 'required|string|max:500', + 'tanggal' => 'required|date', + ]; + } + + public function messages(): array + { + return [ + 'judul.required' => 'Judul pengumuman wajib diisi.', + 'judul.max' => 'Judul maksimal 80 karakter.', + 'isi.required' => 'Isi pengumuman wajib diisi.', + 'tanggal.required' => 'Tanggal tayang wajib diisi.', + ]; + } +} \ No newline at end of file diff --git a/app/Http/Requests/UpdateRoleRequest.php b/app/Http/Requests/UpdateRoleRequest.php new file mode 100644 index 00000000..d3029bc4 --- /dev/null +++ b/app/Http/Requests/UpdateRoleRequest.php @@ -0,0 +1,23 @@ + ['required', 'string', 'max:50', 'unique:roles,nama_role,' . $this->route('role') . ',id_role'], + 'id_permissions' => ['nullable', 'array'], + 'id_permissions.*' => ['exists:permissions,id_permission'], + ]; + } +} diff --git a/app/Http/Requests/UpdateShiftRequest.php b/app/Http/Requests/UpdateShiftRequest.php new file mode 100644 index 00000000..d148e2a0 --- /dev/null +++ b/app/Http/Requests/UpdateShiftRequest.php @@ -0,0 +1,33 @@ + ['required', 'string', 'max:50', 'unique:shift_kerja,nama_shift,' . $this->route('shift') . ',id_shift'], + 'jam_mulai' => ['required', 'date_format:H:i'], + 'jam_selesai' => ['required', 'date_format:H:i', 'after:jam_mulai'], + ]; + } + + public function messages(): array + { + return [ + 'nama_shift.required' => 'Nama shift wajib diisi.', + 'nama_shift.unique' => 'Nama shift ini sudah ada.', + 'jam_mulai.required' => 'Jam mulai wajib diisi.', + 'jam_selesai.required' => 'Jam selesai wajib diisi.', + 'jam_selesai.after' => 'Jam selesai harus setelah jam mulai.', + ]; + } +} diff --git a/app/Models/ApprovalSurat.php b/app/Models/ApprovalSurat.php new file mode 100644 index 00000000..4c4192a0 --- /dev/null +++ b/app/Models/ApprovalSurat.php @@ -0,0 +1,75 @@ +|ApprovalSurat newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat query() + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereCatatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdApproval($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdApprover($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereIdTtdApprover($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereTahap($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApprovalSurat whereUpdatedAt($value) + * @mixin \Eloquent + */ +class ApprovalSurat extends Model +{ + protected $table = 'approval_surat'; + protected $primaryKey = 'id_approval'; + public $incrementing = false; + protected $keyType = 'string'; + + protected $fillable = [ + 'id_surat', + 'id_approver', + 'id_ttd_approver', + 'tahap', + 'status', + 'catatan', + ]; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'APR-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(Str::random(5)); + } + }); + } + + public function suratIzin() + { + return $this->belongsTo(SuratIzin::class, 'id_surat', 'id_surat'); + } + + public function approver() + { + return $this->belongsTo(User::class, 'id_approver'); + } + + public function tandaTanganApprover() + { + return $this->belongsTo(TandaTangan::class, 'id_ttd_approver', 'id_tanda_tangan'); + } +} diff --git a/app/Models/DataWajah.php b/app/Models/DataWajah.php new file mode 100644 index 00000000..69241a87 --- /dev/null +++ b/app/Models/DataWajah.php @@ -0,0 +1,39 @@ +|DataWajah newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah query() + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereEncodingWajah($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereIsVerified($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereLastUpdated($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah wherePathModelYml($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereTanggalLatih($value) + * @method static \Illuminate\Database\Eloquent\Builder|DataWajah whereUpdatedAt($value) + * @mixin \Eloquent + */ +class DataWajah extends Model +{ + protected $table = 'data_wajah'; + protected $guarded = []; + + public function user() + { + return $this->belongsTo(User::class, 'id_user', 'id'); + } +} \ No newline at end of file diff --git a/app/Models/DetailPenggunaanPoin.php b/app/Models/DetailPenggunaanPoin.php new file mode 100644 index 00000000..616d90b8 --- /dev/null +++ b/app/Models/DetailPenggunaanPoin.php @@ -0,0 +1,50 @@ +|DetailPenggunaanPoin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin query() + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereIdPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereIdPoinSumber($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereJumlahDiambil($value) + * @method static \Illuminate\Database\Eloquent\Builder|DetailPenggunaanPoin whereUpdatedAt($value) + * @mixin \Eloquent + */ +class DetailPenggunaanPoin extends Model +{ + use HasFactory; + + protected $table = 'detail_penggunaan_poin'; + protected $primaryKey = 'id'; + + protected $fillable = [ + 'id_penggunaan', + 'id_poin_sumber', + 'jumlah_diambil' + ]; + + public function penggunaan() + { + return $this->belongsTo(PenggunaanPoin::class, 'id_penggunaan', 'id_penggunaan'); + } + + public function poinSumber() + { + return $this->belongsTo(PoinLembur::class, 'id_poin_sumber', 'id_poin'); + } +} diff --git a/app/Models/Divisi.php b/app/Models/Divisi.php new file mode 100644 index 00000000..bcae5ba8 --- /dev/null +++ b/app/Models/Divisi.php @@ -0,0 +1,32 @@ +|Divisi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi query() + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereIdDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereNamaDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Divisi whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Divisi extends Model +{ + use HasFactory; + + protected $table = 'divisi'; + protected $primaryKey = 'id_divisi'; + + protected $fillable = [ + 'nama_divisi' + ]; +} \ No newline at end of file diff --git a/app/Models/HariLibur.php b/app/Models/HariLibur.php new file mode 100644 index 00000000..d43d2ca2 --- /dev/null +++ b/app/Models/HariLibur.php @@ -0,0 +1,25 @@ +belongsTo(Kantor::class, 'id_kantor'); + } +} diff --git a/app/Models/Jabatan.php b/app/Models/Jabatan.php new file mode 100644 index 00000000..478b5da6 --- /dev/null +++ b/app/Models/Jabatan.php @@ -0,0 +1,32 @@ +|Jabatan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan query() + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereIdJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereNamaJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Jabatan whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Jabatan extends Model +{ + use HasFactory; + + protected $table = 'jabatan'; + protected $primaryKey = 'id_jabatan'; + + protected $fillable = [ + 'nama_jabatan' + ]; +} \ No newline at end of file diff --git a/app/Models/JadwalKerja.php b/app/Models/JadwalKerja.php new file mode 100644 index 00000000..d23046bc --- /dev/null +++ b/app/Models/JadwalKerja.php @@ -0,0 +1,60 @@ +|JadwalKerja newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja query() + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdJadwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|JadwalKerja whereUpdatedAt($value) + * @mixin \Eloquent + */ +class JadwalKerja extends Model +{ + use HasFactory; + + protected $table = 'jadwal_kerja'; + protected $primaryKey = 'id_jadwal'; + + protected $fillable = [ + 'id_user', + 'id_shift', + 'tanggal', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function shift() + { + return $this->belongsTo(ShiftKerja::class, 'id_shift'); + } + + public function penggunaanPoin() + { + return $this->hasOne(PenggunaanPoin::class, 'id_user', 'id_user') + ->whereColumn('tanggal_penggunaan', 'tanggal') + ->where('id_status', 2); + } + +} diff --git a/app/Models/JenisIzin.php b/app/Models/JenisIzin.php new file mode 100644 index 00000000..b6e7d477 --- /dev/null +++ b/app/Models/JenisIzin.php @@ -0,0 +1,28 @@ +|JenisIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereIdJenisIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereNamaIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ +class JenisIzin extends Model +{ + protected $table = 'jenis_izin'; + protected $primaryKey = 'id_jenis_izin'; + public $timestamps = false; + + protected $fillable = ['id_jenis_izin', 'nama_izin']; +} diff --git a/app/Models/JenisKompensasi.php b/app/Models/JenisKompensasi.php new file mode 100644 index 00000000..4dd333ef --- /dev/null +++ b/app/Models/JenisKompensasi.php @@ -0,0 +1,28 @@ +|JenisKompensasi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereIdKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereNamaKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisKompensasi whereUpdatedAt($value) + * @mixin \Eloquent + */ +class JenisKompensasi extends Model +{ + protected $table = 'jenis_kompensasi'; + protected $primaryKey = 'id_kompensasi'; + + const UANG = 1; + const POIN = 2; +} diff --git a/app/Models/JenisPengurangan.php b/app/Models/JenisPengurangan.php new file mode 100644 index 00000000..2bfbb5dd --- /dev/null +++ b/app/Models/JenisPengurangan.php @@ -0,0 +1,39 @@ + $penggunaanPoin + * @property-read int|null $penggunaan_poin_count + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan query() + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereIdPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereNamaPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|JenisPengurangan whereUpdatedAt($value) + * @mixin \Eloquent + */ +class JenisPengurangan extends Model +{ + use HasFactory; + + protected $table = 'jenis_pengurangan'; + protected $primaryKey = 'id_pengurangan'; + + protected $fillable = [ + 'nama_pengurangan' + ]; + + public function penggunaanPoin() + { + return $this->hasMany(PenggunaanPoin::class, 'id_pengurangan'); + } +} diff --git a/app/Models/Kantor.php b/app/Models/Kantor.php new file mode 100644 index 00000000..dd074ba1 --- /dev/null +++ b/app/Models/Kantor.php @@ -0,0 +1,53 @@ + $users + * @property-read int|null $users_count + * @method static \Illuminate\Database\Eloquent\Builder|Kantor newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor query() + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereAlamat($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereIdKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereLatitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereLongitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereNamaKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereRadius($value) + * @method static \Illuminate\Database\Eloquent\Builder|Kantor whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Kantor extends Model +{ + use HasFactory; + + protected $table = 'kantor'; + protected $primaryKey = 'id_kantor'; + + protected $fillable = [ + 'nama_kantor', + 'tipe', + 'alamat', + 'latitude', + 'longitude', + 'radius' + ]; + + public function users() + { + + return $this->hasMany(User::class, 'id_kantor'); + } +} diff --git a/app/Models/Lembur.php b/app/Models/Lembur.php new file mode 100644 index 00000000..b3e758a2 --- /dev/null +++ b/app/Models/Lembur.php @@ -0,0 +1,97 @@ +|Lembur newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur query() + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereDurasiMenit($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdKompensasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJamMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJamSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereTanggalLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|Lembur whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Lembur extends Model +{ + use HasFactory; + + protected $table = 'lembur'; + protected $primaryKey = 'id_lembur'; + public $incrementing = false; + protected $keyType = 'string'; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'LMB-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(\Illuminate\Support\Str::random(5)); + } + }); + } + + protected $fillable = [ + 'id_user', + 'tanggal_lembur', + 'jam_mulai', + 'jam_selesai', + 'durasi_menit', + 'keterangan', + 'jumlah_poin', + 'id_kompensasi', + 'id_status', + 'alasan_penolakan', + 'tanggal_diajukan' + ]; + + protected $casts = [ + 'tanggal_lembur' => 'date', + 'tanggal_diajukan' => 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function kompensasi() + { + return $this->belongsTo(JenisKompensasi::class, 'id_kompensasi'); + } + + public function status() + { + return $this->belongsTo(StatusPengajuan::class, 'id_status'); + } +} \ No newline at end of file diff --git a/app/Models/PengajuanIzin.php b/app/Models/PengajuanIzin.php new file mode 100644 index 00000000..c8295817 --- /dev/null +++ b/app/Models/PengajuanIzin.php @@ -0,0 +1,90 @@ +|PengajuanIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereAlasan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereBuktiFile($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdJenisIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereTanggalSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|PengajuanIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ +class PengajuanIzin extends Model +{ + use HasFactory; + + protected $table = 'pengajuan_izin'; + protected $primaryKey = 'id_izin'; + public $incrementing = false; + protected $keyType = 'string'; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'IZN-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(\Illuminate\Support\Str::random(5)); + } + }); + } + + protected $fillable = [ + 'id_user', + 'id_jenis_izin', + 'id_status', + 'tanggal_mulai', + 'tanggal_selesai', + 'alasan', + 'bukti_file', + + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function jenisIzin() + { + return $this->belongsTo(JenisIzin::class, 'id_jenis_izin', 'id_jenis_izin'); + } + + public function statusPengajuan() + { + return $this->belongsTo(StatusPengajuan::class, 'id_status', 'id_status'); + } + + public function suratIzin() + { + return $this->hasOne(SuratIzin::class, 'id_izin', 'id_izin'); + } +} diff --git a/app/Models/PenggunaanPoin.php b/app/Models/PenggunaanPoin.php new file mode 100644 index 00000000..116d8e2e --- /dev/null +++ b/app/Models/PenggunaanPoin.php @@ -0,0 +1,91 @@ +|PenggunaanPoin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin query() + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdPengurangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJamMasukCustom($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJamPulangCustom($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereTanggalDiajukan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereTanggalPenggunaan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PenggunaanPoin whereUpdatedAt($value) + * @mixin \Eloquent + */ +class PenggunaanPoin extends Model +{ + use HasFactory; + + protected $table = 'penggunaan_poin'; + protected $primaryKey = 'id_penggunaan'; + public $incrementing = false; + protected $keyType = 'string'; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'PNP-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(\Illuminate\Support\Str::random(5)); + } + }); + } + + protected $fillable = [ + 'id_user', + 'tanggal_penggunaan', + 'jumlah_poin', + 'id_pengurangan', + 'jam_masuk_custom', + 'jam_pulang_custom', + 'id_status', + 'alasan_penolakan', + 'tanggal_diajukan' + ]; + + protected $casts = [ + 'tanggal_penggunaan' => 'date', + 'tanggal_diajukan' => 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function jenisPengurangan() + { + return $this->belongsTo(JenisPengurangan::class, 'id_pengurangan'); + } + + public function status() + { + return $this->belongsTo(StatusPengajuan::class, 'id_status', 'id_status'); + } +} diff --git a/app/Models/Pengumuman.php b/app/Models/Pengumuman.php new file mode 100644 index 00000000..5f3ae525 --- /dev/null +++ b/app/Models/Pengumuman.php @@ -0,0 +1,51 @@ +|Pengumuman newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman query() + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereDibuatOleh($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereIdPengumuman($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereIsi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereJudul($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Pengumuman whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Pengumuman extends Model +{ + use HasFactory; + + protected $table = 'pengumuman'; + protected $primaryKey = 'id_pengumuman'; + + protected $fillable = [ + 'judul', + 'isi', + 'tanggal', + 'dibuat_oleh' + ]; + + protected $casts = [ + 'tanggal' => 'date', + ]; + + public function pembuat() + { + return $this->belongsTo(User::class, 'dibuat_oleh', 'id'); + } +} \ No newline at end of file diff --git a/app/Models/Permission.php b/app/Models/Permission.php new file mode 100644 index 00000000..df611524 --- /dev/null +++ b/app/Models/Permission.php @@ -0,0 +1,36 @@ + $roles + * @property-read int|null $roles_count + * @method static \Illuminate\Database\Eloquent\Builder|Permission newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Permission newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Permission query() + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereIdPermission($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereNamaPermission($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereSlug($value) + * @method static \Illuminate\Database\Eloquent\Builder|Permission whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Permission extends Model +{ + protected $table = 'permissions'; + protected $primaryKey = 'id_permission'; + protected $fillable = ['nama_permission', 'slug']; + + public function roles(): BelongsToMany + { + return $this->belongsToMany(Role::class, 'detail_role_permissions', 'id_permission', 'id_role'); + } +} \ No newline at end of file diff --git a/app/Models/Poin.php b/app/Models/Poin.php new file mode 100644 index 00000000..a349f87d --- /dev/null +++ b/app/Models/Poin.php @@ -0,0 +1,30 @@ +|Poin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Poin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Poin query() + * @mixin \Eloquent + */ +class Poin extends Model +{ + protected $table = 'poin'; + protected $primaryKey = 'id_poin'; + + protected $fillable = [ + 'id_user', + 'jumlah_poin', + 'sumber', + 'tgl_kadaluarsa', + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user', 'id'); + } +} diff --git a/app/Models/PoinLembur.php b/app/Models/PoinLembur.php new file mode 100644 index 00000000..7a8498e6 --- /dev/null +++ b/app/Models/PoinLembur.php @@ -0,0 +1,65 @@ +|PoinLembur newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur query() + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereExpiredAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdLembur($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereIsFullyUsed($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereJumlahPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereSisaPoin($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|PoinLembur whereUpdatedAt($value) + * @mixin \Eloquent + */ +class PoinLembur extends Model +{ + use HasFactory; + + protected $table = 'poin_lembur'; + protected $primaryKey = 'id_poin'; + + protected $fillable = [ + 'id_user', + 'id_lembur', + 'jumlah_poin', + 'tanggal', + 'keterangan', + 'sisa_poin', + 'expired_at', + 'is_fully_used' + ]; + + protected $casts = [ + 'tanggal' => 'datetime', + 'expired_at' => 'date', + 'is_fully_used' => 'boolean', + ]; + + public function lembur() + { + return $this->belongsTo(Lembur::class, 'id_lembur'); + } +} \ No newline at end of file diff --git a/app/Models/Presensi.php b/app/Models/Presensi.php new file mode 100644 index 00000000..bbe600ac --- /dev/null +++ b/app/Models/Presensi.php @@ -0,0 +1,97 @@ +|Presensi newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi query() + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereAlasanPenolakan($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereAlasanTelat($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereFotoWajahMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereFotoWajahPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdPresensi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereIdValidasi($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereJamMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereJamPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereKeteranganLuarRadius($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereKeteranganPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLatMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLatPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLonMasuk($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereLonPulang($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereTanggal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereVerifikasiWajah($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuMasukAwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuPulangAkhir($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuPulangAwal($value) + * @method static \Illuminate\Database\Eloquent\Builder|Presensi whereWaktuTerlambat($value) + * @mixin \Eloquent + */ +class Presensi extends Model +{ + + protected $table = 'presensi'; + protected $primaryKey = 'id_presensi'; + + protected $fillable = [ + 'id_user', + 'tanggal', + 'jam_masuk', + 'jam_pulang', + 'lat_masuk', + 'lon_masuk', + 'lat_pulang', + 'lon_pulang', + 'foto_wajah_masuk', + 'foto_wajah_pulang', + 'id_status', + 'alasan_telat', + 'verifikasi_wajah', + 'id_validasi', + 'alasan_penolakan', + 'keterangan_luar_radius', + 'keterangan_pulang', + 'waktu_terlambat', + 'waktu_masuk_awal', + 'waktu_pulang_awal', + 'waktu_pulang_akhir' + ]; + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } +} \ No newline at end of file diff --git a/app/Models/RiwayatTukarShift.php b/app/Models/RiwayatTukarShift.php new file mode 100644 index 00000000..a4f29570 --- /dev/null +++ b/app/Models/RiwayatTukarShift.php @@ -0,0 +1,74 @@ +|RiwayatTukarShift newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift query() + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereCreatedBy($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdJadwal1($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdJadwal2($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdRiwayat($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdUser1($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereIdUser2($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereKeterangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|RiwayatTukarShift whereUpdatedAt($value) + * @mixin \Eloquent + */ +class RiwayatTukarShift extends Model +{ + protected $table = 'riwayat_tukar_shift'; + protected $primaryKey = 'id_riwayat'; + + protected $fillable = [ + 'id_user_1', + 'id_jadwal_1', + 'id_user_2', + 'id_jadwal_2', + 'keterangan', + 'created_by', + ]; + + public function user1() + { + return $this->belongsTo(User::class, 'id_user_1', 'id'); + } + + public function jadwal1() + { + return $this->belongsTo(JadwalKerja::class, 'id_jadwal_1', 'id_jadwal'); + } + + public function user2() + { + return $this->belongsTo(User::class, 'id_user_2', 'id'); + } + + public function jadwal2() + { + return $this->belongsTo(JadwalKerja::class, 'id_jadwal_2', 'id_jadwal'); + } + + public function execAdmin() + { + return $this->belongsTo(User::class, 'created_by', 'id'); + } +} diff --git a/app/Models/Role.php b/app/Models/Role.php new file mode 100644 index 00000000..67c6152c --- /dev/null +++ b/app/Models/Role.php @@ -0,0 +1,41 @@ + $permissions + * @property-read int|null $permissions_count + * @property-read \Illuminate\Database\Eloquent\Collection $users + * @property-read int|null $users_count + * @method static \Illuminate\Database\Eloquent\Builder|Role newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Role newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Role query() + * @method static \Illuminate\Database\Eloquent\Builder|Role whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereIdRole($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereNamaRole($value) + * @method static \Illuminate\Database\Eloquent\Builder|Role whereUpdatedAt($value) + * @mixin \Eloquent + */ +class Role extends Model +{ + protected $table = 'roles'; + protected $primaryKey = 'id_role'; + protected $fillable = ['nama_role']; + + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'detail_user_roles', 'id_role', 'id_user'); + } + + public function permissions(): BelongsToMany + { + return $this->belongsToMany(Permission::class, 'detail_role_permissions', 'id_role', 'id_permission'); + } +} \ No newline at end of file diff --git a/app/Models/ShiftKerja.php b/app/Models/ShiftKerja.php new file mode 100644 index 00000000..399ceae8 --- /dev/null +++ b/app/Models/ShiftKerja.php @@ -0,0 +1,46 @@ + $jadwalKerja + * @property-read int|null $jadwal_kerja_count + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja query() + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereIdShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereJamMulai($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereJamSelesai($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereNamaShift($value) + * @method static \Illuminate\Database\Eloquent\Builder|ShiftKerja whereUpdatedAt($value) + * @mixin \Eloquent + */ +class ShiftKerja extends Model +{ + use HasFactory; + + protected $table = 'shift_kerja'; + protected $primaryKey = 'id_shift'; + + protected $fillable = [ + 'nama_shift', + 'jam_mulai', + 'jam_selesai', + ]; + + public function jadwalKerja(): HasMany + { + return $this->hasMany(JadwalKerja::class, 'id_shift', 'id_shift'); + } +} \ No newline at end of file diff --git a/app/Models/StatusPengajuan.php b/app/Models/StatusPengajuan.php new file mode 100644 index 00000000..518b80d3 --- /dev/null +++ b/app/Models/StatusPengajuan.php @@ -0,0 +1,28 @@ +|StatusPengajuan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan query() + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereIdStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereNamaStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|StatusPengajuan whereUpdatedAt($value) + * @mixin \Eloquent + */ +class StatusPengajuan extends Model +{ + protected $table = 'status_pengajuan'; + protected $primaryKey = 'id_status'; + public $timestamps = false; + + protected $fillable = ['id_status', 'nama_status']; +} diff --git a/app/Models/SuratIzin.php b/app/Models/SuratIzin.php new file mode 100644 index 00000000..5e1157dd --- /dev/null +++ b/app/Models/SuratIzin.php @@ -0,0 +1,99 @@ + $approvals + * @property-read int|null $approvals_count + * @property-read \App\Models\PengajuanIzin $pengajuanIzin + * @property-read \App\Models\TandaTangan|null $tandaTanganPengaju + * @property-read \App\Models\User $user + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin query() + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdIzin($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdTtdPengaju($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereIsiSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereNomorSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereStatusSurat($value) + * @method static \Illuminate\Database\Eloquent\Builder|SuratIzin whereUpdatedAt($value) + * @mixin \Eloquent + */ +class SuratIzin extends Model +{ + protected $table = 'surat_izin'; + protected $primaryKey = 'id_surat'; + public $incrementing = false; + protected $keyType = 'string'; + + protected $fillable = [ + 'id_izin', + 'id_user', + 'nomor_surat', + 'isi_surat', + 'id_ttd_pengaju', + 'status_surat', + ]; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'SRT-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(Str::random(5)); + } + + if (empty($model->nomor_surat)) { + $count = static::whereYear('created_at', date('Y'))->count() + 1; + $model->nomor_surat = sprintf('%03d/MPG/IZN/%s', $count, date('Y')); + } + }); + } + + public function pengajuanIzin() + { + return $this->belongsTo(PengajuanIzin::class, 'id_izin', 'id_izin'); + } + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function tandaTanganPengaju() + { + return $this->belongsTo(TandaTangan::class, 'id_ttd_pengaju', 'id_tanda_tangan'); + } + + public function approvals() + { + return $this->hasMany(ApprovalSurat::class, 'id_surat', 'id_surat')->orderBy('tahap'); + } + + public function approvalManajer() + { + return $this->hasOne(ApprovalSurat::class, 'id_surat', 'id_surat')->where('tahap', 1); + } + + public function approvalHrd() + { + return $this->hasOne(ApprovalSurat::class, 'id_surat', 'id_surat')->where('tahap', 2); + } +} diff --git a/app/Models/TandaTangan.php b/app/Models/TandaTangan.php new file mode 100644 index 00000000..23c367b1 --- /dev/null +++ b/app/Models/TandaTangan.php @@ -0,0 +1,56 @@ +|TandaTangan active() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan query() + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereFileTtd($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIdTandaTangan($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIdUser($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereIsActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|TandaTangan whereUpdatedAt($value) + * @mixin \Eloquent + */ +class TandaTangan extends Model +{ + protected $table = 'tanda_tangan'; + protected $primaryKey = 'id_tanda_tangan'; + public $incrementing = false; + protected $keyType = 'string'; + + protected $fillable = ['id_user', 'file_ttd', 'is_active']; + + protected static function booted() + { + static::creating(function ($model) { + if (empty($model->{$model->getKeyName()})) { + $prefix = 'TTD-' . date('ym') . '-'; + $model->{$model->getKeyName()} = $prefix . strtoupper(Str::random(5)); + } + }); + } + + public function user() + { + return $this->belongsTo(User::class, 'id_user'); + } + + public function scopeActive($query) + { + return $query->where('is_active', true); + } +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 00000000..da3c1bb7 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,140 @@ + $jadwalKerja + * @property-read int|null $jadwal_kerja_count + * @property-read \App\Models\Kantor|null $kantor + * @property-read \Illuminate\Database\Eloquent\Collection $lemburs + * @property-read int|null $lemburs_count + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications + * @property-read int|null $notifications_count + * @property-read \Illuminate\Database\Eloquent\Collection $roles + * @property-read int|null $roles_count + * @property-read \Illuminate\Database\Eloquent\Collection $tokens + * @property-read int|null $tokens_count + * @method static \Database\Factories\UserFactory factory($count = null, $state = []) + * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|User onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|User query() + * @method static \Illuminate\Database\Eloquent\Builder|User whereAlamat($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereEmailVerifiedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereFoto($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdDivisi($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdJabatan($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIdKantor($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereIsFaceRegistered($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNamaLengkap($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNik($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereNoTelp($value) + * @method static \Illuminate\Database\Eloquent\Builder|User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereSisaCuti($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereStatusAktif($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereTglBergabung($value) + * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|User withTrashed(bool $withTrashed = true) + * @method static \Illuminate\Database\Eloquent\Builder|User withoutTrashed() + * @mixin \Eloquent + */ +class User extends Authenticatable +{ + use HasApiTokens, HasFactory, Notifiable, SoftDeletes; + + protected $fillable = [ + 'nik', + 'nama_lengkap', + 'email', + 'no_telp', + 'alamat', + 'password', + 'foto', + 'id_kantor', + 'id_divisi', + 'id_jabatan', + 'status_aktif', + 'tgl_bergabung', + 'is_face_registered', + 'sisa_cuti', + ]; + + protected $hidden = ['password', 'remember_token']; + + public function roles(): BelongsToMany + { + return $this->belongsToMany(Role::class, 'detail_user_roles', 'id_user', 'id_role'); + } + + public function hasPermission($permissionSlug): bool + { + return $this->roles()->whereHas('permissions', function ($query) use ($permissionSlug) { + $query->where('slug', $permissionSlug); + })->exists(); + } + + public function kantor(): BelongsTo + { + return $this->belongsTo(Kantor::class, 'id_kantor', 'id_kantor'); + } + + public function divisi(): BelongsTo + { + return $this->belongsTo(Divisi::class, 'id_divisi', 'id_divisi'); + } + + public function jabatan(): BelongsTo + { + return $this->belongsTo(Jabatan::class, 'id_jabatan', 'id_jabatan'); + } + + public function jadwalKerja(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(JadwalKerja::class, 'id_user', 'id'); + } + + public function dataWajah() + { + return $this->hasOne(DataWajah::class, 'id_user', 'id'); + } + + public function lemburs(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(Lembur::class, 'id_user', 'id'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 00000000..67ffd007 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,26 @@ +count(); + $view->with('pendingPresensi', $pendingPresensi); + }); + } +} diff --git a/app/Services/FaceRecognitionService.php b/app/Services/FaceRecognitionService.php new file mode 100644 index 00000000..74993303 --- /dev/null +++ b/app/Services/FaceRecognitionService.php @@ -0,0 +1,173 @@ +where('id', $userId)->first(); + if (!$user) { + throw new \Exception('User tidak ditemukan.', 404); + } + + $existing = DB::table('data_wajah')->where('id_user', $userId)->first(); + if ($existing && $existing->is_verified == 1) { + throw new \Exception('Wajah Anda sudah terverifikasi. Hubungi HRD untuk reset.', 400); + } + + try { + DB::beginTransaction(); + + $storagePath = "face_datasets/{$userId}"; + + if (Storage::disk('local')->exists($storagePath)) { + Storage::disk('local')->deleteDirectory($storagePath); + } + Storage::disk('local')->makeDirectory($storagePath); + + $savedPaths = []; + foreach ($photos as $pose => $file) { + if ($file instanceof UploadedFile) { + $fileName = "user_{$userId}_{$pose}." . $file->getClientOriginalExtension(); + $path = $file->storeAs($storagePath, $fileName, 'local'); + $savedPaths[$pose] = $path; + } + } + + DB::table('data_wajah')->updateOrInsert( + ['id_user' => $userId], + [ + 'path_model_yml' => null, + 'is_verified' => 0, + 'last_updated' => now(), + ] + ); + + DB::table('users') + ->where('id', $userId) + ->update(['is_face_registered' => 1]); + + try { + $pythonPath = env('PYTHON_PATH', 'python'); + $scriptPath = base_path('python_scripts/train_face.py'); + + $absDatasetPath = Storage::disk('local')->path($storagePath); + $modelStoragePath = storage_path("app/face_models"); + + $process = new Process([ + $pythonPath, + $scriptPath, + (string) $userId, + $absDatasetPath, + $modelStoragePath + ]); + + $process->setTimeout(120); + $process->run(); + + if (!$process->isSuccessful()) { + \Illuminate\Support\Facades\Log::warning("Face Training Failed (silently caught): " . $process->getErrorOutput()); + } else { + $output = json_decode($process->getOutput(), true); + if (!isset($output['status']) || $output['status'] !== 'success') { + \Illuminate\Support\Facades\Log::warning("Face Training Failed (silently caught): " . ($output['message'] ?? 'Unknown Error')); + } + } + } catch (\Exception $trainEx) { + \Illuminate\Support\Facades\Log::warning("Face Training Error (silently caught): " . $trainEx->getMessage()); + } + + $relativeModelPath = "face_models/user_{$userId}.yml"; + + DB::table('data_wajah')->where('id_user', $userId)->update([ + 'path_model_yml' => $relativeModelPath, + 'is_verified' => 0, + ]); + + DB::commit(); + + return [ + 'status' => true, + 'message' => 'Foto wajah berhasil didaftarkan dan model telah dilatih.', + 'dataset_path' => $storagePath + ]; + + } catch (\Exception $e) { + DB::rollBack(); + if (isset($storagePath)) { + Storage::disk('local')->deleteDirectory($storagePath); + } + throw $e; + } + } + + public function verifyFace($userId, UploadedFile $fotoMasuk) + { + $dataWajah = DB::table('data_wajah') + ->where('id_user', $userId) + ->where('is_verified', 1) + ->first(); + + if (!$dataWajah || empty($dataWajah->path_model_yml)) { + throw new \Exception('Model wajah belum tersedia atau belum diverifikasi.'); + } + + $tempFileName = "verify_{$userId}_" . time() . ".jpg"; + $tempDir = storage_path("app/temp_verify"); + if (!is_dir($tempDir)) { + mkdir($tempDir, 0755, true); + } + $absPathFoto = $tempDir . "/{$tempFileName}"; + $fotoMasuk->move($tempDir, $tempFileName); + + $absPathModel = storage_path("app/{$dataWajah->path_model_yml}"); + + if (!file_exists($absPathModel)) { + @unlink($absPathFoto); + throw new \Exception("File model .yml tidak ditemukan: {$dataWajah->path_model_yml}"); + } + + $pythonPath = env('PYTHON_PATH', 'python'); + $scriptPath = base_path('python_scripts/verify_face.py'); + + $process = new Process([ + $pythonPath, + $scriptPath, + $absPathModel, + $absPathFoto + ]); + + $process->setTimeout(60); + $process->run(); + + @unlink($absPathFoto); + + if (!$process->isSuccessful()) { + throw new \Exception("Python error: " . $process->getErrorOutput()); + } + + $output = json_decode($process->getOutput(), true); + + if (!isset($output['status'])) { + throw new \Exception("Invalid Python output: " . $process->getOutput()); + } + + if ($output['status'] === 'error') { + throw new \Exception("Error Verifikasi: " . $output['message']); + } + + return [ + 'verified' => $output['match'] === true, + 'confidence' => $output['confidence'] ?? null, + 'verification_status' => $output['verification_status'] ?? 'UNKNOWN', + 'message' => $output['message'] ?? null, + ]; + } +} diff --git a/app/Services/LemburService.php b/app/Services/LemburService.php new file mode 100644 index 00000000..d67c3353 --- /dev/null +++ b/app/Services/LemburService.php @@ -0,0 +1,80 @@ +diffInMinutes($end)); + } else { + $diff = $data['durasi_menit']; + } + + return Lembur::create([ + 'id_user' => $user->id, + 'tanggal_lembur' => $data['tanggal_lembur'], + 'jam_mulai' => $data['jam_mulai'], + 'jam_selesai' => $data['jam_selesai'], + 'durasi_menit' => $diff, + 'keterangan' => $data['keterangan'] ?? null, + 'id_status' => 1, + 'id_kompensasi' => $data['id_kompensasi'] ?? null, + 'tanggal_diajukan' => Carbon::now(), + 'jumlah_poin' => 0, + ]); + } + + public function approve(Lembur $lembur) + { + return DB::transaction(function () use ($lembur) { + $lembur->update([ + 'id_status' => StatusPengajuan::where('nama_status', 'Approved')->first()->id_status ?? 2 + ]); + + if ($lembur->id_kompensasi == JenisKompensasi::POIN) { + $this->generatePoin($lembur); + } + + return true; + }); + } + + public function reject(Lembur $lembur, string $alasan) + { + return $lembur->update([ + 'id_status' => StatusPengajuan::where('nama_status', 'Rejected')->first()->id_status ?? 3, + 'alasan_penolakan' => $alasan + ]); + } + + private function generatePoin(Lembur $lembur) + { + $poinDapat = floor(($lembur->durasi_menit + 15) / 30); + + if ($poinDapat > 0) { + PoinLembur::create([ + 'id_user' => $lembur->id_user, + 'jumlah_poin' => $poinDapat, + 'sisa_poin' => $poinDapat, + 'id_lembur' => $lembur->id_lembur, + 'keterangan' => 'Poin dari Lembur Tanggal ' . Carbon::parse($lembur->tanggal_lembur)->format('d/m/Y'), + 'tanggal' => now(), + 'expired_at' => now()->addMonths(6), + 'is_fully_used' => false + ]); + + $lembur->update(['jumlah_poin' => $poinDapat]); + } + } +} \ No newline at end of file diff --git a/app/Services/PoinService.php b/app/Services/PoinService.php new file mode 100644 index 00000000..133d8a6d --- /dev/null +++ b/app/Services/PoinService.php @@ -0,0 +1,103 @@ +where('is_fully_used', false) + ->where('expired_at', '>', now()) + ->orderBy('expired_at', 'asc') + ->lockForUpdate() + ->get(); + + $totalAvailable = $availablePoins->sum('sisa_poin'); + + if ($totalAvailable < $jumlahPoinDibutuhkan) { + throw new Exception("Saldo poin tidak mencukupi. Anda butuh $jumlahPoinDibutuhkan, saldo tersedia $totalAvailable."); + } + + $remainingNeeded = $jumlahPoinDibutuhkan; + + foreach ($availablePoins as $poin) { + if ($remainingNeeded <= 0) + break; + + $take = min($poin->sisa_poin, $remainingNeeded); + + $poin->sisa_poin -= $take; + if ($poin->sisa_poin == 0) { + $poin->is_fully_used = true; + } + $poin->save(); + + DetailPenggunaanPoin::create([ + 'id_penggunaan' => $idPenggunaan, + 'id_poin_sumber' => $poin->id_poin, + 'jumlah_diambil' => $take + ]); + + $remainingNeeded -= $take; + } + + $penggunaan = PenggunaanPoin::find($idPenggunaan); + if ($penggunaan) { + $penggunaan->id_status = 2; + $penggunaan->save(); + } + } + + public function refundPoin($idPenggunaan) + { + $details = DetailPenggunaanPoin::where('id_penggunaan', $idPenggunaan)->get(); + + foreach ($details as $detail) { + $poinSumber = PoinLembur::find($detail->id_poin_sumber); + if ($poinSumber) { + $poinSumber->sisa_poin += $detail->jumlah_diambil; + $poinSumber->is_fully_used = false; + $poinSumber->save(); + } + $detail->delete(); + } + + $penggunaan = PenggunaanPoin::find($idPenggunaan); + if ($penggunaan) { + $penggunaan->id_status = 3; + $penggunaan->save(); + } + } + + public function getActivePoints($userId) + { + return PoinLembur::where('id_user', $userId) + ->where('is_fully_used', false) + ->where('expired_at', '>', now()) + ->sum('sisa_poin'); + } + + public function getExpiringPoints($userId) + { + return PoinLembur::where('id_user', $userId) + ->where('is_fully_used', false) + ->whereBetween('expired_at', [now(), now()->addDays(30)]) + ->orderBy('expired_at', 'asc') + ->get(); + } + + public function getPointHistory($userId) + { + return PenggunaanPoin::with(['status', 'jenisPengurangan']) + ->where('id_user', $userId) + ->orderBy('tanggal_penggunaan', 'desc') + ->get(); + } +} \ No newline at end of file diff --git a/app/Services/PresensiService.php b/app/Services/PresensiService.php new file mode 100644 index 00000000..45170f32 --- /dev/null +++ b/app/Services/PresensiService.php @@ -0,0 +1,264 @@ +where('id_user', $userId) + ->where('tanggal', $date) + ->first(); + + if (!$jadwal) { + return null; + } + + $jamMasuk = $jadwal->shift->jam_mulai; + $jamPulang = $jadwal->shift->jam_selesai; + $status = 'Normal'; + $lokasi = $jadwal->kantor; + + $penggunaanPoin = PenggunaanPoin::where('id_user', $userId) + ->where('tanggal_penggunaan', $date) + ->where('id_status', 2) + ->first(); + + if ($penggunaanPoin) { + if ($penggunaanPoin->id_pengurangan == 4 && $penggunaanPoin->jam_masuk_custom) { + $jamMasuk = $penggunaanPoin->jam_masuk_custom; + if (strlen($jamMasuk) == 5) + $jamMasuk .= ':00'; + $status = 'Masuk Siang (Poin)'; + } + + if ($penggunaanPoin->id_pengurangan == 5 && $penggunaanPoin->jam_pulang_custom) { + $jamPulang = $penggunaanPoin->jam_pulang_custom; + if (strlen($jamPulang) == 5) + $jamPulang .= ':00'; + $status = 'Pulang Cepat (Poin)'; + } + } + + return [ + 'jam_masuk' => $jamMasuk, + 'jam_pulang' => $jamPulang, + 'shift_nama' => $jadwal->shift->nama_shift, + 'status_jadwal' => $status, + 'lokasi' => $lokasi + ]; + } + + public function absenMasuk($user, $request) + { + $hariIni = Carbon::today('Asia/Jakarta')->toDateString(); + $jamSekarang = Carbon::now('Asia/Jakarta'); + + $jadwal = JadwalKerja::with(['shift']) + ->where('id_user', $user->id) + ->where('tanggal', $hariIni) + ->first(); + + if (!$jadwal) { + throw new \Exception('Anda tidak memiliki jadwal kerja hari ini.', 403); + } + + $cekPresensi = Presensi::where('id_user', $user->id) + ->where('tanggal', $hariIni) + ->first(); + + if ($cekPresensi) { + throw new \Exception('Anda sudah melakukan absen masuk hari ini.', 400); + } + + $kantor = $user->kantor; + if (!$kantor) { + throw new \Exception('Kantor belum disetting.', 400); + } + + $jarak = $this->calculateDistance($request->latitude, $request->longitude, $kantor->latitude, $kantor->longitude); + $radiusKantor = $kantor->radius; + $isDalamRadius = ($jarak <= $radiusKantor); + + $dynamicSchedule = $this->getDynamicSchedule($user->id, $hariIni); + + if (!$dynamicSchedule) { + $jamMasukEfektif = $jadwal->shift->jam_mulai; + $statusJadwal = 'Normal'; + } else { + $jamMasukEfektif = $dynamicSchedule['jam_masuk']; + $statusJadwal = $dynamicSchedule['status_jadwal']; + } + + $toleransi = 10; + $batasTerlambat = Carbon::parse($jamMasukEfektif, 'Asia/Jakarta')->addMinutes($toleransi); + + $isTerlambat = $jamSekarang->greaterThan($batasTerlambat); + + $jamJadwalMasuk = Carbon::parse($jamMasukEfektif, 'Asia/Jakarta'); + $waktuTerlambat = null; + $waktuMasukAwal = null; + + if ($jamSekarang->greaterThan($jamJadwalMasuk)) { + $waktuTerlambat = $jamSekarang->diff($jamJadwalMasuk)->format('%H:%I:%S'); + } else if ($jamSekarang->lessThan($jamJadwalMasuk)) { + $waktuMasukAwal = $jamSekarang->diff($jamJadwalMasuk)->format('%H:%I:%S'); + } + + $idValidasi = 2; + $statusKehadiran = 'Tepat Waktu'; + $alasanTelat = $request->alasan_telat ?? null; + + if ($isTerlambat) { + $idValidasi = 1; + $statusKehadiran = 'Terlambat'; + } else if ($waktuMasukAwal != null) { + $idValidasi = 2; + $statusKehadiran = 'Datang Awal'; + if (str_contains($statusJadwal, 'Poin')) { + $statusKehadiran = 'Masuk Siang (Poin) - Datang Awal'; + } + } else { + $idValidasi = 2; + + if (str_contains($statusJadwal, 'Poin')) { + $statusKehadiran = 'Masuk Siang (Poin)'; + } + } + + if (!$isDalamRadius) { + $idValidasi = 1; + } + + $idStatus = ($statusKehadiran === 'Terlambat') ? 2 : 1; + + $faceVerified = 0; + if ($user->dataWajah && $user->dataWajah->is_verified == 1) { + $faceVerified = 1; + } + + $fotoPath = $this->uploadFile($request->file('foto'), 'masuk', $user->id); + + $presensi = Presensi::create([ + 'id_user' => $user->id, + 'tanggal' => $hariIni, + 'jam_masuk' => $jamSekarang->format('H:i:s'), + 'lat_masuk' => $request->latitude, + 'lon_masuk' => $request->longitude, + 'foto_wajah_masuk' => $fotoPath, + 'id_status' => $idStatus, + 'alasan_telat' => $alasanTelat, + 'id_validasi' => $idValidasi, + 'verifikasi_wajah' => $faceVerified, + 'keterangan_luar_radius' => $request->keterangan_luar_radius ?? null, + 'waktu_terlambat' => $waktuTerlambat, + 'waktu_masuk_awal' => $waktuMasukAwal + ]); + + return [ + 'jam_masuk' => $jamSekarang->format('H:i'), + 'status_kehadiran' => $statusKehadiran, + 'dalam_radius' => $isDalamRadius, + 'jarak' => round($jarak) . ' meter', + 'face_verified' => (bool) $faceVerified, + 'jam_efektif' => $jamMasukEfektif + ]; + } + + public function absenPulang($user, $request) + { + $jamSekarang = Carbon::now('Asia/Jakarta'); + $hariIni = Carbon::today('Asia/Jakarta')->toDateString(); + + $presensi = Presensi::where('id_user', $user->id) + ->whereNull('jam_pulang') + ->latest() + ->first(); + + if (!$presensi) { + throw new \Exception('Belum ada data absen masuk hari ini atau sudah pulang.', 404); + } + + if ($presensi->jam_pulang) { + throw new \Exception('Anda sudah absen pulang hari ini.', 400); + } + + $dynamicSchedule = $this->getDynamicSchedule($user->id, $hariIni); + + $jamPulangEfektif = $dynamicSchedule['jam_pulang']; + $statusJadwal = $dynamicSchedule['status_jadwal']; + + $jamPulangTarget = Carbon::parse($jamPulangEfektif, 'Asia/Jakarta'); + $isPulangAwal = $jamSekarang->lessThan($jamPulangTarget); + + $waktuPulangAwal = null; + $waktuPulangAkhir = null; + + if ($jamSekarang->lessThan($jamPulangTarget)) { + $waktuPulangAwal = $jamSekarang->diff($jamPulangTarget)->format('%H:%I:%S'); + } else if ($jamSekarang->greaterThan($jamPulangTarget)) { + $waktuPulangAkhir = $jamSekarang->diff($jamPulangTarget)->format('%H:%I:%S'); + } + + $statusPulang = 'Tepat Waktu'; + $keterangan = $request->keterangan_pulang ?? null; + + if ($isPulangAwal) { + $statusPulang = 'Pulang Awal'; + } else if ($waktuPulangAkhir != null) { + $statusPulang = 'Lembur / Pulang Akhir'; + } else { + if (str_contains($statusJadwal, 'Poin')) { + $statusPulang = 'Pulang Cepat (Poin)'; + } + } + + $fotoPath = $this->uploadFile($request->file('foto'), 'pulang', $user->id); + + $presensi->update([ + 'jam_pulang' => $jamSekarang->format('H:i:s'), + 'lat_pulang' => $request->latitude, + 'lon_pulang' => $request->longitude, + 'foto_wajah_pulang' => $fotoPath, + 'keterangan_pulang' => $keterangan, + 'waktu_pulang_awal' => $waktuPulangAwal, + 'waktu_pulang_akhir' => $waktuPulangAkhir + ]); + + return [ + 'jam_pulang' => $jamSekarang->format('H:i'), + 'status_pulang' => $statusPulang, + 'keterangan' => $keterangan + ]; + } + + private function uploadFile(UploadedFile $file, $type, $userId) + { + $fileName = "{$type}_{$userId}_" . time() . '.' . $file->getClientOriginalExtension(); + return $file->storeAs('uploads/absensi', $fileName, 'public'); + } + + private function calculateDistance($lat1, $lon1, $lat2, $lon2) + { + $earthRadius = 6371000; + + $dLat = deg2rad($lat2 - $lat1); + $dLon = deg2rad($lon2 - $lon1); + + $a = sin($dLat / 2) * sin($dLat / 2) + + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * + sin($dLon / 2) * sin($dLon / 2); + + $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); + + return $earthRadius * $c; + } +} diff --git a/app/SwaggerDocs.php b/app/SwaggerDocs.php new file mode 100644 index 00000000..3b95176b --- /dev/null +++ b/app/SwaggerDocs.php @@ -0,0 +1,33 @@ +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 00000000..7be5e373 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,21 @@ +withRouting( + web: __DIR__ . '/../routes/web.php', + api: __DIR__ . '/../routes/api.php', + commands: __DIR__ . '/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->alias([ + 'role' => \App\Http\Middleware\CheckRole::class, + ]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + + })->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 00000000..9fd1765a --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,6 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "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.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "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.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "darkaonline/l5-swagger", + "version": "10.1.0", + "source": { + "type": "git", + "url": "https://github.com/DarkaOnLine/L5-Swagger.git", + "reference": "62582008f851bdcda40d27898a1ec609da9e509c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/62582008f851bdcda40d27898a1ec609da9e509c", + "reference": "62582008f851bdcda40d27898a1ec609da9e509c", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^12.1 || ^11.44", + "php": "^8.2", + "swagger-api/swagger-ui": ">=5.18.3", + "symfony/yaml": "^5.0 || ^6.0 || ^7.0", + "zircote/swagger-php": "^6.0" + }, + "require-dev": { + "mockery/mockery": "1.*", + "orchestra/testbench": "^10.0 || ^9.0 || ^8.0 || 7.* || ^6.15 || 5.*", + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "L5Swagger": "L5Swagger\\L5SwaggerFacade" + }, + "providers": [ + "L5Swagger\\L5SwaggerServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "L5Swagger\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Darius Matulionis", + "email": "darius@matulionis.lt" + } + ], + "description": "OpenApi or Swagger integration to Laravel", + "keywords": [ + "api", + "documentation", + "laravel", + "openapi", + "specification", + "swagger", + "ui" + ], + "support": { + "issues": "https://github.com/DarkaOnLine/L5-Swagger/issues", + "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/10.1.0" + }, + "funding": [ + { + "url": "https://github.com/DarkaOnLine", + "type": "github" + } + ], + "time": "2026-01-16T06:31:36+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.1.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/3.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.19.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf", + "reference": "b287d2a16aceffbf6e0295559b39662612b77fcf", + "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 || ~8.5.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.19.0" + }, + "time": "2025-10-17T16:34:55+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2026-03-10T16:41:02+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2025-08-22T14:27:06+00:00" + }, + { + "name": "laravel/framework", + "version": "v12.54.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "325497463e7599cd14224c422c6e5dd2fe832868" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/325497463e7599cd14224c422c6e5dd2fe832868", + "reference": "325497463e7599cd14224c422c6e5dd2fe832868", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.8.1", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/log-implementation": "1.0|2.0|3.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/json-schema": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/reflection": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^10.9.0", + "pda/pheanstalk": "^5.0.6|^7.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", + "resend/resend-php": "^0.10.0|^1.0", + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0|^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0|^1.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2026-03-10T20:25:56+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.14", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "reference": "9f0e371244eedfe2ebeaa72c79c54bb5df6e0176", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.14" + }, + "time": "2026-03-01T09:02:38+00:00" + }, + { + "name": "laravel/sanctum", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "symfony/console": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2026-02-07T17:19:31+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2026-02-20T19:59:49+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.11.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.11.1" + }, + "time": "2026-02-06T14:12:35+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "84b1ca48347efdbe775426f108622a42735a6579" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/84b1ca48347efdbe775426f108622a42735a6579", + "reference": "84b1ca48347efdbe775426f108622a42735a6579", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.31.1", + "commonmark/commonmark.js": "0.31.1", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2026-03-05T21:37:03+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/flysystem", + "version": "3.32.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "reference": "254b1595b16b22dbddaaef9ed6ca9fdac4956725", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.32.0" + }, + "time": "2026-02-25T17:01:41+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+00:00" + }, + { + "name": "league/uri", + "version": "7.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76", + "reference": "4436c6ec8d458e4244448b069cc572d088230b76", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-01-14T17:24:56+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-01-15T06:54:53+00:00" + }, + { + "name": "maatwebsite/excel", + "version": "3.1.67", + "source": { + "type": "git", + "url": "https://github.com/SpartnerNL/Laravel-Excel.git", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/e508e34a502a3acc3329b464dad257378a7edb4d", + "reference": "e508e34a502a3acc3329b464dad257378a7edb4d", + "shasum": "" + }, + "require": { + "composer/semver": "^3.3", + "ext-json": "*", + "illuminate/support": "5.8.*||^6.0||^7.0||^8.0||^9.0||^10.0||^11.0||^12.0", + "php": "^7.0||^8.0", + "phpoffice/phpspreadsheet": "^1.30.0", + "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||^10.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.67" + }, + "funding": [ + { + "url": "https://laravel-excel.com/commercial-support", + "type": "custom" + }, + { + "url": "https://github.com/patrickbrouwers", + "type": "github" + } + ], + "time": "2025-08-26T09:13:16+00:00" + }, + { + "name": "maennchen/zipstream-php", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/maennchen/ZipStream-PHP.git", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/682f1098a8fddbaf43edac2306a691c7ad508ec5", + "reference": "682f1098a8fddbaf43edac2306a691c7ad508ec5", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "ext-zlib": "*", + "php-64bit": "^8.3" + }, + "require-dev": { + "brianium/paratest": "^7.7", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.86", + "guzzlehttp/guzzle": "^7.5", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^2.5", + "phpunit/phpunit": "^12.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.2.1" + }, + "funding": [ + { + "url": "https://github.com/maennchen", + "type": "github" + } + ], + "time": "2025-12-10T09:58:31+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": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.11.3", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2026-03-11T17:23:39+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.1.3" + }, + "time": "2026-02-13T03:05:33+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "It's like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2026-02-16T23:10:27+00:00" + }, + { + "name": "phpoffice/phpspreadsheet", + "version": "1.30.2", + "source": { + "type": "git", + "url": "https://github.com/PHPOffice/PhpSpreadsheet.git", + "reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", + "reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2", + "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.0 <8.5.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "dev-main", + "doctrine/instantiator": "^1.5", + "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" + }, + { + "name": "Owen Leibman" + } + ], + "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.30.2" + }, + "time": "2026-01-11T05:58:24+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.12.21", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "reference": "4821fab5b7cd8c49a673a9fd5754dc9162bb9e97", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.21" + }, + "time": "2026-03-06T21:21:28+00:00" + }, + { + "name": "radebatz/type-info-extras", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DerManoMann/type-info-extras.git", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/95a524a74a61648b44e355cb33d38db4b17ef5ce", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "symfony/type-info": "^7.3.8 || ^7.4.1 || ^8.0 || ^8.1-@dev" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.70", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Radebatz\\TypeInfoExtras\\": "src" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Martin Rademacher", + "email": "mano@radebatz.org" + } + ], + "description": "Extras for symfony/type-info", + "homepage": "http://radebatz.net/mano/", + "keywords": [ + "component", + "symfony", + "type-info", + "types" + ], + "support": { + "issues": "https://github.com/DerManoMann/type-info-extras/issues", + "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.7" + }, + "time": "2026-03-06T22:40:29+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, + { + "name": "swagger-api/swagger-ui", + "version": "v5.32.0", + "source": { + "type": "git", + "url": "https://github.com/swagger-api/swagger-ui.git", + "reference": "1b33a5976d8f5e02d3bc40de1deae3a4a3642e36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/1b33a5976d8f5e02d3bc40de1deae3a4a3642e36", + "reference": "1b33a5976d8f5e02d3bc40de1deae3a4a3642e36", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Anna Bodnia", + "email": "anna.bodnia@gmail.com" + }, + { + "name": "Buu Nguyen", + "email": "buunguyen@gmail.com" + }, + { + "name": "Josh Ponelat", + "email": "jponelat@gmail.com" + }, + { + "name": "Kyle Shockey", + "email": "kyleshockey1@gmail.com" + }, + { + "name": "Robert Barnwell", + "email": "robert@robertismy.name" + }, + { + "name": "Sahar Jafari", + "email": "shr.jafari@gmail.com" + } + ], + "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", + "homepage": "http://swagger.io", + "keywords": [ + "api", + "documentation", + "openapi", + "specification", + "swagger", + "ui" + ], + "support": { + "issues": "https://github.com/swagger-api/swagger-ui/issues", + "source": "https://github.com/swagger-api/swagger-ui/tree/v5.32.0" + }, + "time": "2026-02-27T11:52:15+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v8.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-11-12T15:46:48+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T14:06:20+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a178bf80f05dbbe469a337730eba79d61315262", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T13:07:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-20T16:42:42+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-05T11:45:55+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-29T09:40:50+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T13:15:18+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-06T16:33:18+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-25T16:50:00+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v7.4.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-05T15:24:09+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "608476f4604102976d687c483ac63a79ba18cc97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.4.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-26T15:07:59+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-25T16:50:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T10:14:57+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/http-client-contracts": "<2.5", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v8.0.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-17T13:07:04+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/type-info", + "version": "v8.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "3c7de103dd6cb68be24e155838a64ef4a70ae195" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/3c7de103dd6cb68be24e155838a64ef4a70ae195", + "reference": "3c7de103dd6cb68be24e155838a64ef4a70ae195", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v8.0.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-03-04T13:55:34+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v7.4.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-01-03T23:30:35+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-15T10:53:20+00:00" + }, + { + "name": "symfony/yaml", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-02-09T09:33:46+00:00" + }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "https://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2024-11-21T01:49:47+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "6.0.6", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "9447c1f45b5ae93185caea9a0c8e298399188a25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/9447c1f45b5ae93185caea9a0c8e298399188a25", + "reference": "9447c1f45b5ae93185caea9a0c8e298399188a25", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "radebatz/type-info-extras": "^1.0.2", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5", + "rector/rector": "^2.3.1" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/6.0.6" + }, + "funding": [ + { + "url": "https://github.com/zircote", + "type": "github" + } + ], + "time": "2026-02-28T06:42:58+00:00" + } + ], + "packages-dev": [ + { + "name": "barryvdh/laravel-ide-helper", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-ide-helper.git", + "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b106f7ee85f263c4f103eca49e7bf3862c2e5e75", + "reference": "b106f7ee85f263c4f103eca49e7bf3862c2e5e75", + "shasum": "" + }, + "require": { + "barryvdh/reflection-docblock": "^2.4", + "composer/class-map-generator": "^1.0", + "ext-json": "*", + "illuminate/console": "^11.15 || ^12", + "illuminate/database": "^11.15 || ^12", + "illuminate/filesystem": "^11.15 || ^12", + "illuminate/support": "^11.15 || ^12", + "php": "^8.2" + }, + "require-dev": { + "ext-pdo_sqlite": "*", + "friendsofphp/php-cs-fixer": "^3", + "illuminate/config": "^11.15 || ^12", + "illuminate/view": "^11.15 || ^12", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^9.2 || ^10", + "phpunit/phpunit": "^10.5 || ^11.5.3", + "spatie/phpunit-snapshot-assertions": "^4 || ^5", + "vimeo/psalm": "^5.4", + "vlucas/phpdotenv": "^5" + }, + "suggest": { + "illuminate/events": "Required for automatic helper generation (^6|^7|^8|^9|^10|^11)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\LaravelIdeHelper\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.", + "keywords": [ + "autocomplete", + "codeintel", + "dev", + "helper", + "ide", + "laravel", + "netbeans", + "phpdoc", + "phpstorm", + "sublime" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.6.1" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-10T09:11:07+00:00" + }, + { + "name": "barryvdh/reflection-docblock", + "version": "v2.4.1", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/ReflectionDocBlock.git", + "reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/ReflectionDocBlock/zipball/4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b", + "reference": "4f5ba70c30c81f2ce03a16a9965832cfcc31ed3b", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.14|^9" + }, + "suggest": { + "dflydev/markdown": "~1.0", + "erusev/parsedown": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Barryvdh": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "mike.vanriel@naenius.com" + } + ], + "support": { + "source": "https://github.com/barryvdh/ReflectionDocBlock/tree/v2.4.1" + }, + "time": "2026-03-05T20:09:01+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.1", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "reference": "8f5fa3cc214230e71f54924bd0197a3bcc705eb1", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-12-29T13:15:25+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.18.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.24|^11.0|^12.0|^13.0", + "illuminate/log": "^10.24|^11.0|^12.0|^13.0", + "illuminate/process": "^10.24|^11.0|^12.0|^13.0", + "illuminate/support": "^10.24|^11.0|^12.0|^13.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0|^12.0|^13.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.13|^9.17|^10.8|^11.0", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", + "phpstan/phpstan": "^1.12.27", + "symfony/var-dumper": "^6.3|^7.0|^8.0", + "symfony/yaml": "^6.3|^7.0|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "dev", + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2026-02-09T13:44:54+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.94.2", + "illuminate/view": "^12.54.1", + "larastan/larastan": "^3.9.3", + "laravel-zero/framework": "^12.0.5", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.0" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "dev", + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2026-03-12T15:51:39+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.53.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/e340eaa2bea9b99192570c48ed837155dbf24fbb", + "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/yaml": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2026-02-06T12:16:02+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.9.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", + "php": "^8.2.0", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "conflict": { + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "dev", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2026-02-17T17:33:08+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "11.0.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2c1ed04922802c15e1de5d7447b4856de949cf56", + "reference": "2c1ed04922802c15e1de5d7447b4856de949cf56", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.1", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.3.1" + }, + "require-dev": { + "phpunit/phpunit": "^11.5.46" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.12" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2025-12-24T07:01:01+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/2f3a64888c814fc235386b7387dd5b5ed92ad903", + "reference": "2f3a64888c814fc235386b7387dd5b5ed92ad903", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T13:52:54+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^11.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:07:44+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:08:43+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:09:35+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "11.5.55", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/adc7262fccc12de2b30f12a8aa0b33775d814f00", + "reference": "adc7262fccc12de2b30f12a8aa0b33775d814f00", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.12", + "phpunit/php-file-iterator": "^5.1.1", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.3", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.1", + "sebastian/exporter": "^6.3.2", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/recursion-context": "^6.0.3", + "sebastian/type": "^5.1.3", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "11.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.55" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:37:06+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:41:36+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-03-19T07:56:08+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:45:54+00:00" + }, + { + "name": "sebastian/comparator", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "reference": "2c95e1e86cb8dd41beb8d502057d1081ccc8eca9", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.4" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:26:40+00:00" + }, + { + "name": "sebastian/complexity", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:49:50+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "sebastian/environment", + "version": "7.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2025-05-21T11:55:47+00:00" + }, + { + "name": "sebastian/exporter", + "version": "6.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/70a298763b40b213ec087c51c739efcaa90bcd74", + "reference": "70a298763b40b213ec087c51c739efcaa90bcd74", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.3-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:12:51+00:00" + }, + { + "name": "sebastian/global-state", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:57:36+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:58:38+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:00:13+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T05:01:32+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:42:22+00:00" + }, + { + "name": "sebastian/type", + "version": "5.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:55:48+00:00" + }, + { + "name": "sebastian/version", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", + "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-11-17T20:03:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 00000000..f63a80c9 --- /dev/null +++ b/config/app.php @@ -0,0 +1,36 @@ + env('APP_NAME', 'Laravel'), + + 'env' => env('APP_ENV', 'production'), + + 'debug' => (bool) env('APP_DEBUG', false), + + 'url' => env('APP_URL', 'http://localhost'), + + 'timezone' => 'Asia/Jakarta', + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) + ), + ], + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 00000000..81934868 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,36 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', App\Models\User::class), + ], + + ], + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 00000000..c37b4eac --- /dev/null +++ b/config/cache.php @@ -0,0 +1,80 @@ + env('CACHE_STORE', 'database'), + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + 'failover' => [ + 'driver' => 'failover', + 'stores' => [ + 'database', + 'array', + ], + ], + + ], + + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 00000000..bcf9e8bc --- /dev/null +++ b/config/database.php @@ -0,0 +1,137 @@ + env('DB_CONNECTION', 'sqlite'), + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => env('DB_SSLMODE', 'prefer'), + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + + ], + + ], + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + 'default' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_DB', '0'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + 'cache' => [ + 'url' => env('REDIS_URL'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'username' => env('REDIS_USERNAME'), + 'password' => env('REDIS_PASSWORD'), + 'port' => env('REDIS_PORT', '6379'), + 'database' => env('REDIS_CACHE_DB', '1'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 00000000..818fa887 --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,45 @@ + env('FILESYSTEM_DISK', 'local'), + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => false, + ], + + 's3' => [ + 'driver' => 's3', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION'), + 'bucket' => env('AWS_BUCKET'), + 'url' => env('AWS_URL'), + 'endpoint' => env('AWS_ENDPOINT'), + 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), + 'throw' => false, + 'report' => false, + ], + + ], + + 'links' => [ + public_path('storage') => storage_path('app/public'), + ], + +]; diff --git a/config/ide-helper.php b/config/ide-helper.php new file mode 100644 index 00000000..0d774e38 --- /dev/null +++ b/config/ide-helper.php @@ -0,0 +1,85 @@ + '_ide_helper.php', + + 'models_filename' => '_ide_helper_models.php', + + 'meta_filename' => '.phpstorm.meta.php', + + 'include_fluent' => false, + + 'include_factory_builders' => false, + + 'write_model_magic_where' => true, + + 'write_model_external_builder_methods' => true, + + 'write_model_relation_count_properties' => true, + 'write_model_relation_exists_properties' => false, + + 'write_eloquent_model_mixins' => false, + + 'include_helpers' => false, + + 'helper_files' => [ + base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', + base_path() . '/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php', + ], + + 'model_locations' => [ + 'app', + ], + + 'ignored_models' => [ + + ], + + 'model_hooks' => [ + + ], + + 'extra' => [ + 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], + 'Session' => ['Illuminate\Session\Store'], + ], + + 'magic' => [], + + 'interfaces' => [ + + ], + + 'model_camel_case_properties' => false, + + 'type_overrides' => [ + 'integer' => 'int', + 'boolean' => 'bool', + ], + + 'include_class_docblocks' => false, + + 'force_fqn' => false, + + 'use_generics_annotations' => true, + + 'macro_default_return_types' => [ + Illuminate\Http\Client\Factory::class => Illuminate\Http\Client\PendingRequest::class, + ], + + 'additional_relation_types' => [], + + 'additional_relation_return_types' => [], + + 'enforce_nullable_relationships' => true, + + 'post_migrate' => [ + + ], + + 'ignored_aliases' => [ + 'PDF', + ], + +]; diff --git a/config/l5-swagger.php b/config/l5-swagger.php new file mode 100644 index 00000000..ebed5158 --- /dev/null +++ b/config/l5-swagger.php @@ -0,0 +1,132 @@ + 'default', + 'documentations' => [ + 'default' => [ + 'api' => [ + 'title' => 'L5 Swagger UI', + ], + + 'routes' => [ + + 'api' => 'api/documentation', + ], + 'paths' => [ + + 'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true), + + 'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'), + + 'docs_json' => 'api-docs.json', + + 'docs_yaml' => 'api-docs.yaml', + + 'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'), + + 'annotations' => [ + base_path('app'), + ], + ], + ], + ], + 'defaults' => [ + 'routes' => [ + + 'docs' => 'docs', + + 'oauth2_callback' => 'api/oauth2-callback', + + 'middleware' => [ + 'api' => [], + 'asset' => [], + 'docs' => [], + 'oauth2_callback' => [], + ], + + 'group_options' => [], + ], + + 'paths' => [ + + 'docs' => storage_path('api-docs'), + + 'views' => base_path('resources/views/vendor/l5-swagger'), + + 'base' => env('L5_SWAGGER_BASE_PATH', null), + + 'excludes' => [], + ], + + 'scanOptions' => [ + + 'default_processors_configuration' => [ + + ], + + 'analyser' => null, + + 'analysis' => null, + + 'processors' => [ + + ], + + 'pattern' => null, + + 'exclude' => [ + 'vendor', + ], + + 'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION), + ], + + 'securityDefinitions' => [ + 'securitySchemes' => [ + + ], + 'security' => [ + + [ + + ], + ], + ], + + 'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false), + + 'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false), + + 'proxy' => false, + + 'additional_config_url' => null, + + 'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null), + + 'validator_url' => null, + + 'ui' => [ + 'display' => [ + 'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false), + + 'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'), + + 'filter' => env('L5_SWAGGER_UI_FILTERS', true), + ], + + 'authorization' => [ + + 'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false), + + 'oauth2' => [ + + 'use_pkce_with_authorization_code_grant' => false, + ], + ], + ], + + 'constants' => [ + 'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'), + ], + ], +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 00000000..3458fd3a --- /dev/null +++ b/config/logging.php @@ -0,0 +1,96 @@ + env('LOG_CHANNEL', 'stack'), + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'handler_with' => [ + 'stream' => 'php://stderr', + ], + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 00000000..4ff22aaf --- /dev/null +++ b/config/mail.php @@ -0,0 +1,73 @@ + env('MAIL_MAILER', 'log'), + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + 'sendmail' => [ + 'transport' => 'sendmail', + 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), + ], + + 'log' => [ + 'transport' => 'log', + 'channel' => env('MAIL_LOG_CHANNEL'), + ], + + 'array' => [ + 'transport' => 'array', + ], + + 'failover' => [ + 'transport' => 'failover', + 'mailers' => [ + 'smtp', + 'log', + ], + 'retry_after' => 60, + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + ], + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 00000000..e9892373 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,80 @@ + env('QUEUE_CONNECTION', 'database'), + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + 'deferred' => [ + 'driver' => 'deferred', + ], + + 'background' => [ + 'driver' => 'background', + ], + + 'failover' => [ + 'driver' => 'failover', + 'connections' => [ + 'database', + 'deferred', + ], + ], + + ], + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 00000000..ea85549e --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,26 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + + ))), + + 'guard' => ['web'], + + 'expiration' => null, + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 00000000..0f13a0f1 --- /dev/null +++ b/config/services.php @@ -0,0 +1,26 @@ + [ + 'key' => env('POSTMARK_API_KEY'), + ], + + 'resend' => [ + 'key' => env('RESEND_API_KEY'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 00000000..f7fe509a --- /dev/null +++ b/config/session.php @@ -0,0 +1,42 @@ + env('SESSION_DRIVER', 'database'), + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + 'encrypt' => env('SESSION_ENCRYPT', false), + + 'files' => storage_path('framework/sessions'), + + 'connection' => env('SESSION_CONNECTION'), + + 'table' => env('SESSION_TABLE', 'sessions'), + + 'store' => env('SESSION_STORE'), + + 'lottery' => [2, 100], + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug((string) env('APP_NAME', 'laravel')).'-session' + ), + + 'path' => env('SESSION_PATH', '/'), + + 'domain' => env('SESSION_DOMAIN'), + + 'secure' => env('SESSION_SECURE_COOKIE'), + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 00000000..9b19b93c --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 00000000..74c771aa --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,35 @@ + + */ + public function definition(): array + { + return [ + 'nama_lengkap' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + 'nik' => fake()->unique()->numerify('##########'), + 'no_telp' => fake()->phoneNumber(), + 'alamat' => fake()->address(), + 'status_aktif' => 1, + 'is_face_registered' => 0, + + ]; + } +} diff --git a/database/migrations/2026_02_03_140500_add_sisa_cuti_to_users_table.php b/database/migrations/2026_02_03_140500_add_sisa_cuti_to_users_table.php new file mode 100644 index 00000000..a02502f6 --- /dev/null +++ b/database/migrations/2026_02_03_140500_add_sisa_cuti_to_users_table.php @@ -0,0 +1,22 @@ +integer('sisa_cuti')->default(12)->after('password'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('sisa_cuti'); + }); + } +}; diff --git a/database/migrations/2026_02_03_140600_create_hari_libur_table.php b/database/migrations/2026_02_03_140600_create_hari_libur_table.php new file mode 100644 index 00000000..9d09ea1f --- /dev/null +++ b/database/migrations/2026_02_03_140600_create_hari_libur_table.php @@ -0,0 +1,23 @@ +id('id_hari_libur'); + $table->date('tanggal'); + $table->string('keterangan'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('hari_libur'); + } +}; diff --git a/database/migrations/2026_02_04_114853_create_personal_access_tokens_table.php b/database/migrations/2026_02_04_114853_create_personal_access_tokens_table.php new file mode 100644 index 00000000..74b1b58c --- /dev/null +++ b/database/migrations/2026_02_04_114853_create_personal_access_tokens_table.php @@ -0,0 +1,28 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/database/migrations/2026_02_06_112201_create_face_registrations_table.php b/database/migrations/2026_02_06_112201_create_face_registrations_table.php new file mode 100644 index 00000000..06060958 --- /dev/null +++ b/database/migrations/2026_02_06_112201_create_face_registrations_table.php @@ -0,0 +1,32 @@ +id('id_face_registration'); + $table->unsignedBigInteger('id_pegawai'); + $table->text('face_embedding')->nullable(); + $table->string('foto_pendaftaran', 255)->nullable(); + $table->enum('status_verifikasi', ['pending', 'approved', 'rejected'])->default('pending'); + $table->unsignedBigInteger('verified_by')->nullable(); + $table->timestamp('verified_at')->nullable(); + $table->timestamps(); + + $table->foreign('id_pegawai')->references('id_pegawai')->on('pegawai')->onDelete('cascade'); + $table->foreign('verified_by')->references('id')->on('users')->onDelete('set null'); + }); + } + } + + public function down(): void + { + Schema::dropIfExists('face_registrations'); + } +}; diff --git a/database/migrations/2026_02_06_120220_add_keterangan_luar_radius_to_presensi_table.php b/database/migrations/2026_02_06_120220_add_keterangan_luar_radius_to_presensi_table.php new file mode 100644 index 00000000..34f3877a --- /dev/null +++ b/database/migrations/2026_02_06_120220_add_keterangan_luar_radius_to_presensi_table.php @@ -0,0 +1,23 @@ +id('id_poin'); + $table->foreignId('id_user')->constrained('users')->onDelete('cascade'); + $table->integer('jumlah_poin'); + $table->string('sumber')->default('bonus'); + $table->date('tgl_kadaluarsa'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('poins'); + } +}; diff --git a/database/migrations/2026_02_12_051600_add_fifo_columns_to_poin_lembur_table.php b/database/migrations/2026_02_12_051600_add_fifo_columns_to_poin_lembur_table.php new file mode 100644 index 00000000..06137c3d --- /dev/null +++ b/database/migrations/2026_02_12_051600_add_fifo_columns_to_poin_lembur_table.php @@ -0,0 +1,30 @@ +integer('sisa_poin')->nullable()->after('jumlah_poin'); + } + if (!Schema::hasColumn('poin_lembur', 'expired_at')) { + $table->date('expired_at')->nullable()->after('tanggal'); + } + if (!Schema::hasColumn('poin_lembur', 'is_fully_used')) { + $table->boolean('is_fully_used')->default(false)->after('expired_at'); + } + }); + } + + public function down(): void + { + Schema::table('poin_lembur', function (Blueprint $table) { + $table->dropColumn(['sisa_poin', 'expired_at', 'is_fully_used']); + }); + } +}; diff --git a/database/migrations/2026_02_12_075459_add_custom_time_to_penggunaan_poin_table.php b/database/migrations/2026_02_12_075459_add_custom_time_to_penggunaan_poin_table.php new file mode 100644 index 00000000..603189af --- /dev/null +++ b/database/migrations/2026_02_12_075459_add_custom_time_to_penggunaan_poin_table.php @@ -0,0 +1,27 @@ +time('jam_masuk_custom')->nullable()->after('jumlah_poin'); + } + if (!Schema::hasColumn('penggunaan_poin', 'jam_pulang_custom')) { + $table->time('jam_pulang_custom')->nullable()->after('jam_masuk_custom'); + } + }); + } + + public function down(): void + { + Schema::table('penggunaan_poin', function (Blueprint $table) { + $table->dropColumn(['jam_masuk_custom', 'jam_pulang_custom']); + }); + } +}; diff --git a/database/migrations/2026_02_12_075803_add_keterangan_pulang_to_presensi_table.php b/database/migrations/2026_02_12_075803_add_keterangan_pulang_to_presensi_table.php new file mode 100644 index 00000000..e7db13f6 --- /dev/null +++ b/database/migrations/2026_02_12_075803_add_keterangan_pulang_to_presensi_table.php @@ -0,0 +1,24 @@ +string('keterangan_pulang', 150)->nullable()->after('alasan_telat'); + }); + } + } + + public function down(): void + { + Schema::table('presensi', function (Blueprint $table) { + $table->dropColumn('keterangan_pulang'); + }); + } +}; diff --git a/database/migrations/2026_02_13_062927_update_structure_for_fifo_points.php b/database/migrations/2026_02_13_062927_update_structure_for_fifo_points.php new file mode 100644 index 00000000..47dfbae5 --- /dev/null +++ b/database/migrations/2026_02_13_062927_update_structure_for_fifo_points.php @@ -0,0 +1,19 @@ +dropForeign(['id_penggunaan']); + } catch (\Exception $e) { + + } + $table->dropColumn('id_penggunaan'); + } + + if (!Schema::hasColumn('poin_lembur', 'sisa_poin')) { + $table->integer('sisa_poin')->after('jumlah_poin')->default(0); + } + if (!Schema::hasColumn('poin_lembur', 'expired_at')) { + $table->date('expired_at')->nullable()->after('tanggal'); + } + if (!Schema::hasColumn('poin_lembur', 'is_fully_used')) { + $table->boolean('is_fully_used')->default(false)->after('expired_at'); + } + }); + + if (!Schema::hasTable('detail_penggunaan_poin')) { + Schema::create('detail_penggunaan_poin', function (Blueprint $table) { + $table->id(); + $table->foreignId('id_penggunaan')->constrained('penggunaan_poin')->onDelete('cascade'); + $table->foreignId('id_poin_sumber')->constrained('poin_lembur')->onDelete('cascade'); + $table->integer('jumlah_diambil'); + $table->timestamps(); + }); + } + } + + public function down(): void + { + + } +}; diff --git a/database/migrations/2026_02_13_090000_update_structure_for_fifo_points.php b/database/migrations/2026_02_13_090000_update_structure_for_fifo_points.php new file mode 100644 index 00000000..bd055e53 --- /dev/null +++ b/database/migrations/2026_02_13_090000_update_structure_for_fifo_points.php @@ -0,0 +1,56 @@ +dropForeign(['id_penggunaan']); + } catch (\Exception $e) { + + } + $table->dropColumn('id_penggunaan'); + } + + if (!Schema::hasColumn('poin_lembur', 'sisa_poin')) { + $table->integer('sisa_poin')->after('jumlah_poin')->comment('Saldo aktif poin ini'); + } + if (!Schema::hasColumn('poin_lembur', 'expired_at')) { + $table->date('expired_at')->after('tanggal')->comment('Batas waktu penggunaan'); + } + if (!Schema::hasColumn('poin_lembur', 'is_fully_used')) { + $table->boolean('is_fully_used')->default(false)->after('expired_at')->comment('True jika sisa_poin 0'); + } + }); + + if (!Schema::hasTable('detail_penggunaan_poin')) { + Schema::create('detail_penggunaan_poin', function (Blueprint $table) { + $table->id(); + $table->foreignId('id_penggunaan')->constrained('penggunaan_poin', 'id_penggunaan')->onDelete('cascade'); + $table->foreignId('id_poin_sumber')->constrained('poin_lembur', 'id_poin')->onDelete('cascade'); + $table->integer('jumlah_diambil'); + $table->timestamps(); + }); + } + } + + public function down(): void + { + Schema::dropIfExists('detail_penggunaan_poin'); + Schema::table('poin_lembur', function (Blueprint $table) { + + if (!Schema::hasColumn('poin_lembur', 'id_penggunaan')) { + $table->foreignId('id_penggunaan')->nullable()->constrained('penggunaan_poin', 'id_penggunaan'); + } + + }); + } +}; diff --git a/database/migrations/2026_02_16_144322_update_schema_for_face_recognition.php b/database/migrations/2026_02_16_144322_update_schema_for_face_recognition.php new file mode 100644 index 00000000..f324ba63 --- /dev/null +++ b/database/migrations/2026_02_16_144322_update_schema_for_face_recognition.php @@ -0,0 +1,39 @@ +tinyInteger('is_face_registered')->default(0)->after('password'); + } + }); + + if (!Schema::hasTable('data_wajah')) { + Schema::create('data_wajah', function (Blueprint $table) { + $table->id(); + $table->foreignId('id_user')->constrained('users')->cascadeOnDelete(); + $table->string('path_model_yml')->nullable(); + $table->tinyInteger('is_verified')->default(0)->comment('0:Pending, 1:Approved, 2:Rejected'); + $table->timestamp('last_updated')->nullable(); + }); + } + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + if (Schema::hasColumn('users', 'is_face_registered')) { + $table->dropColumn('is_face_registered'); + } + }); + + Schema::dropIfExists('data_wajah'); + } +}; diff --git a/database/migrations/2026_02_16_163810_add_path_model_yml_to_data_wajah_table.php b/database/migrations/2026_02_16_163810_add_path_model_yml_to_data_wajah_table.php new file mode 100644 index 00000000..fb0f22ca --- /dev/null +++ b/database/migrations/2026_02_16_163810_add_path_model_yml_to_data_wajah_table.php @@ -0,0 +1,22 @@ +string('path_model_yml')->nullable()->after('id_user'); + }); + } + + public function down(): void + { + Schema::table('data_wajah', function (Blueprint $table) { + $table->dropColumn('path_model_yml'); + }); + } +}; diff --git a/database/migrations/2026_02_16_164216_add_is_verified_and_last_updated_to_data_wajah.php b/database/migrations/2026_02_16_164216_add_is_verified_and_last_updated_to_data_wajah.php new file mode 100644 index 00000000..9b064e22 --- /dev/null +++ b/database/migrations/2026_02_16_164216_add_is_verified_and_last_updated_to_data_wajah.php @@ -0,0 +1,27 @@ +tinyInteger('is_verified')->default(0)->comment('0:Pending, 1:Approved, 2:Rejected')->after('path_model_yml'); + } + if (!Schema::hasColumn('data_wajah', 'last_updated')) { + $table->timestamp('last_updated')->nullable()->after('is_verified'); + } + }); + } + + public function down(): void + { + Schema::table('data_wajah', function (Blueprint $table) { + $table->dropColumn(['is_verified', 'last_updated']); + }); + } +}; diff --git a/database/migrations/2026_02_16_164449_make_encoding_wajah_nullable_in_data_wajah.php b/database/migrations/2026_02_16_164449_make_encoding_wajah_nullable_in_data_wajah.php new file mode 100644 index 00000000..b0516a66 --- /dev/null +++ b/database/migrations/2026_02_16_164449_make_encoding_wajah_nullable_in_data_wajah.php @@ -0,0 +1,26 @@ +text('encoding_wajah')->nullable()->change(); + } + }); + } + + public function down(): void + { + Schema::table('data_wajah', function (Blueprint $table) { + if (Schema::hasColumn('data_wajah', 'encoding_wajah')) { + + } + }); + } +}; diff --git a/database/migrations/2026_02_21_082548_add_selisih_waktu_to_presensi_table.php b/database/migrations/2026_02_21_082548_add_selisih_waktu_to_presensi_table.php new file mode 100644 index 00000000..66129235 --- /dev/null +++ b/database/migrations/2026_02_21_082548_add_selisih_waktu_to_presensi_table.php @@ -0,0 +1,30 @@ +time('waktu_terlambat')->nullable()->after('keterangan_pulang'); + $table->time('waktu_masuk_awal')->nullable()->after('waktu_terlambat'); + $table->time('waktu_pulang_awal')->nullable()->after('waktu_masuk_awal'); + $table->time('waktu_pulang_akhir')->nullable()->after('waktu_pulang_awal'); + }); + } + + public function down(): void + { + Schema::table('presensi', function (Blueprint $table) { + $table->dropColumn([ + 'waktu_terlambat', + 'waktu_masuk_awal', + 'waktu_pulang_awal', + 'waktu_pulang_akhir' + ]); + }); + } +}; diff --git a/database/migrations/2026_03_01_060000_add_performance_indexes.php b/database/migrations/2026_03_01_060000_add_performance_indexes.php new file mode 100644 index 00000000..0c989280 --- /dev/null +++ b/database/migrations/2026_03_01_060000_add_performance_indexes.php @@ -0,0 +1,91 @@ +index('id_jabatan'); + $table->index('id_divisi'); + $table->index('id_kantor'); + $table->index('status_aktif'); + }); + + Schema::table('presensi', function (Blueprint $table) { + $table->index('tanggal'); + $table->index('id_user'); + $table->index('id_status'); + $table->index('id_validasi'); + $table->index(['id_user', 'tanggal']); + }); + + Schema::table('jadwal_kerja', function (Blueprint $table) { + $table->index(['id_user', 'tanggal']); + }); + + Schema::table('penggunaan_poin', function (Blueprint $table) { + $table->index(['id_user', 'tanggal_penggunaan']); + $table->index('id_status'); + }); + + Schema::table('pengajuan_izin', function (Blueprint $table) { + $table->index('id_status'); + $table->index('id_user'); + }); + + Schema::table('lembur', function (Blueprint $table) { + $table->index('id_status'); + $table->index('id_user'); + }); + + Schema::table('poin_lembur', function (Blueprint $table) { + $table->index('id_user'); + $table->index('tanggal'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropIndex(['id_jabatan']); + $table->dropIndex(['id_divisi']); + $table->dropIndex(['id_kantor']); + $table->dropIndex(['status_aktif']); + }); + + Schema::table('presensi', function (Blueprint $table) { + $table->dropIndex(['tanggal']); + $table->dropIndex(['id_user']); + $table->dropIndex(['id_status']); + $table->dropIndex(['id_validasi']); + $table->dropIndex(['id_user', 'tanggal']); + }); + + Schema::table('jadwal_kerja', function (Blueprint $table) { + $table->dropIndex(['id_user', 'tanggal']); + }); + + Schema::table('penggunaan_poin', function (Blueprint $table) { + $table->dropIndex(['id_user', 'tanggal_penggunaan']); + $table->dropIndex(['id_status']); + }); + + Schema::table('pengajuan_izin', function (Blueprint $table) { + $table->dropIndex(['id_status']); + $table->dropIndex(['id_user']); + }); + + Schema::table('lembur', function (Blueprint $table) { + $table->dropIndex(['id_status']); + $table->dropIndex(['id_user']); + }); + + Schema::table('poin_lembur', function (Blueprint $table) { + $table->dropIndex(['id_user']); + $table->dropIndex(['tanggal']); + }); + } +}; diff --git a/database/migrations/2026_03_01_141559_change_primary_keys_to_string_combination.php b/database/migrations/2026_03_01_141559_change_primary_keys_to_string_combination.php new file mode 100644 index 00000000..0dc17f1b --- /dev/null +++ b/database/migrations/2026_03_01_141559_change_primary_keys_to_string_combination.php @@ -0,0 +1,97 @@ +convertExistingData(); + + DB::statement('ALTER TABLE poin_lembur ADD CONSTRAINT poin_lembur_id_lembur_foreign FOREIGN KEY (id_lembur) REFERENCES lembur (id_lembur) ON DELETE SET NULL;'); + + DB::statement('ALTER TABLE detail_penggunaan_poin ADD CONSTRAINT detail_penggunaan_poin_id_penggunaan_foreign FOREIGN KEY (id_penggunaan) REFERENCES penggunaan_poin (id_penggunaan) ON DELETE CASCADE;'); + } + + private function convertExistingData() + { + + $izinRecords = DB::table('pengajuan_izin')->get(); + foreach ($izinRecords as $izin) { + if (is_numeric($izin->id_izin)) { + $createdDate = $izin->created_at ? date('ym', strtotime($izin->created_at)) : date('ym'); + $newId = 'IZN-' . $createdDate . '-' . strtoupper(Str::random(5)); + + DB::table('pengajuan_izin') + ->where('id_izin', $izin->id_izin) + ->update(['id_izin' => $newId]); + } + } + + $lemburRecords = DB::table('lembur')->get(); + foreach ($lemburRecords as $lembur) { + if (is_numeric($lembur->id_lembur)) { + $createdDate = $lembur->created_at ? date('ym', strtotime($lembur->created_at)) : date('ym'); + $newId = 'LMB-' . $createdDate . '-' . strtoupper(Str::random(5)); + + DB::table('poin_lembur') + ->where('id_lembur', $lembur->id_lembur) + ->update(['id_lembur' => $newId]); + + DB::table('lembur') + ->where('id_lembur', $lembur->id_lembur) + ->update(['id_lembur' => $newId]); + } + } + + $penggunaanRecords = DB::table('penggunaan_poin')->get(); + foreach ($penggunaanRecords as $penggunaan) { + if (is_numeric($penggunaan->id_penggunaan)) { + $createdDate = $penggunaan->created_at ? date('ym', strtotime($penggunaan->created_at)) : date('ym'); + $newId = 'PNP-' . $createdDate . '-' . strtoupper(Str::random(5)); + + DB::table('detail_penggunaan_poin') + ->where('id_penggunaan', $penggunaan->id_penggunaan) + ->update(['id_penggunaan' => $newId]); + + DB::table('penggunaan_poin') + ->where('id_penggunaan', $penggunaan->id_penggunaan) + ->update(['id_penggunaan' => $newId]); + } + } + } + + public function down(): void + { + + DB::statement('ALTER TABLE poin_lembur DROP FOREIGN KEY poin_lembur_id_lembur_foreign;'); + DB::statement('ALTER TABLE detail_penggunaan_poin DROP FOREIGN KEY detail_penggunaan_poin_id_penggunaan_foreign;'); + + DB::statement('ALTER TABLE pengajuan_izin MODIFY id_izin BIGINT UNSIGNED AUTO_INCREMENT;'); + + DB::statement('ALTER TABLE lembur MODIFY id_lembur BIGINT UNSIGNED AUTO_INCREMENT;'); + DB::statement('ALTER TABLE poin_lembur MODIFY id_lembur BIGINT UNSIGNED NULL;'); + + DB::statement('ALTER TABLE penggunaan_poin MODIFY id_penggunaan BIGINT UNSIGNED AUTO_INCREMENT;'); + DB::statement('ALTER TABLE detail_penggunaan_poin MODIFY id_penggunaan BIGINT UNSIGNED NOT NULL;'); + + DB::statement('ALTER TABLE poin_lembur ADD CONSTRAINT poin_lembur_id_lembur_foreign FOREIGN KEY (id_lembur) REFERENCES lembur (id_lembur) ON DELETE SET NULL;'); + DB::statement('ALTER TABLE detail_penggunaan_poin ADD CONSTRAINT detail_penggunaan_poin_id_penggunaan_foreign FOREIGN KEY (id_penggunaan) REFERENCES penggunaan_poin (id_penggunaan) ON DELETE CASCADE;'); + } +}; diff --git a/database/migrations/2026_03_02_220000_create_tanda_tangan_table.php b/database/migrations/2026_03_02_220000_create_tanda_tangan_table.php new file mode 100644 index 00000000..0153b23a --- /dev/null +++ b/database/migrations/2026_03_02_220000_create_tanda_tangan_table.php @@ -0,0 +1,25 @@ +string('id_tanda_tangan', 20)->primary(); + $table->foreignId('id_user')->constrained('users')->cascadeOnDelete(); + $table->string('file_ttd'); + $table->boolean('is_active')->default(true); + $table->timestamps(); + + $table->index(['id_user', 'is_active']); + }); + } + + public function down(): void + { + Schema::dropIfExists('tanda_tangan'); + } +}; diff --git a/database/migrations/2026_03_02_220100_create_surat_izin_table.php b/database/migrations/2026_03_02_220100_create_surat_izin_table.php new file mode 100644 index 00000000..070ccb30 --- /dev/null +++ b/database/migrations/2026_03_02_220100_create_surat_izin_table.php @@ -0,0 +1,30 @@ +string('id_surat', 20)->primary(); + $table->string('id_izin', 20); + $table->foreignId('id_user')->constrained('users'); + $table->string('nomor_surat', 50); + $table->text('isi_surat'); + $table->string('id_ttd_pengaju', 20)->nullable(); + $table->enum('status_surat', ['menunggu_manajer', 'menunggu_hrd', 'disetujui', 'ditolak'])->default('menunggu_manajer'); + $table->timestamps(); + + $table->foreign('id_izin')->references('id_izin')->on('pengajuan_izin')->cascadeOnDelete(); + $table->foreign('id_ttd_pengaju')->references('id_tanda_tangan')->on('tanda_tangan')->nullOnDelete(); + $table->index(['id_user', 'status_surat']); + }); + } + + public function down(): void + { + Schema::dropIfExists('surat_izin'); + } +}; diff --git a/database/migrations/2026_03_02_220200_create_approval_surat_table.php b/database/migrations/2026_03_02_220200_create_approval_surat_table.php new file mode 100644 index 00000000..0117f7a3 --- /dev/null +++ b/database/migrations/2026_03_02_220200_create_approval_surat_table.php @@ -0,0 +1,30 @@ +string('id_approval', 20)->primary(); + $table->string('id_surat', 20); + $table->foreignId('id_approver')->constrained('users'); + $table->string('id_ttd_approver', 20)->nullable(); + $table->tinyInteger('tahap')->comment('1=Manajer, 2=HRD'); + $table->enum('status', ['disetujui', 'ditolak'])->default('disetujui'); + $table->text('catatan')->nullable(); + $table->timestamps(); + + $table->foreign('id_surat')->references('id_surat')->on('surat_izin')->cascadeOnDelete(); + $table->foreign('id_ttd_approver')->references('id_tanda_tangan')->on('tanda_tangan')->nullOnDelete(); + $table->unique(['id_surat', 'tahap']); + }); + } + + public function down(): void + { + Schema::dropIfExists('approval_surat'); + } +}; diff --git a/database/migrations/2026_03_11_181342_create_riwayat_tukar_shifts_table.php b/database/migrations/2026_03_11_181342_create_riwayat_tukar_shifts_table.php new file mode 100644 index 00000000..4faa84f7 --- /dev/null +++ b/database/migrations/2026_03_11_181342_create_riwayat_tukar_shifts_table.php @@ -0,0 +1,29 @@ +id('id_riwayat'); + $table->foreignId('id_user_1')->constrained('users', 'id')->cascadeOnDelete(); + $table->unsignedBigInteger('id_jadwal_1'); + $table->foreign('id_jadwal_1')->references('id_jadwal')->on('jadwal_kerja')->cascadeOnDelete(); + $table->foreignId('id_user_2')->constrained('users', 'id')->cascadeOnDelete(); + $table->unsignedBigInteger('id_jadwal_2'); + $table->foreign('id_jadwal_2')->references('id_jadwal')->on('jadwal_kerja')->cascadeOnDelete(); + $table->text('keterangan')->nullable(); + $table->foreignId('created_by')->constrained('users', 'id')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('riwayat_tukar_shift'); + } +}; diff --git a/database/migrations/2026_03_16_155110_add_id_kantor_to_hari_libur_table.php b/database/migrations/2026_03_16_155110_add_id_kantor_to_hari_libur_table.php new file mode 100644 index 00000000..8efd6353 --- /dev/null +++ b/database/migrations/2026_03_16_155110_add_id_kantor_to_hari_libur_table.php @@ -0,0 +1,25 @@ +unsignedBigInteger('id_kantor')->nullable()->after('keterangan'); + $table->foreign('id_kantor')->references('id_kantor')->on('kantor')->onDelete('set null'); + }); + } + + public function down(): void + { + Schema::table('hari_libur', function (Blueprint $table) { + $table->dropForeign(['id_kantor']); + $table->dropColumn('id_kantor'); + }); + } +}; diff --git a/database/migrations/manual_add_keterangan_luar_radius.sql b/database/migrations/manual_add_keterangan_luar_radius.sql new file mode 100644 index 00000000..12a27bff --- /dev/null +++ b/database/migrations/manual_add_keterangan_luar_radius.sql @@ -0,0 +1,9 @@ +-- Migration: Add keterangan_luar_radius to presensi table +-- File: database/migrations/manual_add_keterangan_luar_radius.sql +-- Author: MPG HRIS Team +-- Created: 2026-02-06 + +ALTER TABLE `presensi` +ADD COLUMN `keterangan_luar_radius` TEXT NULL AFTER `id_validasi`; + +-- Deskripsi: Kolom untuk menyimpan keterangan/alasan ketika user melakukan presensi di luar radius kantor diff --git a/database/migrations/manual_face_registrations.sql b/database/migrations/manual_face_registrations.sql new file mode 100644 index 00000000..a57944a4 --- /dev/null +++ b/database/migrations/manual_face_registrations.sql @@ -0,0 +1,18 @@ +-- Manual SQL script untuk create face_registrations table +-- Run this manually via phpMyAdmin or MySQL Workbench jika migration gagal + +CREATE TABLE IF NOT EXISTS `face_registrations` ( + `id_face_registration` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + `id_pegawai` BIGINT UNSIGNED NOT NULL, + `face_embedding` TEXT NULL COMMENT 'JSON array dari Python face recognition service', + `foto_pendaftaran` VARCHAR(255) NULL, + `status_verifikasi` ENUM('pending', 'approved', 'rejected') NOT NULL DEFAULT 'pending', + `verified_by` BIGINT UNSIGNED NULL, + `verified_at` TIMESTAMP NULL, + `created_at` TIMESTAMP NULL, + `updated_at` TIMESTAMP NULL, + + -- Foreign Keys (hapus jika error, lalu run manual FK setelah pastikan kolom exist) + CONSTRAINT `fk_face_reg_pegawai` FOREIGN KEY (`id_pegawai`) REFERENCES `pegawai` (`id_pegawai`) ON DELETE CASCADE, + CONSTRAINT `fk_face_reg_verified_by` FOREIGN KEY (`verified_by`) REFERENCES `users` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 00000000..fcafb48e --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,64 @@ +insert([ + [ + 'nama_kantor' => 'Notis Digital', + 'alamat' => 'Jln. Kalimantan No. 1, Jember', + 'latitude' => -8.172110, + 'longitude' => 113.700550, + 'radius_meter' => 200, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'nama_kantor' => 'Curah Manis Studio', + 'alamat' => 'Jln. Mastrip, Jember', + 'latitude' => -8.172110, + 'longitude' => 113.700550, + 'radius_meter' => 200, + 'created_at' => now(), + 'updated_at' => now(), + ] + ]); + + User::factory(10)->create([ + 'id_kantor' => 1, + ]); + + User::factory(15)->create([ + 'id_kantor' => 2, + ]); + + User::factory()->create([ + 'nama_lengkap' => 'Julian (Super Admin)', + 'email' => 'julian@admin.com', + 'password' => bcrypt('password'), + 'divisi' => 'IT', + 'jabatan' => 'Manager', + 'id_kantor' => 1, + ]); + + $this->call([ + MasterDataSeeder::class, + PermissionSeeder::class, + PresensiStatusSeeder::class, + PresensiSeeder::class, + + JenisIzinSeeder::class, + StatusPengajuanSeeder::class, + + PengajuanIzinSeeder::class, + ]); + } +} diff --git a/database/seeders/FixLegacyRolesSeeder.php b/database/seeders/FixLegacyRolesSeeder.php new file mode 100644 index 00000000..7401db01 --- /dev/null +++ b/database/seeders/FixLegacyRolesSeeder.php @@ -0,0 +1,33 @@ + 'admin']); + $rolePegawai = Role::firstOrCreate(['nama_role' => 'pegawai']); + + $oldAdmins = User::where('role', 'admin')->get(); + foreach ($oldAdmins as $user) { + if (!$user->roles()->where('nama_role', 'admin')->exists()) { + $user->roles()->attach($roleAdmin->id_role ?? $roleAdmin->id); + $this->command->info("Migrated legacy admin: {$user->email}"); + } + } + + $oldStaff = User::where('role', 'pegawai')->get(); + foreach ($oldStaff as $user) { + if (!$user->roles()->where('nama_role', 'pegawai')->exists()) { + $user->roles()->attach($rolePegawai->id_role ?? $rolePegawai->id); + $this->command->info("Migrated legacy staff: {$user->email}"); + } + } + } +} diff --git a/database/seeders/FixNegativeLemburDuration.php b/database/seeders/FixNegativeLemburDuration.php new file mode 100644 index 00000000..dacac0ac --- /dev/null +++ b/database/seeders/FixNegativeLemburDuration.php @@ -0,0 +1,80 @@ +get(); + + if ($lembursNegatif->isEmpty()) { + echo "✅ Tidak ada lembur dengan durasi negatif.\n"; + return; + } + + echo "⚠️ Ditemukan {$lembursNegatif->count()} lembur dengan durasi negatif.\n\n"; + + foreach ($lembursNegatif as $lembur) { + echo "📋 ID Lembur: {$lembur->id_lembur}\n"; + echo " Durasi lama: {$lembur->durasi_menit} menit\n"; + + $start = Carbon::parse($lembur->jam_mulai); + $end = Carbon::parse($lembur->jam_selesai); + $durasiBenar = abs($start->diffInMinutes($end)); + + echo " Durasi benar: {$durasiBenar} menit\n"; + + $lembur->update(['durasi_menit' => $durasiBenar]); + + $statusApproved = DB::table('status_pengajuan') + ->where('nama_status', 'Approved') + ->first(); + + if ( + $lembur->id_status == ($statusApproved->id_status ?? 2) && + $lembur->id_kompensasi == JenisKompensasi::POIN + ) { + + echo " 🔄 Re-generating poin...\n"; + + PoinLembur::where('id_lembur', $lembur->id_lembur)->delete(); + + $poinBaru = floor($durasiBenar / 30); + + if ($poinBaru > 0) { + PoinLembur::create([ + 'id_user' => $lembur->id_user, + 'jumlah_poin' => $poinBaru, + 'sisa_poin' => $poinBaru, + 'id_lembur' => $lembur->id_lembur, + 'keterangan' => 'Poin dari Lembur Tanggal ' . Carbon::parse($lembur->tanggal_lembur)->format('d/m/Y') . ' (Diperbaiki)', + 'tanggal' => now(), + 'expired_at' => now()->addMonths(6), + 'is_fully_used' => false + ]); + + $lembur->update(['jumlah_poin' => $poinBaru]); + + echo " ✅ Poin berhasil di-generate: {$poinBaru} poin\n"; + } else { + echo " ⚠️ Durasi terlalu singkat untuk mendapat poin\n"; + } + } + + echo "\n"; + } + + echo "✅ Selesai! Total {$lembursNegatif->count()} lembur diperbaiki.\n"; + } +} diff --git a/database/seeders/JenisIzinSeeder.php b/database/seeders/JenisIzinSeeder.php new file mode 100644 index 00000000..0068cf58 --- /dev/null +++ b/database/seeders/JenisIzinSeeder.php @@ -0,0 +1,21 @@ +delete(); + + DB::table('jenis_izin')->insert([ + + ['id_jenis_izin' => 1, 'nama_izin' => 'Sakit'], + ['id_jenis_izin' => 2, 'nama_izin' => 'Cuti'], + ['id_jenis_izin' => 3, 'nama_izin' => 'Izin'], + ]); + } +} diff --git a/database/seeders/JenisPenguranganSeeder.php b/database/seeders/JenisPenguranganSeeder.php new file mode 100644 index 00000000..55554431 --- /dev/null +++ b/database/seeders/JenisPenguranganSeeder.php @@ -0,0 +1,17 @@ +insertOrIgnore([ + ['id_pengurangan' => 1, 'nama_pengurangan' => 'Datang Terlambat'], + ['id_pengurangan' => 2, 'nama_pengurangan' => 'Pulang Cepat'], + ]); + } +} diff --git a/database/seeders/MasterDataSeeder.php b/database/seeders/MasterDataSeeder.php new file mode 100644 index 00000000..c7ea422b --- /dev/null +++ b/database/seeders/MasterDataSeeder.php @@ -0,0 +1,66 @@ + 'Information Technology']); + $divHR = Divisi::create(['nama_divisi' => 'Human Resources']); + Divisi::create(['nama_divisi' => 'Finance & Accounting']); + Divisi::create(['nama_divisi' => 'Marketing']); + + $jabMgr = Jabatan::create(['nama_jabatan' => 'Manager']); + $jabSpv = Jabatan::create(['nama_jabatan' => 'Supervisor']); + $jabStf = Jabatan::create(['nama_jabatan' => 'Staff']); + Jabatan::create(['nama_jabatan' => 'Internship']); + + $kPusat = Kantor::create([ + 'nama_kantor' => 'Kantor Pusat', + 'alamat' => 'Jl. Jendral Sudirman No. 1, Jakarta', + 'tipe' => 'Pusat' + ]); + + $kCabang = Kantor::create([ + 'nama_kantor' => 'Curah Manis Studio', + 'alamat' => 'Jl. Curah Manis, Malang', + 'tipe' => 'Cabang' + ]); + + User::create([ + 'nama_lengkap' => 'Super Administrator', + 'email' => 'admin@mpg.co.id', + 'password' => Hash::make('admin123'), + 'role' => 'admin', + 'nik' => '999999999', + 'status_aktif' => 1, + + 'id_divisi' => $divIT->id_divisi, + 'id_jabatan' => $jabMgr->id_jabatan, + 'id_kantor' => $kPusat->id_kantor, + 'tgl_bergabung' => now(), + ]); + + User::create([ + 'nama_lengkap' => 'Budi Santoso', + 'email' => 'budi@mpg.co.id', + 'password' => Hash::make('Mpg123!'), + 'role' => 'pegawai', + 'nik' => '350712345678', + 'status_aktif' => 1, + 'id_divisi' => $divHR->id_divisi, + 'id_jabatan' => $jabStf->id_jabatan, + 'id_kantor' => $kCabang->id_kantor, + 'tgl_bergabung' => now(), + ]); + } +} diff --git a/database/seeders/PengajuanIzinSeeder.php b/database/seeders/PengajuanIzinSeeder.php new file mode 100644 index 00000000..a3e960e4 --- /dev/null +++ b/database/seeders/PengajuanIzinSeeder.php @@ -0,0 +1,52 @@ +limit(5)->get(); + + if ($users->isEmpty()) { + $this->command->warn('Tidak ada user untuk di-seed.'); + return; + } + + foreach ($users as $user) { + try { + + $idJenis = rand(1, 3); + + $idStatus = rand(1, 3); + + $tglMulai = now()->addDays(rand(-10, 10)); + $tglSelesai = (clone $tglMulai)->addDays(rand(1, 3)); + + DB::table('pengajuan_izin')->insertOrIgnore([ + 'id_user' => $user->id, + 'id_jenis_izin' => $idJenis, + 'tanggal_mulai' => $tglMulai->format('Y-m-d'), + 'tanggal_selesai' => $tglSelesai->format('Y-m-d'), + 'alasan' => 'Seeding Dummy Auto: ' . Str::random(10), + 'bukti_file' => 'https://placehold.co/600x800/png?text=Bukti+Dummy', + 'id_status' => $idStatus, + + 'created_at' => now(), + 'updated_at' => now(), + ]); + + } catch (\Exception $e) { + $this->command->error('Gagal seed user ' . $user->id . ': ' . $e->getMessage()); + } + } + + $this->command->info('Data Pengajuan Izin (FIXED) berhasil dibuat!'); + } +} diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 00000000..3b6f4a87 --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,110 @@ + $p], + ['slug' => $p] + ); + } + + $adminRole = Role::where('nama_role', 'admin')->first(); + if ($adminRole) { + $allPermissions = Permission::all(); + $adminRole->permissions()->sync($allPermissions); + } + + $hrdRole = Role::where('nama_role', 'hrd')->first(); + if ($hrdRole) { + $hrdPermissions = Permission::whereIn('nama_permission', [ + 'view_dashboard', + 'manage_divisi', 'manage_jabatan', 'manage_kantor', 'manage_shift', 'manage_hari_libur', + 'view_pegawai', 'create_pegawai', 'edit_pegawai', 'view_pegawai_detail', + 'view_presensi_all', 'approve_presensi', 'correction_presensi', + 'view_izin_all', 'approve_izin', + 'view_lembur_all', 'approve_lembur', + 'manage_face_data', + 'view_laporan' + ])->get(); + $hrdRole->permissions()->sync($hrdPermissions); + } + + $managerRole = Role::where('nama_role', 'manager')->first(); + if ($managerRole) { + $managerPermissions = Permission::whereIn('nama_permission', [ + 'view_dashboard', + 'view_pegawai', 'view_pegawai_detail', + 'view_presensi_team', + 'view_izin_team', 'approve_izin', + 'view_lembur_team', 'approve_lembur' + ])->get(); + $managerRole->permissions()->sync($managerPermissions); + } + + $pegawaiRole = Role::where('nama_role', 'pegawai')->first(); + if ($pegawaiRole) { + $pegawaiPermissions = Permission::whereIn('nama_permission', [ + 'view_dashboard', + 'view_presensi_self', + 'create_presensi', + 'create_izin', + 'create_lembur' + ])->get(); + $pegawaiRole->permissions()->sync($pegawaiPermissions); + } + } +} diff --git a/database/seeders/PresensiSeeder.php b/database/seeders/PresensiSeeder.php new file mode 100644 index 00000000..cc54dd92 --- /dev/null +++ b/database/seeders/PresensiSeeder.php @@ -0,0 +1,85 @@ +format('Y-m-d'); + + $jadwalHariIni = JadwalKerja::with('shift') + ->where('tanggal', $today) + ->get(); + + if ($jadwalHariIni->isEmpty()) { + $this->command->warn('Tidak ada jadwal kerja untuk tanggal ' . $today . '. Harap jalankan generator jadwal terlebih dahulu atau isi manual.'); + return; + } + + $this->command->info('Ditemukan ' . $jadwalHariIni->count() . ' jadwal untuk hari ini via PresensiSeeder.'); + + foreach ($jadwalHariIni as $jadwal) { + try { + if (!$jadwal->shift) + continue; + + $jamMasukShift = $jadwal->shift->jam_masuk ?? $jadwal->shift->jam_mulai; + $jamPulangShift = $jadwal->shift->jam_pulang ?? $jadwal->shift->jam_selesai; + + $shiftStart = Carbon::parse($jadwal->tanggal . ' ' . $jamMasukShift); + $shiftEnd = Carbon::parse($jadwal->tanggal . ' ' . $jamPulangShift); + + $randomMinutes = rand(-10, 30); + $jamMasuk = $shiftStart->copy()->addMinutes($randomMinutes); + + $status = 1; + $alasan = null; + $toleransi = 10; + + if ($randomMinutes > $toleransi) { + $status = 2; + $alasan = 'Terlambat ' . ($randomMinutes) . ' menit (Dummy Data)'; + } + + $validasi = (rand(1, 10) > 2) ? 1 : 2; + + $lat = -8.172110 + (rand(-100, 100) / 100000); + $lon = 113.700550 + (rand(-100, 100) / 100000); + + Presensi::firstOrCreate( + [ + 'id_user' => $jadwal->id_user, + 'tanggal' => $today + ], + [ + 'jam_masuk' => $jamMasuk->format('H:i:s'), + 'jam_pulang' => $shiftEnd->format('H:i:s'), + 'lat_masuk' => $lat, + 'lon_masuk' => $lon, + 'lat_pulang' => $lat, + 'lon_pulang' => $lon, + 'foto_wajah_masuk' => 'https://placehold.co/400x400/png?text=Masuk', + 'foto_wajah_pulang' => 'https://placehold.co/400x400/png?text=Pulang', + 'id_status' => $status, + 'alasan_telat' => $alasan, + 'id_validasi' => $validasi, + 'verifikasi_wajah' => 0 + ] + ); + + } catch (\Exception $e) { + $this->command->error('Gagal seed user ' . $jadwal->id_user . ': ' . $e->getMessage()); + } + } + + $this->command->info('Presensi dummy berhasil dibuat!'); + } +} diff --git a/database/seeders/PresensiStatusSeeder.php b/database/seeders/PresensiStatusSeeder.php new file mode 100644 index 00000000..bfa90ce5 --- /dev/null +++ b/database/seeders/PresensiStatusSeeder.php @@ -0,0 +1,24 @@ +insertOrIgnore([ + ['id_status' => 1, 'nama_status' => 'Tepat Waktu', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 2, 'nama_status' => 'Terlambat', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 3, 'nama_status' => 'Izin', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 4, 'nama_status' => 'Sakit', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 5, 'nama_status' => 'Alpha', 'created_at' => now(), 'updated_at' => now()], + ]); + + $this->command->info('Tabel status_presensi berhasil diisi!'); + } +} diff --git a/database/seeders/StatusPengajuanSeeder.php b/database/seeders/StatusPengajuanSeeder.php new file mode 100644 index 00000000..576b6027 --- /dev/null +++ b/database/seeders/StatusPengajuanSeeder.php @@ -0,0 +1,21 @@ +delete(); + + DB::table('status_pengajuan')->insert([ + + ['id_status' => 1, 'nama_status' => 'Disetujui'], + ['id_status' => 2, 'nama_status' => 'Pending'], + ['id_status' => 3, 'nama_status' => 'Ditolak'], + ]); + } +} diff --git a/database/seeders/StatusSeeder.php b/database/seeders/StatusSeeder.php new file mode 100644 index 00000000..0f0bcac4 --- /dev/null +++ b/database/seeders/StatusSeeder.php @@ -0,0 +1,33 @@ +delete(); + + DB::table('status_presensi')->insert([ + ['id_status' => 1, 'nama_status' => 'Tepat Waktu', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 2, 'nama_status' => 'Terlambat', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 3, 'nama_status' => 'Izin', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 4, 'nama_status' => 'Sakit', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 5, 'nama_status' => 'Alpha', 'created_at' => now(), 'updated_at' => now()], + ]); + + DB::table('status_validasi_presensi')->delete(); + + DB::table('status_validasi_presensi')->insert([ + ['id_status' => 1, 'nama_status' => 'Valid', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 2, 'nama_status' => 'Pending', 'created_at' => now(), 'updated_at' => now()], + ['id_status' => 3, 'nama_status' => 'Ditolak', 'created_at' => now(), 'updated_at' => now()], + ]); + + $this->command->info('Data Master Status berhasil dibuat!'); + } +} diff --git a/database/seeders/SuperAdminSeeder.php b/database/seeders/SuperAdminSeeder.php new file mode 100644 index 00000000..348b4cd0 --- /dev/null +++ b/database/seeders/SuperAdminSeeder.php @@ -0,0 +1,55 @@ + 'IT Dept']); + $jabatan = Jabatan::first() ?? Jabatan::create(['nama_jabatan' => 'Super Admin']); + $kantor = Kantor::first() ?? Kantor::create(['nama_kantor' => 'Headquarters', 'alamat' => 'Cloud', 'tipe' => 'Pusat']); + + $roleAdmin = Role::firstOrCreate( + ['nama_role' => 'admin'], + ['keterangan' => 'Administrator with Full Access'] + ); + + $user = User::updateOrCreate( + ['email' => 'superadmin@mpg.co.id'], + [ + 'nama_lengkap' => 'Super Administrator', + 'password' => Hash::make('password'), + 'nik' => '77777777', + 'no_telp' => '081234567890', + 'alamat' => 'Server Room', + 'status_aktif' => 1, + 'tgl_bergabung' => now(), + 'id_divisi' => $divisi->id_divisi ?? $divisi->id, + 'id_jabatan' => $jabatan->id_jabatan ?? $jabatan->id, + 'id_kantor' => $kantor->id_kantor ?? $kantor->id, + 'sisa_cuti' => 12, + + ] + ); + + if (!$user->roles()->where('nama_role', 'admin')->exists()) { + $user->roles()->attach($roleAdmin->id_role ?? $roleAdmin->id); + $this->command->info('Role admin attached to user.'); + } + + $this->command->info('Super Admin created successfully!'); + $this->command->info('Email: superadmin@mpg.co.id'); + $this->command->info('Password: password'); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..52b16463 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2631 @@ +{ + "name": "mpg-hris", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@hotwired/turbo": "^8.0.23" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/vite": "^4.0.0", + "autoprefixer": "^10.4.23", + "axios": "^1.11.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^2.0.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "^7.0.7" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hotwired/turbo": { + "version": "8.0.23", + "resolved": "https://registry.npmjs.org/@hotwired/turbo/-/turbo-8.0.23.tgz", + "integrity": "sha512-GZ7cijxEZ6Ig71u7rD6LHaRv/wcE/hNsc+nEfiWOkLNqUgLOwo5MNGWOy5ZV9ZUDSiQx1no7YxjTNnT4O6//cQ==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz", + "integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001765", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001765.tgz", + "integrity": "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.277", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.277.tgz", + "integrity": "sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.1.0.tgz", + "integrity": "sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..9570c4ef --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/vite": "^4.0.0", + "autoprefixer": "^10.4.23", + "axios": "^1.11.0", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^2.0.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", + "vite": "^7.0.7" + }, + "dependencies": { + "@hotwired/turbo": "^8.0.23" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..44d21192 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 00000000..b574a597 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # Redirect Trailing Slashes If Not A Folder... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_URI} (.+)/$ + RewriteRule ^ %1 [L,R=301] + + # Send Requests To Front Controller... + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + diff --git a/public/assets/images/auth/auth-img.png b/public/assets/images/auth/auth-img.png new file mode 100644 index 00000000..7d7887d1 Binary files /dev/null and b/public/assets/images/auth/auth-img.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/public/index.php b/public/index.php new file mode 100644 index 00000000..ee8f07e9 --- /dev/null +++ b/public/index.php @@ -0,0 +1,20 @@ +handleRequest(Request::capture()); diff --git a/public/js/dashboard-script.js b/public/js/dashboard-script.js new file mode 100644 index 00000000..59954dca --- /dev/null +++ b/public/js/dashboard-script.js @@ -0,0 +1,47 @@ +document.addEventListener('DOMContentLoaded', function() { +}); + +function confirmDelete(id) { + Swal.fire({ + html: ` +
+
+ + + +
+

Hapus Data?

+

+ Data yang dihapus akan hilang secara permanen dari sistem dan tidak dapat dikembalikan. +

+
+ `, + showCancelButton: true, + confirmButtonText: 'Ya, Hapus Permanen', + cancelButtonText: 'Batalkan', + buttonsStyling: false, + customClass: { + confirmButton: 'px-6 py-2.5 bg-red-600 text-white rounded-xl font-bold hover:bg-red-700 transition shadow-lg shadow-red-200 mr-2', + cancelButton: 'px-6 py-2.5 bg-slate-100 text-slate-600 rounded-xl font-bold hover:bg-slate-200 transition' + }, + width: '400px', + reverseButtons: true + }).then((result) => { + if (result.isConfirmed) { + Swal.fire({ + html: ` +
+
+

Menghapus Data...

+
+ `, + width: '250px', + showConfirmButton: false, + allowOutsideClick: false + }); + + const form = document.getElementById('delete-form-' + id); + if (form) form.submit(); + } + }) +} \ No newline at end of file diff --git a/public/js/form-handler.js b/public/js/form-handler.js new file mode 100644 index 00000000..39559d74 --- /dev/null +++ b/public/js/form-handler.js @@ -0,0 +1,45 @@ +let isFormModified = false; + +document.addEventListener('DOMContentLoaded', function() { + + const form = document.querySelector('form'); + const submitBtn = document.getElementById('submitBtn'); + const submitText = document.getElementById('submitText'); + const submitSpinner = document.getElementById('submitSpinner'); + + if (form) { + form.addEventListener('change', () => { + isFormModified = true; + }); + + form.addEventListener('submit', function() { + isFormModified = false; + + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.classList.add('opacity-75', 'cursor-not-allowed'); + } + if (submitSpinner) submitSpinner.classList.remove('hidden'); + if (submitText) submitText.textContent = 'Menyimpan...'; + }); + } + + window.addEventListener('beforeunload', function(e) { + if (isFormModified) { + e.preventDefault(); + e.returnValue = ''; + } + }); + + const backLinks = document.querySelectorAll('.btn-back'); + backLinks.forEach(link => { + link.addEventListener('click', function(e) { + if (isFormModified) { + const confirmLeave = confirm('Anda memiliki perubahan yang belum disimpan. Yakin ingin keluar?'); + if (!confirmLeave) { + e.preventDefault(); + } + } + }); + }); +}); \ No newline at end of file diff --git a/public/js/kantor-map.js b/public/js/kantor-map.js new file mode 100644 index 00000000..e663ae1a --- /dev/null +++ b/public/js/kantor-map.js @@ -0,0 +1,241 @@ +(function () { + const defaultLoc = [-7.9797, 112.6304]; + let maps = { create: null, edit: null }; + let markers = { create: null, edit: null }; + let circles = { create: null, edit: null }; + let geocoders = { create: null, edit: null }; + + function setVal(id, v) { + const el = document.getElementById(id); + if (el) el.value = v; + } + + function locateUser(type) { + if (!navigator.geolocation) { + Swal.fire({ + icon: 'error', + title: 'Tidak Didukung', + text: 'Browser Anda tidak mendukung fitur lokasi.', + confirmButtonColor: '#1e293b', + }); + return; + } + + Swal.fire({ + title: 'Mencari Lokasi...', + text: 'Mengambil posisi GPS perangkat Anda', + allowOutsideClick: false, + showConfirmButton: false, + didOpen: () => Swal.showLoading() + }); + + navigator.geolocation.getCurrentPosition( + (pos) => { + Swal.close(); + const lat = pos.coords.latitude; + const lng = pos.coords.longitude; + if (maps[type]) { + maps[type].setView([lat, lng], 16); + if (markers[type]) markers[type].setLatLng([lat, lng]); + if (circles[type]) circles[type].setLatLng([lat, lng]); + setVal(`${type}-lat`, lat.toFixed(6)); + setVal(`${type}-long`, lng.toFixed(6)); + } + }, + (error) => { + Swal.close(); + let msg = 'Gagal mendapatkan lokasi.'; + if (error.code === 1) msg = 'Izin lokasi ditolak oleh pengguna. Aktifkan izin lokasi di pengaturan browser.'; + else if (error.code === 2) msg = 'Posisi tidak dapat ditentukan. Pastikan GPS aktif.'; + else if (error.code === 3) msg = 'Waktu permintaan lokasi habis. Coba lagi.'; + + Swal.fire({ + icon: 'warning', + title: 'Lokasi Tidak Ditemukan', + text: msg, + confirmButtonColor: '#1e293b', + confirmButtonText: 'OK' + }); + }, + { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } + ); + } + + function removeExtraMarkers(type) { + if (!maps[type]) return; + maps[type].eachLayer(function(layer) { + if (layer instanceof L.Marker && layer !== markers[type]) { + maps[type].removeLayer(layer); + } + }); + } + + function initMap(type, lat, lng, radius) { + const container = document.getElementById(`map-${type}`); + if (!container) return; + + if (maps[type]) { + const pos = [lat, lng]; + maps[type].setView(pos, 15); + if (markers[type]) markers[type].setLatLng(pos); + if (circles[type]) circles[type].setLatLng(pos); + if (circles[type]) circles[type].setRadius(radius); + removeExtraMarkers(type); + setTimeout(() => maps[type].invalidateSize(), 300); + return; + } + + maps[type] = L.map(`map-${type}`).setView([lat, lng], 15); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(maps[type]); + + markers[type] = L.marker([lat, lng], { draggable: true }).addTo(maps[type]); + circles[type] = L.circle([lat, lng], { + color: type === 'create' ? '#3b82f6' : (type === 'detail' ? '#1e293b' : '#f59e0b'), + fillOpacity: 0.1, + radius: radius + }).addTo(maps[type]); + + const update = (e) => { + const latlng = e.target ? e.target.getLatLng() : e.latlng; + if (markers[type]) markers[type].setLatLng([latlng.lat, latlng.lng]); + if (circles[type]) circles[type].setLatLng([latlng.lat, latlng.lng]); + setVal(`${type}-lat`, latlng.lat.toFixed(6)); + setVal(`${type}-long`, latlng.lng.toFixed(6)); + removeExtraMarkers(type); + }; + + markers[type].on('dragend', update); + maps[type].on('click', update); + + if (typeof L.Control.Geocoder !== 'undefined') { + geocoders[type] = L.Control.geocoder({ + placeholder: 'Cari lokasi...', + errorMessage: 'Lokasi tidak ditemukan', + collapsed: false, + defaultMarkGeocode: false, + geocoder: L.Control.Geocoder.nominatim({ + geocodingQueryParams: { + countrycodes: 'id', + 'accept-language': 'id' + } + }) + }).on('markgeocode', function (e) { + const { center } = e.geocode; + maps[type].setView(center, 16); + if (markers[type]) markers[type].setLatLng(center); + if (circles[type]) circles[type].setLatLng(center); + setVal(`${type}-lat`, center.lat.toFixed(6)); + setVal(`${type}-long`, center.lng.toFixed(6)); + + removeExtraMarkers(type); + + setTimeout(() => { + removeExtraMarkers(type); + }, 200); + }).addTo(maps[type]); + } + + const locateBtn = L.control({ position: 'topleft' }); + locateBtn.onAdd = function() { + const div = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-custom'); + div.style.backgroundColor = 'white'; + div.style.width = '30px'; + div.style.height = '30px'; + div.style.display = 'flex'; + div.style.alignItems = 'center'; + div.style.justifyContent = 'center'; + div.style.cursor = 'pointer'; + div.innerHTML = ''; + div.onclick = function(e) { + e.stopPropagation(); + locateUser(type); + }; + return div; + }; + locateBtn.addTo(maps[type]); + + setTimeout(() => maps[type].invalidateSize(), 300); + } + + function setupRadiusListeners() { + ['create', 'edit'].forEach(t => { + const el = document.getElementById(`${t}-radius`); + if (el) el.addEventListener('input', (e) => circles[t] ? circles[t].setRadius(e.target.value) : null); + }); + } + + function cleanupMaps() { + ['create', 'edit', 'detail'].forEach(t => { + if (maps[t]) { + maps[t].remove(); + maps[t] = null; + markers[t] = null; + circles[t] = null; + geocoders[t] = null; + } + }); + } + + window.addEventListener('open-modal', function (e) { + if (e.detail == 'create-kantor') { + setVal('create-lat', defaultLoc[0]); + setVal('create-long', defaultLoc[1]); + setTimeout(() => { + initMap('create', defaultLoc[0], defaultLoc[1], 50); + locateUser('create'); + }, 300); + } + }); + + window.openEditModal = function (btn) { + const d = btn.dataset; + setVal('edit-nama', d.nama); + setVal('edit-tipe', d.tipe); + setVal('edit-alamat', d.alamat); + setVal('edit-radius', d.radius); + setVal('edit-lat', d.lat); + setVal('edit-long', d.long); + + const form = document.getElementById('form-edit'); + if (form && window.kantorUpdateUrl) { + form.action = window.kantorUpdateUrl.replace(':id', d.id); + } + + window.dispatchEvent(new CustomEvent('open-modal', { detail: 'edit-kantor' })); + setTimeout(() => initMap('edit', parseFloat(d.lat), parseFloat(d.long), parseInt(d.radius)), 300); + }; + + window.openDetailModal = function(btn) { + const data = btn.dataset; + document.getElementById('detail-nama').innerText = data.nama; + document.getElementById('detail-tipe').innerText = data.tipe; + document.getElementById('detail-lat').innerText = data.lat; + document.getElementById('detail-long').innerText = data.long; + document.getElementById('detail-radius').innerText = data.radius + ' m'; + document.getElementById('detail-alamat').innerText = data.alamat || '-'; + + window.dispatchEvent(new CustomEvent('open-modal', { + detail: 'detail-kantor' + })); + + setTimeout(() => { + initMap('detail', parseFloat(data.lat), parseFloat(data.long), parseInt(data.radius)); + }, 300); + } + + setupRadiusListeners(); + + if (!window.__kantorTurboAttached) { + window.__kantorTurboAttached = true; + + document.addEventListener('turbo:before-cache', function () { + cleanupMaps(); + }); + + document.addEventListener('turbo:load', function () { + if (document.getElementById('map-create') || document.getElementById('map-edit')) { + setupRadiusListeners(); + } + }); + } +})(); \ No newline at end of file diff --git a/public/js/login-script.js b/public/js/login-script.js new file mode 100644 index 00000000..8642cb79 --- /dev/null +++ b/public/js/login-script.js @@ -0,0 +1,118 @@ +document.addEventListener('DOMContentLoaded', function() { + + const toggleBtn = document.getElementById('toggle-password-btn'); + const passwordInput = document.getElementById('password'); + const eyeIcon = document.getElementById('eye-icon'); + const eyeOffIcon = document.getElementById('eye-off-icon'); + + if (toggleBtn && passwordInput) { + toggleBtn.addEventListener('click', function() { + if (passwordInput.type === 'password') { + passwordInput.type = 'text'; + if (eyeIcon) eyeIcon.style.display = 'none'; + if (eyeOffIcon) eyeOffIcon.style.display = 'block'; + } else { + passwordInput.type = 'password'; + if (eyeIcon) eyeIcon.style.display = 'block'; + if (eyeOffIcon) eyeOffIcon.style.display = 'none'; + } + }); + } + + const loginForm = document.getElementById('login-form'); + if (loginForm) { + loginForm.addEventListener('submit', function(e) { + e.preventDefault(); + + const submitBtn = loginForm.querySelector('button[type="submit"]'); + const originalText = submitBtn.innerHTML; + + submitBtn.innerHTML = ` + + + + + Processing... + `; + submitBtn.disabled = true; + submitBtn.classList.add('opacity-75', 'cursor-not-allowed'); + + const formData = new FormData(loginForm); + + fetch(loginForm.action, { + method: 'POST', + body: formData, + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json' + } + }) + .then(async response => { + if (response.status === 419) { + Swal.fire({ + icon: 'info', + title: 'Sesi Kedaluwarsa', + text: 'Halaman akan dimuat ulang, silakan coba lagi.', + confirmButtonText: 'OK', + confirmButtonColor: '#1e293b', + width: '400px', + }).then(() => { + window.location.reload(); + }); + return; + } + + const contentType = response.headers.get('content-type') || ''; + const isJson = contentType.includes('application/json'); + let data = null; + + try { + data = isJson ? await response.json() : null; + } catch (parseError) { + data = null; + } + + if (response.ok && data?.success) { + window.location.href = data.redirect || '/dashboard'; + } else { + let errorMessage = 'Email atau Password yang Anda masukkan salah.'; + + if (data?.message) { + errorMessage = data.message; + } + + if (data?.errors) { + const errorList = Object.values(data.errors).flat(); + errorMessage = `
    ${errorList.map(err => `
  • ${err}
  • `).join('')}
`; + } + + Swal.fire({ + icon: 'error', + title: 'Login Gagal', + html: errorMessage, + confirmButtonText: 'Coba Lagi', + confirmButtonColor: '#1e293b', + width: '400px', + }); + + submitBtn.innerHTML = originalText; + submitBtn.disabled = false; + submitBtn.classList.remove('opacity-75', 'cursor-not-allowed'); + } + }) + .catch(error => { + console.error('Error:', error); + Swal.fire({ + icon: 'error', + title: 'Koneksi Bermasalah', + text: 'Tidak dapat terhubung ke server. Periksa koneksi internet Anda.', + confirmButtonText: 'Muat Ulang', + confirmButtonColor: '#1e293b', + width: '400px' + }).then(() => { + window.location.reload(); + }); + }); + }); + } +}); \ No newline at end of file diff --git a/public/js/notifications.js b/public/js/notifications.js new file mode 100644 index 00000000..b64bbb47 --- /dev/null +++ b/public/js/notifications.js @@ -0,0 +1,123 @@ +(function() { + const swalData = document.getElementById('swal-data'); + const flashError = document.querySelector('.flash-data-error'); + const flashSuccess = document.querySelector('.flash-data-success'); + + function showSuccess(msg) { + Swal.fire({ + icon: 'success', + title: 'Berhasil!', + text: msg, + timer: 3000, + showConfirmButton: false, + toast: true, + position: 'top-end', + timerProgressBar: true, + background: '#fff', + color: '#130F26' + }); + } + + function showError(msg) { + Swal.fire({ + icon: 'error', + title: 'Gagal!', + text: msg, + confirmButtonColor: '#1e293b', + }); + } + + if (swalData) { + const successMessage = swalData.getAttribute('data-success'); + const errorMessage = swalData.getAttribute('data-error'); + + if (successMessage && successMessage !== '') { + showSuccess(successMessage); + swalData.setAttribute('data-success', ''); + } + + if (errorMessage && errorMessage !== '') { + showError(errorMessage); + swalData.setAttribute('data-error', ''); + } + + const validationErrors = swalData.getAttribute('data-errors'); + if (validationErrors) { + try { + const errors = JSON.parse(validationErrors); + if (errors.length > 0) { + Swal.fire({ + icon: 'error', + title: 'Gagal Validasi!', + html: `
    ${errors.map(err => `
  • ${err}
  • `).join('')}
`, + confirmButtonColor: '#d33', + }); + } + } catch (e) {} + swalData.setAttribute('data-errors', '[]'); + } + } + + if (flashError) { + const msg = flashError.getAttribute('data-message'); + if (msg && msg !== '') { + showError(msg); + flashError.setAttribute('data-message', ''); + } + } + if (flashSuccess) { + const msg = flashSuccess.getAttribute('data-message'); + if (msg && msg !== '') { + showSuccess(msg); + flashSuccess.setAttribute('data-message', ''); + } + } +})(); + +function confirmDelete(id) { + Swal.fire({ + title: 'Apakah Anda yakin?', + text: "Data ini akan dihapus secara permanen.", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + cancelButtonColor: '#3085d6', + confirmButtonText: 'Ya, hapus saja!', + cancelButtonText: 'Batal', + reverseButtons: true + }).then((result) => { + if (result.isConfirmed) { + const form = document.getElementById('delete-form-' + id); + if (form) { + form.submit(); + } else { + console.error('Form hapus tidak ditemukan untuk ID:', id); + } + } + }); + } + +function confirmAction(event, formId, message, confirmBtnColor = '#3085d6', confirmBtnText = 'Ya, lanjutkan!') { + event.preventDefault(); + + Swal.fire({ + title: 'Konfirmasi Tindakan', + text: message, + icon: 'question', + showCancelButton: true, + confirmButtonColor: confirmBtnColor, + cancelButtonColor: '#64748b', + confirmButtonText: confirmBtnText, + cancelButtonText: 'Batal', + reverseButtons: true + }).then((result) => { + if (result.isConfirmed) { + const form = document.getElementById(formId); + if (form) { + form.submit(); + } else { + console.error('Form tidak ditemukan:', formId); + } + } + }); +} \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 00000000..eb053628 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/python_scripts/requirements.txt b/python_scripts/requirements.txt new file mode 100644 index 00000000..5d5cb2bd --- /dev/null +++ b/python_scripts/requirements.txt @@ -0,0 +1,2 @@ +opencv-contrib-python-headless +numpy diff --git a/python_scripts/train_face.py b/python_scripts/train_face.py new file mode 100644 index 00000000..b8743c9b --- /dev/null +++ b/python_scripts/train_face.py @@ -0,0 +1,134 @@ +import cv2 +import os +import sys +import numpy as np +import json + +# --- KONSTANTA & FUNGSI SHARED (Harus sama dengan verify_face.py) --- +FACE_SIZE = (200, 200) +# Pastikan opencv-contrib-python terinstall untuk modul face +face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') +profile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_profileface.xml') +eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml') + +def apply_clahe(gray_img): + clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) + return clahe.apply(gray_img) + +def align_face(gray_img, face_rect): + (x, y, w, h) = face_rect + roi_gray = gray_img[y:y+h, x:x+w] + eyes = eye_cascade.detectMultiScale(roi_gray, 1.1, 15, minSize=(w//6, h//6)) + + if len(eyes) >= 2: + eyes = sorted(eyes, key=lambda e: e[0]) + l_center = (int(eyes[0][0] + eyes[0][2]/2), int(eyes[0][1] + eyes[0][3]/2)) + r_center = (int(eyes[1][0] + eyes[1][2]/2), int(eyes[1][1] + eyes[1][3]/2)) + dy = r_center[1] - l_center[1] + dx = r_center[0] - l_center[0] + angle = np.degrees(np.arctan2(dy, dx)) + + if abs(angle) < 30: + center = (float(w / 2), float(h / 2)) + M = cv2.getRotationMatrix2D(center, angle, 1.0) + rotated = cv2.warpAffine(roi_gray, M, (w, h), flags=cv2.INTER_CUBIC) + return rotated + return roi_gray + +def preprocess_for_training(img_path): + """Versi simplified dari preprocess untuk training data""" + img = cv2.imread(img_path) + if img is None: return None + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # Deteksi Wajah + faces = face_cascade.detectMultiScale(gray, 1.1, 5) + if len(faces) == 0: + faces = profile_cascade.detectMultiScale(gray, 1.1, 5) + if len(faces) == 0: + gray = cv2.flip(gray, 1) + faces = profile_cascade.detectMultiScale(gray, 1.1, 5) + + if len(faces) == 0: return None + + # Ambil wajah terbesar + faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True) + + # Alignment & Enhancement + aligned = align_face(gray, faces[0]) + resized = cv2.resize(aligned, FACE_SIZE, interpolation=cv2.INTER_AREA) + denoised = cv2.bilateralFilter(resized, 5, 75, 75) + final = apply_clahe(denoised) + + return final + +def train_model(user_id, dataset_path, model_storage_path): + try: + if not os.path.exists(dataset_path): + raise Exception(f"Dataset path not found: {dataset_path}") + + face_samples = [] + ids = [] + + # Setup LBPH dengan parameter optimal dari simulasi Anda + recognizer = cv2.face.LBPHFaceRecognizer_create( + radius=1, + neighbors=8, + grid_x=8, + grid_y=8, + threshold=100.0 + ) + + image_paths = [os.path.join(dataset_path, f) for f in os.listdir(dataset_path) if f.endswith(('.jpg', '.jpeg', '.png'))] + + if len(image_paths) == 0: + raise Exception("No images found in dataset path") + + success_count = 0 + for image_path in image_paths: + processed_face = preprocess_for_training(image_path) + + if processed_face is not None: + face_samples.append(processed_face) + ids.append(int(user_id)) + success_count += 1 + else: + # Log gambar yang gagal dideteksi wajahnya + pass + + if len(face_samples) == 0: + raise Exception("Wajah tidak terdeteksi pada semua foto pendaftaran. Pastikan foto jelas dan pencahayaan cukup.") + + # Latih model + recognizer.train(face_samples, np.array(ids)) + + # Simpan model + if not os.path.exists(model_storage_path): + os.makedirs(model_storage_path) + + model_file = os.path.join(model_storage_path, f"user_{user_id}.yml") + recognizer.save(model_file) + + print(json.dumps({ + "status": "success", + "message": f"Model trained successfully with {success_count} valid samples", + "model_path": model_file + })) + + except Exception as e: + print(json.dumps({ + "status": "error", + "message": str(e) + })) + +if __name__ == "__main__": + if len(sys.argv) < 4: # Fixed check to ensure 3 arguments are present + print(json.dumps({"status": "error", "message": "Invalid arguments. Usage: python train_face.py "})) + sys.exit(1) + + u_id = sys.argv[1] + d_path = sys.argv[2] + m_path = sys.argv[3] + + train_model(u_id, d_path, m_path) diff --git a/python_scripts/verify_face.py b/python_scripts/verify_face.py new file mode 100644 index 00000000..1124eca2 --- /dev/null +++ b/python_scripts/verify_face.py @@ -0,0 +1,219 @@ +import cv2 +import sys +import os +import json +import numpy as np + +# --- KONFIGURASI & KONSTANTA --- +FACE_SIZE = (200, 200) +BLUR_THRESHOLD = 30.0 # Dilonggarkan untuk kamera depan HP +THRESHOLD_MATCH = 80.0 # < 80: Approved (langsung cocok) +THRESHOLD_REVIEW = 100.0 # 80-100: Pending (Review HRD) + +# Inisialisasi Cascade +face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml') +profile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_profileface.xml') +eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml') + +def apply_clahe(gray_img): + clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) + return clahe.apply(gray_img) + +def get_blur_score(img): + return cv2.Laplacian(img, cv2.CV_64F).var() + +def fix_exif_rotation(img_path): + """Memperbaiki orientasi gambar berdasarkan EXIF metadata dari kamera HP""" + try: + from PIL import Image + pil_img = Image.open(img_path) + exif = pil_img.getexif() + orientation = exif.get(274, 1) # Tag 274 = Orientation + + if orientation == 3: + pil_img = pil_img.rotate(180, expand=True) + elif orientation == 6: + pil_img = pil_img.rotate(270, expand=True) + elif orientation == 8: + pil_img = pil_img.rotate(90, expand=True) + + img_array = np.array(pil_img) + if len(img_array.shape) == 3 and img_array.shape[2] == 3: + img_array = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR) + return img_array + except ImportError: + return None + except Exception: + return None + +def detect_faces_multi(gray, min_face_size): + """Coba deteksi wajah di 4 orientasi (0, 90, 180, 270) + flip""" + rotations = [ + (0, None), + (90, cv2.ROTATE_90_CLOCKWISE), + (180, cv2.ROTATE_180), + (270, cv2.ROTATE_90_COUNTERCLOCKWISE), + ] + for angle, rot_code in rotations: + test_gray = gray if rot_code is None else cv2.rotate(gray, rot_code) + + # Frontal face - strict + faces = face_cascade.detectMultiScale(test_gray, 1.1, 5, minSize=(min_face_size, min_face_size)) + if len(faces) > 0: + return faces, test_gray + + # Frontal face - longgar + faces = face_cascade.detectMultiScale(test_gray, 1.05, 3, minSize=(min_face_size, min_face_size)) + if len(faces) > 0: + return faces, test_gray + + # Flip horizontal + flipped = cv2.flip(test_gray, 1) + faces = face_cascade.detectMultiScale(flipped, 1.05, 3, minSize=(min_face_size, min_face_size)) + if len(faces) > 0: + return faces, flipped + + return [], gray + +def align_face(gray_img, face_rect): + (x, y, w, h) = face_rect + roi_gray = gray_img[y:y+h, x:x+w] + + eyes = eye_cascade.detectMultiScale(roi_gray, 1.1, 15, minSize=(w//6, h//6)) + + if len(eyes) >= 2: + eyes = sorted(eyes, key=lambda e: e[0]) + l_center = (int(eyes[0][0] + eyes[0][2]/2), int(eyes[0][1] + eyes[0][3]/2)) + r_center = (int(eyes[1][0] + eyes[1][2]/2), int(eyes[1][1] + eyes[1][3]/2)) + + dy = r_center[1] - l_center[1] + dx = r_center[0] - l_center[0] + angle = np.degrees(np.arctan2(dy, dx)) + + if abs(angle) < 30: + center = (float(w / 2), float(h / 2)) + M = cv2.getRotationMatrix2D(center, angle, 1.0) + rotated = cv2.warpAffine(roi_gray, M, (w, h), flags=cv2.INTER_CUBIC) + return rotated + + return roi_gray + +def preprocess_image(img_path): + """Pipeline pemrosesan gambar dengan EXIF rotation handling""" + if not os.path.exists(img_path): + raise Exception("File gambar input tidak ditemukan.") + + # Coba baca dengan EXIF rotation fix dulu (kamera HP) + img = fix_exif_rotation(img_path) + if img is None: + img = cv2.imread(img_path) + + if img is None: + raise Exception("Gagal membaca file gambar (Corrupted/Not Valid).") + + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + + # 1. Cek Kualitas: Blur + blur_score = get_blur_score(gray) + if blur_score < BLUR_THRESHOLD: + raise Exception(f"Foto terlalu buram (Score: {round(blur_score, 1)}). Harap foto ulang.") + + h_img, w_img = gray.shape + min_face_size = int(min(h_img, w_img) * 0.1) + + # 2. Deteksi Wajah (Multi-orientasi + flip) + faces, gray = detect_faces_multi(gray, min_face_size) + + if len(faces) == 0: + # Terakhir coba profil samping + faces = profile_cascade.detectMultiScale(gray, 1.1, 3, minSize=(min_face_size, min_face_size)) + + if len(faces) == 0: + raise Exception("Wajah tidak terdeteksi. Pastikan pencahayaan cukup dan wajah terlihat jelas.") + + # Ambil wajah terbesar + faces = sorted(faces, key=lambda f: f[2] * f[3], reverse=True) + (x, y, w, h) = faces[0] + + # 3. Cek Jarak + face_ratio = max(w, h) / max(h_img, w_img) + if face_ratio < 0.1: + raise Exception("Wajah terlalu jauh. Mohon dekatkan kamera.") + + # 4. Alignment & Preprocessing + aligned_face = align_face(gray, faces[0]) + resized_face = cv2.resize(aligned_face, FACE_SIZE, interpolation=cv2.INTER_AREA) + denoised = cv2.bilateralFilter(resized_face, 5, 75, 75) + final_img = apply_clahe(denoised) + + return final_img, blur_score + +def verify_face(model_path, image_path): + try: + if not os.path.exists(model_path): + raise Exception("Model biometrik user belum tersedia (Belum training).") + + # Parameter HARUS sama dengan train_face.py + recognizer = cv2.face.LBPHFaceRecognizer_create( + radius=1, + neighbors=8, + grid_x=8, + grid_y=8, + threshold=100.0 + ) + recognizer.read(model_path) + + processed_face = None + blur_score = 0.0 + + # Preprocess Image + try: + processed_face, blur_score = preprocess_image(image_path) + except Exception as e_proc: + print(json.dumps({ + "status": "success", + "match": False, + "confidence": 999, + "verification_status": "PREPROCESSING_FAILED", + "message": str(e_proc) + })) + return + + # Prediksi + id_user, confidence = recognizer.predict(processed_face) + + # Logika Keputusan: LBPH 0 = Identik, >100 = Sangat Berbeda + status_verifikasi = "REJECTED" + is_match = False + + if confidence < THRESHOLD_MATCH: + status_verifikasi = "APPROVED" + is_match = True + elif confidence < THRESHOLD_REVIEW: + status_verifikasi = "PENDING" + is_match = True + + print(json.dumps({ + "status": "success", + "match": is_match, + "verification_status": status_verifikasi, + "confidence": round(confidence, 2), + "blur_score": round(blur_score, 1), + "user_id": id_user + })) + + except Exception as e: + print(json.dumps({ + "status": "error", + "message": str(e) + })) + +if __name__ == "__main__": + if len(sys.argv) < 3: + print(json.dumps({"status": "error", "message": "Invalid arguments"})) + sys.exit(1) + + m_path = sys.argv[1] + i_path = sys.argv[2] + + verify_face(m_path, i_path) diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100644 index 00000000..394a4b24 --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,110 @@ +@import "tailwindcss"; +@source "../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php"; + +@theme { + --color-primary: #130f26; + --color-success: #16a34a; + --color-danger: #dc2626; + --color-warning: #eab308; + + --font-sans: "Inter", sans-serif; + --shadow-glow: 0 0 15px rgba(19, 15, 38, 0.15); + --shadow-soft: 0 4px 20px -2px rgba(0, 0, 0, 0.05); + --shadow-card: 0 2px 12px -2px rgba(0, 0, 0, 0.04); +} + +body { + font-family: "Inter", sans-serif !important; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +div:where(.swal2-container) * { + font-family: "Inter", sans-serif !important; +} + +div:where(.swal2-container) div:where(.swal2-popup):not(.swal2-toast) { + border-radius: 20px !important; + padding: 32px !important; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.05), + 0 25px 50px -12px rgba(0, 0, 0, 0.25) !important; + background: #fff !important; + width: 32em !important; + max-width: 90% !important; +} + +div:where(.swal2-container).swal2-backdrop-show { + background: transparent !important; + backdrop-filter: none !important; +} + +div:where(.swal2-container) .swal2-title, +div:where(.swal2-container) .swal2-html-container { + font-family: "Inter", sans-serif !important; +} +div:where(.swal2-container) .swal2-title { + font-size: 1.5rem !important; + color: #1e293b !important; + padding-top: 1rem !important; +} + +div:where(.swal2-container) .swal2-actions { + width: 100% !important; + margin-top: 2rem !important; + gap: 1rem !important; +} + +div:where(.swal2-container) button.swal2-confirm, +div:where(.swal2-container) button.swal2-cancel { + flex: 1 !important; + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + padding: 12px 24px !important; + border-radius: 12px !important; + font-weight: 600 !important; + font-size: 0.95rem !important; + transition: all 0.2s ease !important; + box-shadow: none !important; + border: none !important; + cursor: pointer !important; +} + +div:where(.swal2-container) button.swal2-confirm { + background-color: #ef4444 !important; + color: white !important; +} +div:where(.swal2-container) button.swal2-confirm:hover { + background-color: #dc2626 !important; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3) !important; +} + +div:where(.swal2-container) button.swal2-cancel { + background-color: #f1f5f9 !important; + color: #64748b !important; +} +div:where(.swal2-container) button.swal2-cancel:hover { + background-color: #e2e8f0 !important; + color: #475569 !important; + transform: translateY(-2px); +} + +div:where(.swal2-icon) { + border-width: 3px !important; + transform: scale(0.9); +} diff --git a/resources/js/app.js b/resources/js/app.js new file mode 100644 index 00000000..71ff348e --- /dev/null +++ b/resources/js/app.js @@ -0,0 +1,4 @@ +import './bootstrap'; +import * as Turbo from '@hotwired/turbo'; + +Turbo.start(); diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js new file mode 100644 index 00000000..5f1390b0 --- /dev/null +++ b/resources/js/bootstrap.js @@ -0,0 +1,4 @@ +import axios from 'axios'; +window.axios = axios; + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; diff --git a/resources/lang/id/validation.php b/resources/lang/id/validation.php new file mode 100644 index 00000000..7b3ae0f9 --- /dev/null +++ b/resources/lang/id/validation.php @@ -0,0 +1,139 @@ + ':attribute harus diterima.', + 'active_url' => ':attribute bukan URL yang valid.', + 'after' => ':attribute harus tanggal setelah :date.', + 'after_or_equal' => ':attribute harus tanggal setelah atau sama dengan :date.', + 'alpha' => ':attribute hanya boleh berisi huruf.', + 'alpha_dash' => ':attribute hanya boleh berisi huruf, angka, strip, dan garis bawah.', + 'alpha_num' => ':attribute hanya boleh berisi huruf dan angka.', + 'array' => ':attribute harus berupa array.', + 'before' => ':attribute harus tanggal sebelum :date.', + 'before_or_equal' => ':attribute harus tanggal sebelum atau sama dengan :date.', + 'between' => [ + 'numeric' => ':attribute harus antara :min dan :max.', + 'file' => ':attribute harus antara :min dan :max kilobyte.', + 'string' => ':attribute harus antara :min dan :max karakter.', + 'array' => ':attribute harus antara :min dan :max item.', + ], + 'boolean' => ':attribute harus bernilai true atau false.', + 'confirmed' => 'Konfirmasi :attribute tidak cocok.', + 'date' => ':attribute bukan format tanggal yang valid.', + 'date_equals' => ':attribute harus tanggal yang sama dengan :date.', + 'date_format' => ':attribute tidak sesuai format :format.', + 'different' => ':attribute dan :other harus berbeda.', + 'digits' => ':attribute harus :digits digit.', + 'digits_between' => ':attribute harus antara :min dan :max digit.', + 'email' => ':attribute harus alamat email yang valid.', + 'exists' => ':attribute yang dipilih tidak valid.', + 'file' => ':attribute harus berupa file.', + 'filled' => ':attribute wajib diisi.', + 'gt' => [ + 'numeric' => ':attribute harus lebih besar dari :value.', + 'file' => ':attribute harus lebih besar dari :value kilobyte.', + 'string' => ':attribute harus lebih dari :value karakter.', + 'array' => ':attribute harus memiliki lebih dari :value item.', + ], + 'gte' => [ + 'numeric' => ':attribute harus lebih besar atau sama dengan :value.', + 'file' => ':attribute harus lebih besar atau sama dengan :value kilobyte.', + 'string' => ':attribute harus lebih dari atau sama dengan :value karakter.', + 'array' => ':attribute harus memiliki :value item atau lebih.', + ], + 'image' => ':attribute harus berupa gambar.', + 'in' => ':attribute yang dipilih tidak valid.', + 'in_array' => ':attribute tidak ada dalam :other.', + 'integer' => ':attribute harus berupa bilangan bulat.', + 'ip' => ':attribute harus alamat IP yang valid.', + 'json' => ':attribute harus berupa JSON string yang valid.', + 'lt' => [ + 'numeric' => ':attribute harus kurang dari :value.', + 'file' => ':attribute harus kurang dari :value kilobyte.', + 'string' => ':attribute harus kurang dari :value karakter.', + 'array' => ':attribute harus memiliki kurang dari :value item.', + ], + 'lte' => [ + 'numeric' => ':attribute harus kurang dari atau sama dengan :value.', + 'file' => ':attribute harus kurang dari atau sama dengan :value kilobyte.', + 'string' => ':attribute harus kurang dari atau sama dengan :value karakter.', + 'array' => ':attribute tidak boleh memiliki lebih dari :value item.', + ], + 'max' => [ + 'numeric' => ':attribute tidak boleh lebih dari :max.', + 'file' => ':attribute tidak boleh lebih dari :max kilobyte.', + 'string' => ':attribute tidak boleh lebih dari :max karakter.', + 'array' => ':attribute tidak boleh memiliki lebih dari :max item.', + ], + 'mimes' => ':attribute harus berupa file bertipe: :values.', + 'mimetypes' => ':attribute harus berupa file bertipe: :values.', + 'min' => [ + 'numeric' => ':attribute minimal :min.', + 'file' => ':attribute minimal :min kilobyte.', + 'string' => ':attribute minimal :min karakter.', + 'array' => ':attribute minimal harus memiliki :min item.', + ], + 'not_in' => ':attribute yang dipilih tidak valid.', + 'numeric' => ':attribute harus berupa angka.', + 'present' => ':attribute wajib ada.', + 'regex' => 'Format :attribute tidak valid.', + 'required' => ':attribute wajib diisi.', + 'required_if' => ':attribute wajib diisi ketika :other bernilai :value.', + 'required_unless' => ':attribute wajib diisi kecuali :other bernilai :values.', + 'required_with' => ':attribute wajib diisi ketika :values ada.', + 'required_with_all' => ':attribute wajib diisi ketika :values ada.', + 'required_without' => ':attribute wajib diisi ketika :values tidak ada.', + 'required_without_all' => ':attribute wajib diisi ketika tidak ada :values yang ada.', + 'same' => ':attribute dan :other harus sama.', + 'size' => [ + 'numeric' => ':attribute harus :size.', + 'file' => ':attribute harus :size kilobyte.', + 'string' => ':attribute harus :size karakter.', + 'array' => ':attribute harus memiliki :size item.', + ], + 'string' => ':attribute harus berupa string.', + 'unique' => ':attribute sudah digunakan.', + 'url' => 'Format :attribute tidak valid.', + + 'attributes' => [ + 'email' => 'email', + 'password' => 'kata sandi', + 'nama_lengkap' => 'nama lengkap', + 'nik' => 'NIK', + 'no_telp' => 'nomor telepon', + 'alamat' => 'alamat', + 'id_divisi' => 'divisi', + 'id_jabatan' => 'jabatan', + 'id_kantor' => 'kantor', + 'id_role' => 'role', + 'id_shift' => 'shift', + 'id_user' => 'pegawai', + 'id_user_1' => 'pegawai pertama', + 'id_user_2' => 'pegawai kedua', + 'id_jadwal_1' => 'jadwal pertama', + 'id_jadwal_2' => 'jadwal kedua', + 'tanggal' => 'tanggal', + 'tanggal_mulai' => 'tanggal mulai', + 'tanggal_selesai' => 'tanggal selesai', + 'jam_masuk' => 'jam masuk', + 'jam_pulang' => 'jam pulang', + 'alasan' => 'alasan', + 'alasan_telat' => 'catatan koreksi', + 'keterangan' => 'keterangan', + 'bukti_file' => 'bukti dokumen', + 'foto' => 'foto', + 'nama_kantor' => 'nama kantor', + 'nama_divisi' => 'nama divisi', + 'nama_jabatan' => 'nama jabatan', + 'nama_shift' => 'nama shift', + 'latitude' => 'latitude', + 'longitude' => 'longitude', + 'radius' => 'radius', + 'sisa_cuti' => 'sisa cuti', + 'presensi_ids' => 'data presensi', + 'action' => 'aksi', + 'user_ids' => 'daftar pegawai', + 'id_jenis_izin' => 'jenis izin', + 'id_status' => 'status', + ], +]; diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php new file mode 100644 index 00000000..e4b950cc --- /dev/null +++ b/resources/views/auth/login.blade.php @@ -0,0 +1,165 @@ + + + + + + Login Administrator - Absensi Digital + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + + + + + + +
+ + + +
+
+ +
+

Sign in

+

+ Masuk menggunakan akun administrator Anda. +

+
+ +
+ @csrf + +
+ +
+ +
+
+ +
+ +
+ + + +
+
+ +
+ +
+ +
+ +
+

+ © {{ date('Y') }} Menjadi Pengaruh Group. All rights reserved. +

+
+ +
+
+
+ +
+
+ + + + + + + diff --git a/resources/views/components/action-button.blade.php b/resources/views/components/action-button.blade.php new file mode 100644 index 00000000..642c368b --- /dev/null +++ b/resources/views/components/action-button.blade.php @@ -0,0 +1,23 @@ +@props(['href' => null, 'type' => 'button', 'color' => 'blue']) + +@php + $colors = [ + 'blue' => 'bg-blue-50 text-blue-600 hover:bg-blue-100 border-blue-200', + 'yellow' => 'bg-yellow-50 text-yellow-600 hover:bg-yellow-100 border-yellow-200', + 'red' => 'bg-red-50 text-red-600 hover:bg-red-100 border-red-200', + 'slate' => 'bg-slate-50 text-slate-600 hover:bg-slate-100 border-slate-200', + ]; + + $style = $colors[$color] ?? $colors['blue']; + $baseClass = "p-2 rounded-lg transition border flex items-center justify-center"; +@endphp + +@if($href) + merge(['class' => "$baseClass $style"]) }}> + {{ $slot }} + +@else + +@endif diff --git a/resources/views/components/alert.blade.php b/resources/views/components/alert.blade.php new file mode 100644 index 00000000..3ea19eaf --- /dev/null +++ b/resources/views/components/alert.blade.php @@ -0,0 +1,37 @@ +@props(['type' => 'info', 'title' => null]) + +@php + $variants = [ + 'info' => 'bg-blue-50 border-blue-100 text-blue-800 icon-blue-600', + + 'danger' => 'bg-red-50 border-red-100 text-red-800 icon-red-600', + + 'success' => 'bg-emerald-50 border-emerald-100 text-emerald-800 icon-emerald-600', + + 'warning' => 'bg-orange-50 border-orange-100 text-orange-800 icon-orange-600', + ]; + + $style = $variants[$type] ?? $variants['info']; + +@endphp + +
merge(['class' => "p-4 rounded-lg border flex items-start gap-3 $style"]) }}> +
+ @if($type == 'danger') + + @elseif($type == 'success') + + @else + + @endif +
+ +
+ @if($title) +

{{ $title }}

+ @endif +
+ {{ $slot }} +
+
+
diff --git a/resources/views/components/avatar.blade.php b/resources/views/components/avatar.blade.php new file mode 100644 index 00000000..9a598aee --- /dev/null +++ b/resources/views/components/avatar.blade.php @@ -0,0 +1,18 @@ +@props(['name' => 'User', 'image' => null]) + +@php + $initials = collect(explode(' ', $name)) + ->map(function ($segment) { + return strtoupper(substr($segment, 0, 1)); + }) + ->take(2) + ->join(''); +@endphp + +
merge(['class' => 'w-10 h-10 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center overflow-hidden shrink-0']) }}> + @if($image) + {{ $name }} + @else + {{ $initials }} + @endif +
diff --git a/resources/views/components/back-button.blade.php b/resources/views/components/back-button.blade.php new file mode 100644 index 00000000..e083e888 --- /dev/null +++ b/resources/views/components/back-button.blade.php @@ -0,0 +1,11 @@ +@props(['href']) + +merge(['class' => 'btn-back inline-flex items-center px-4 py-2 text-sm font-semibold text-slate-600 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 transition-all shadow-sm cursor-pointer']) }}> + + + + + + {{ $slot->isEmpty() ? 'Kembali' : $slot }} + diff --git a/resources/views/components/badge.blade.php b/resources/views/components/badge.blade.php new file mode 100644 index 00000000..23210324 --- /dev/null +++ b/resources/views/components/badge.blade.php @@ -0,0 +1,21 @@ +@props(['color' => 'gray']) + +@php + $colors = [ + 'blue' => 'bg-blue-50 text-blue-700 border-blue-200 ring-blue-600/10', + 'green' => 'bg-emerald-50 text-emerald-700 border-emerald-200 ring-emerald-600/10', + 'red' => 'bg-red-50 text-red-700 border-red-200 ring-red-600/10', + 'yellow' => 'bg-amber-50 text-amber-700 border-amber-200 ring-amber-600/10', + 'purple' => 'bg-violet-50 text-violet-700 border-violet-200 ring-violet-600/10', + 'orange' => 'bg-orange-50 text-orange-700 border-orange-200 ring-orange-600/10', + 'cyan' => 'bg-cyan-50 text-cyan-700 border-cyan-200 ring-cyan-600/10', + 'gray' => 'bg-slate-50 text-slate-600 border-slate-200 ring-slate-600/10', + 'navy' => 'bg-[#130F26]/5 text-[#130F26] border-[#130F26]/10 ring-[#130F26]/10', + ]; + + $style = $colors[$color] ?? $colors['gray']; +@endphp + +merge(['class' => "inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-semibold border ring-1 ring-inset $style"]) }}> + {{ $slot }} + diff --git a/resources/views/components/breadcrumb.blade.php b/resources/views/components/breadcrumb.blade.php new file mode 100644 index 00000000..2124ccc7 --- /dev/null +++ b/resources/views/components/breadcrumb.blade.php @@ -0,0 +1,67 @@ +@php + $segments = request()->segments(); + $url = ''; + + $titles = [ + 'dashboard' => 'Dashboard', + 'presensi' => 'Monitoring Presensi', + 'izin' => 'Manajemen Izin', + 'laporan' => 'Laporan Rekap', + 'pegawai' => 'Data Karyawan', + 'jadwal' => 'Penjadwalan', + 'face-approval' => 'Verifikasi Wajah', + 'lembur' => 'Lembur', + 'penggunaan-poin' => 'Persetujuan Poin', + 'pengumuman' => 'Pengumuman', + 'kantor' => 'Kantor', + 'divisi' => 'Divisi', + 'jabatan' => 'Jabatan', + 'role' => 'Role', + 'shift' => 'Shift Kerja' + ]; +@endphp + +@if(count($segments) > 0 && $segments[0] !== 'dashboard') + +@endif diff --git a/resources/views/components/button-edit.blade.php b/resources/views/components/button-edit.blade.php new file mode 100644 index 00000000..4d42c1bf --- /dev/null +++ b/resources/views/components/button-edit.blade.php @@ -0,0 +1,17 @@ +@props(['href' => null]) + +@if($href) + + merge(['class' => 'p-2 bg-amber-50 text-amber-600 hover:bg-amber-100 rounded-lg transition border border-amber-200 group inline-flex items-center justify-center']) }} title="Edit"> + + + + +@else + + +@endif diff --git a/resources/views/components/button.blade.php b/resources/views/components/button.blade.php new file mode 100644 index 00000000..5eb04edc --- /dev/null +++ b/resources/views/components/button.blade.php @@ -0,0 +1,24 @@ +@props(['type' => 'submit', 'variant' => 'primary', 'href' => null]) + +@php + $baseClass = "px-6 py-2.5 rounded-xl font-medium transition-all duration-300 inline-flex items-center justify-center cursor-pointer text-sm md:text-base hover:-translate-y-0.5 active:scale-95 active:translate-y-0"; + + $variants = [ + 'primary' => "bg-gradient-to-r from-[#130F26] to-[#2a244a] text-white hover:shadow-[var(--shadow-glow)] border border-transparent", + 'secondary' => "bg-white border border-slate-200 text-slate-700 hover:bg-slate-50 hover:shadow-sm", + + 'danger' => "bg-gradient-to-r from-red-600 to-red-500 text-white hover:shadow-[0_4px_15px_-3px_rgba(220,38,38,0.4)] border border-transparent", + ]; + + $class = $baseClass . ' ' . ($variants[$variant] ?? $variants['primary']); +@endphp + +@if($href) + merge(['class' => $class]) }}> + {{ $slot }} + +@else + +@endif diff --git a/resources/views/components/checkbox.blade.php b/resources/views/components/checkbox.blade.php new file mode 100644 index 00000000..c25de6da --- /dev/null +++ b/resources/views/components/checkbox.blade.php @@ -0,0 +1,39 @@ +@props([ + 'name' => '', + 'value' => '', + 'label' => '', + 'id' => null, + 'checked' => false, + 'disabled' => false, +]) + +@php + $inputId = $id ?? ($name . '_' . $value); +@endphp + + diff --git a/resources/views/components/date-input.blade.php b/resources/views/components/date-input.blade.php new file mode 100644 index 00000000..2d1031d3 --- /dev/null +++ b/resources/views/components/date-input.blade.php @@ -0,0 +1,36 @@ +@props(['label' => null, 'name', 'value' => '']) + +
+ @if($label) + + @endif + +
+ + + merge(['class' => 'w-full pl-4 pr-10 py-2.5 bg-white border border-slate-300 rounded-xl text-sm outline-none focus:ring-2 focus:ring-[#130F26] focus:border-[#130F26] transition-all text-slate-600 cursor-pointer appearance-none']) }} + > + +
+ + + +
+
+ + @error($name) +

{{ $message }}

+ @enderror +
diff --git a/resources/views/components/delete-button.blade.php b/resources/views/components/delete-button.blade.php new file mode 100644 index 00000000..5cc3e5df --- /dev/null +++ b/resources/views/components/delete-button.blade.php @@ -0,0 +1,9 @@ +@props(['id']) + + diff --git a/resources/views/components/empty-state.blade.php b/resources/views/components/empty-state.blade.php new file mode 100644 index 00000000..957445fb --- /dev/null +++ b/resources/views/components/empty-state.blade.php @@ -0,0 +1,16 @@ +@props(['message' => 'Data tidak ditemukan', 'hint' => 'Coba ubah kata kunci pencarian atau filter.']) + + + +
+ + + + +

{{ $message }}

+ @if($hint) +

{{ $hint }}

+ @endif +
+ + diff --git a/resources/views/components/filter-select.blade.php b/resources/views/components/filter-select.blade.php new file mode 100644 index 00000000..9c8555b7 --- /dev/null +++ b/resources/views/components/filter-select.blade.php @@ -0,0 +1,14 @@ +@props(['id' => null, 'name' => null]) + +
+ + +
+ + + +
+
diff --git a/resources/views/components/image-upload.blade.php b/resources/views/components/image-upload.blade.php new file mode 100644 index 00000000..d57d6eb2 --- /dev/null +++ b/resources/views/components/image-upload.blade.php @@ -0,0 +1,48 @@ +@props(['name', 'label' => 'Foto Profil', 'default' => null, 'placeholder' => null]) + +
+

{{ $label }}

+ +
+
+ + + + +
+ +
+ + +
+ + @error($name) +

{{ $message }}

+ @enderror +

Format: JPG, PNG (Maks. 5MB)

+
+
diff --git a/resources/views/components/input.blade.php b/resources/views/components/input.blade.php new file mode 100644 index 00000000..82c322b5 --- /dev/null +++ b/resources/views/components/input.blade.php @@ -0,0 +1,32 @@ +@props(['label' => null, 'name', 'type' => 'text', 'placeholder' => '', 'value' => '', 'disabled' => false]) + +@php + $id = $attributes->get('id', $name); +@endphp + +
only('class')->merge(['class' => 'w-full mb-4']) }}> + @if($label) + + @endif + + except(['id', 'name', 'type', 'value', 'placeholder', 'class'])->merge([ + 'class' => 'w-full px-4 py-2 border border-slate-200 bg-slate-50 rounded-xl focus:bg-white focus:ring-4 focus:ring-[#130F26]/10 focus:border-[#130F26] outline-none transition-all duration-300 placeholder-slate-400 disabled:bg-slate-100 disabled:text-slate-500 h-11' + ]) !!} + > + + @error($name) +

+ + {{ $message }} +

+ @enderror +
diff --git a/resources/views/components/modal.blade.php b/resources/views/components/modal.blade.php new file mode 100644 index 00000000..d09a96c1 --- /dev/null +++ b/resources/views/components/modal.blade.php @@ -0,0 +1,48 @@ +@props(['name', 'title']) + + diff --git a/resources/views/components/page-header.blade.php b/resources/views/components/page-header.blade.php new file mode 100644 index 00000000..c3d9b147 --- /dev/null +++ b/resources/views/components/page-header.blade.php @@ -0,0 +1,11 @@ +
merge(['class' => 'flex flex-col md:flex-row md:items-center justify-between gap-4']) }}> +
+

{{ $title }}

+ @if(isset($subtitle)) +

{{ $subtitle }}

+ @endif +
+
+ {{ $slot }} +
+
diff --git a/resources/views/components/pagination.blade.php b/resources/views/components/pagination.blade.php new file mode 100644 index 00000000..12ac6027 --- /dev/null +++ b/resources/views/components/pagination.blade.php @@ -0,0 +1,42 @@ +@props(['paginator']) + +@if($paginator->hasPages()) +
+
+ + + +
+ + @if ($paginator->onFirstPage()) + + ← Previous + + @else + + ← Previous + + @endif + + @if ($paginator->hasMorePages()) + + Next → + + @else + + Next → + + @endif + +
+
+
+@endif diff --git a/resources/views/components/search-input.blade.php b/resources/views/components/search-input.blade.php new file mode 100644 index 00000000..c3fca596 --- /dev/null +++ b/resources/views/components/search-input.blade.php @@ -0,0 +1,15 @@ +@props(['name' => 'search', 'placeholder' => 'Cari data...', 'value' => '']) + +
only('class')->merge(['class' => 'relative w-full lg:w-64']) }}> + except('class')->merge(['class' => 'w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition h-10']) }}> + + +
diff --git a/resources/views/components/select.blade.php b/resources/views/components/select.blade.php new file mode 100644 index 00000000..1706667e --- /dev/null +++ b/resources/views/components/select.blade.php @@ -0,0 +1,36 @@ +@props(['label' => null, 'name' => null, 'value' => '']) + +@php + $id = $attributes->get('id', $name); +@endphp + +
only('class')->merge(['class' => 'relative w-full']) }}> + @if($label) + + @endif + +
+ + +
+ + + +
+
+ + @if($name) + @error($name) +

{{ $message }}

+ @enderror + @endif +
diff --git a/resources/views/components/stat-card.blade.php b/resources/views/components/stat-card.blade.php new file mode 100644 index 00000000..aa61dc84 --- /dev/null +++ b/resources/views/components/stat-card.blade.php @@ -0,0 +1,34 @@ +@props(['label'=>'', 'value', 'unit' => '', 'color' => 'blue']) + +@php + $colors = [ + 'indigo' => 'bg-indigo-50 text-indigo-600', + 'purple' => 'bg-purple-50 text-purple-600', + 'blue' => 'bg-blue-50 text-blue-600', + 'emerald' => 'bg-emerald-50 text-emerald-600', + 'orange' => 'bg-orange-50 text-orange-600', + 'red' => 'bg-red-50 text-red-600', + ]; + + $colorClass = $colors[$color] ?? $colors['blue']; +@endphp + +
+ +
+ +
+
+ {{ $slot }} +
+ {{ $label }} +
+
+

+ {{ $value }} + @if($unit) + {{ $unit }} + @endif +

+
+
diff --git a/resources/views/components/table.blade.php b/resources/views/components/table.blade.php new file mode 100644 index 00000000..1e81be19 --- /dev/null +++ b/resources/views/components/table.blade.php @@ -0,0 +1,34 @@ +
+
+ + + + {{ $header }} + + + + + {{ $slot }} + +
+
+
+ + diff --git a/resources/views/components/textarea.blade.php b/resources/views/components/textarea.blade.php new file mode 100644 index 00000000..4fe6910a --- /dev/null +++ b/resources/views/components/textarea.blade.php @@ -0,0 +1,23 @@ +@props(['label' => null, 'name', 'value' => '']) + +@php + $id = $attributes->get('id', $name); +@endphp + +
+ @if($label) + + @endif + + + + @error($name) +

{{ $message }}

+ @enderror +
diff --git a/resources/views/cuti/index.blade.php b/resources/views/cuti/index.blade.php new file mode 100644 index 00000000..cb9b7327 --- /dev/null +++ b/resources/views/cuti/index.blade.php @@ -0,0 +1,201 @@ +@extends('layouts.app') + +@section('title', 'Manajemen Sisa Cuti') + +@section('content') +
+ + + +
+ +
+
+
+
+ + + +
+ +
+
+ + +
+ + + + Pegawai + Sisa Cuti + Aksi + + + @forelse ($pegawai as $p) + + +
+ +
+
{{ $p->nama_lengkap }}
+
{{ $p->nik ?? '-' }} • {{ $p->jabatan->nama_jabatan ?? '-' }}
+
+
+ + + + {{ $p->sisa_cuti }} Hari + + + + + + + @empty + + @endforelse +
+ + @if($pegawai->hasPages()) +
+ {{ $pegawai->links() }} +
+ @endif +
+
+ + + + +@endsection + +@section('script') + +@endsection diff --git a/resources/views/dashboard/index.blade.php b/resources/views/dashboard/index.blade.php new file mode 100644 index 00000000..35ac5079 --- /dev/null +++ b/resources/views/dashboard/index.blade.php @@ -0,0 +1,270 @@ +@extends('layouts.app') + +@section('title', 'Dashboard') + +@section('content') +
+

+ {{ \Carbon\Carbon::now('Asia/Jakarta')->hour < 12 ? 'Selamat Pagi' : (\Carbon\Carbon::now('Asia/Jakarta')->hour < 15 ? 'Selamat Siang' : 'Selamat Sore') }}, + {{ Auth::user()->nama_lengkap }}! 👋 +

+

+ {{ \Carbon\Carbon::now('Asia/Jakarta')->translatedFormat('l, d F Y') }} +

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

Tren Kehadiran (7 Hari Terakhir)

+
+ +
+
+ +
+ +
+
+

+ + + + + Menunggu Persetujuan Izin/Cuti +

+ Lihat + Semua +
+
+ @forelse($pengajuanPending['izin'] as $izin) +
+
+
+ {{ substr($izin->user->nama_lengkap ?? 'U', 0, 2) }} +
+
+

{{ $izin->user->nama_lengkap }}

+

+ {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->format('d M') }} - + {{ \Carbon\Carbon::parse($izin->tanggal_selesai)->format('d M Y') }} + {{ $izin->jenisIzin->nama_izin ?? 'Izin' }} +

+
+
+ Pending +
+ @empty +
+ Tidak ada pengajuan izin/cuti yang pending. +
+ @endforelse +
+
+ +
+
+

+ + + + + Menunggu Persetujuan Surat Izin +

+ Lihat Semua +
+
+ @forelse($pengajuanPending['surat_izin'] ?? [] as $surat) +
+
+
+ {{ substr($surat->user->nama_lengkap ?? 'U', 0, 2) }} +
+
+

{{ $surat->user->nama_lengkap }}

+

+ Tanggal: {{ \Carbon\Carbon::parse($surat->tanggal)->translatedFormat('d F Y') }} + Keperluan: {{ $surat->keperluan }} +

+
+
+ Pending +
+ @empty +
+ Tidak ada pengajuan surat izin yang pending. +
+ @endforelse +
+
+ +
+ +
+
+

+ + + + Lembur +

+ Lihat +
+
+ @forelse($pengajuanPending['lembur'] as $lembur) +
+

{{ $lembur->user->nama_lengkap }}

+

+ {{ \Carbon\Carbon::parse($lembur->tanggal_lembur)->format('d M Y') }} + ({{ $lembur->durasi_menit }} menit) +

+
+ @empty +
Tidak ada lembur pending.
+ @endforelse +
+
+ +
+
+

+ + + + + Dispensasi Poin +

+ Lihat +
+
+ @forelse($pengajuanPending['poin'] as $poin) +
+

{{ $poin->user->nama_lengkap }}

+

+ {{ \Carbon\Carbon::parse($poin->tanggal_penggunaan)->format('d M Y') }} + (-{{ $poin->jumlah_poin }} poin) +

+
+ @empty +
Tidak ada dispensasi poin.
+ @endforelse +
+
+
+ + @if($pengajuanPending['presensi']->count() > 0) +
+
+

+ + + + + Butuh Validasi Absen (Luar Radius) +

+ Proses + Sekarang +
+
+ @foreach($pengajuanPending['presensi'] as $presensi) +
+
+

{{ $presensi->user->nama_lengkap }}

+

+ {{ \Carbon\Carbon::parse($presensi->tanggal)->format('d M Y') }} • + {{ \Carbon\Carbon::parse($presensi->jam_masuk)->format('H:i') }} +

+
+
+ @endforeach +
+
+ @endif +
+
+@endsection + +@push('scripts') + + +@endpush diff --git a/resources/views/divisi/index.blade.php b/resources/views/divisi/index.blade.php new file mode 100644 index 00000000..a7fd9780 --- /dev/null +++ b/resources/views/divisi/index.blade.php @@ -0,0 +1,119 @@ +@extends('layouts.app') + +@section('title', 'Data Divisi') + +@section('content') +
+ + + + + + +
+ + + No + Nama Divisi + + Aksi + + + @forelse($divisi as $key => $d) + + + + {{ $divisi->firstItem() + $key }} + + + + {{ $d->nama_divisi }} + + + +
+ + + + + + + +
+ + + @empty + + @endforelse +
+ + +
+
+ + +
+ @csrf +
+ +
+
+ + +
+
+
+ + +
+ @csrf + @method('PUT') + + + +
+ + + +
+ +
+ + +
+
+
+ + + +@endsection diff --git a/resources/views/face-approval/index.blade.php b/resources/views/face-approval/index.blade.php new file mode 100644 index 00000000..69a6a8a2 --- /dev/null +++ b/resources/views/face-approval/index.blade.php @@ -0,0 +1,151 @@ +@extends('layouts.app') + +@section('title', 'Verifikasi Wajah') + +@section('content') +
+ + +
+ + + Karyawan + Divisi / Jabatan + Preview Foto + Status Saat Ini + Aksi + + + @forelse ($users as $user) + + +
+ @if($user->foto) + + @else + + @endif +
+
{{ $user->nama_lengkap }}
+
{{ $user->email }}
+
+
+ + +
{{ $user->divisi->nama_divisi ?? '-' }}
+
{{ $user->jabatan->nama_jabatan ?? '-' }}
+ + +
+ @php + $poses = ['depan' => 'Depan', 'kanan' => 'Kanan', 'kiri' => 'Kiri', 'bawah' => 'Bawah']; + @endphp + @foreach($poses as $poseKey => $poseLabel) + @if(isset($user->face_photos[$poseKey])) +
+ + {{ $poseLabel }} +
+ @else +
+ + + + {{ $poseLabel }} +
+ @endif + @endforeach +
+ + + + Menunggu Verifikasi + + + +
+ + + + + +
+ + + @empty + + @endforelse +
+
+
+ + +@endsection + +@section('script') + +@endsection diff --git a/resources/views/hari_libur/index.blade.php b/resources/views/hari_libur/index.blade.php new file mode 100644 index 00000000..e6195a69 --- /dev/null +++ b/resources/views/hari_libur/index.blade.php @@ -0,0 +1,250 @@ +@extends('layouts.app') + +@section('title', 'Data Hari Libur') + +@section('style') + + +@endsection + +@section('content') +
+ + +
+
+ + + @foreach($kantors as $k) + + @endforeach + +
+
+ @csrf + + + Sinkronisasi ({{ date('Y') }}) + +
+ + + + + + Tambah Hari Libur + +
+
+ +
+

+ + Kalender Libur Nasional & Perusahaan +

+
+
+ +
+ + + No + Tanggal + Kantor + Keterangan + Aksi + + + @forelse($hariLiburs as $key => $hl) + + + {{ $hariLiburs->firstItem() + $key }} + + + {{ \Carbon\Carbon::parse($hl->tanggal)->translatedFormat('d F Y') }} + + + {!! $hl->id_kantor ? 'Khusus: ' . $hl->kantor->nama_kantor . '' : 'Global' !!} + + + {{ $hl->keterangan }} + + +
+ + + + + +
+ + + @empty + + @endforelse +
+ + +
+
+ + +
+ @csrf +
+ +
+
+ + + @foreach($kantors as $k) + + @endforeach + +
+
+ +
+
+ Batal + Simpan +
+
+
+ + +
+ @csrf + @method('PUT') + + + +
+ +
+ +
+ + + @foreach($kantors as $k) + + @endforeach + +
+ +
+ +
+ +
+ Batal + Update +
+
+
+ + +@endsection diff --git a/resources/views/izin/index.blade.php b/resources/views/izin/index.blade.php new file mode 100644 index 00000000..213899a4 --- /dev/null +++ b/resources/views/izin/index.blade.php @@ -0,0 +1,384 @@ +@extends('layouts.app') + +@section('title', 'Manajemen Izin & Cuti') + +@section('content') +
+ + +
+ + + + + Buat Izin + + +
+ + + + + + + + + + + + + + + +
+
+ +
+ + + Pegawai + Jenis Izin + Tanggal + Status + Aksi + + + @forelse($izin as $item) + + +
+
+ {{ substr($item->user->nama_lengkap ?? 'U', 0, 2) }} +
+
+
{{ $item->user->nama_lengkap ?? '-' }}
+
{{ $item->user->nik ?? '-' }}
+
+
+ + + + + {{ $item->jenisIzin->nama_izin ?? 'Unknown' }} + + + +
+ {{ \Carbon\Carbon::parse($item->tanggal_mulai)->format('d M Y') }} + s/d + {{ \Carbon\Carbon::parse($item->tanggal_selesai)->format('d M Y') }} +
+ + + @php + $statusName = $item->statusPengajuan->nama_status ?? '-'; + $badgeColor = match ($item->id_status) { + 2 => 'green', + 3 => 'red', + default => 'yellow' + }; + @endphp + @if($item->id_jenis_izin == 2 && $item->id_status == 1) + Via Surat Izin + @else + {{ $statusName }} + @endif + + +
+ + @if($item->id_jenis_izin == 2 && $item->suratIzin) + + Lihat Surat → + + @endif +
+ + + @empty + + @endforelse +
+
+ + +
+ + +
+ @csrf + +
+ + + @foreach($kantor as $k) + + @endforeach + + + + + @foreach($divisi as $d) + + @endforeach + +
+ +
+ + + @foreach($users as $user) + + @endforeach + +
+ +
+ + @foreach($jenisIzin as $j) + + @endforeach + +
+ +
+ + +
+ +
+ +
+ + + +
+ Batal + + + Simpan Pengajuan + +
+ +
+ + +
+ +
+
+

Nama Pegawai

+

Tanggal...

+
+ Jenis +
+ +
+ +

... +

+
+ +
+ +
+ + + Tidak ada lampiran +
+
+ +
+ + + +
+ Tutup + + @auth + + + + + + Tolak + + + Setujui + + @endauth +
+
+
+
+ + + +@endsection + +@section('script') + +@endsection diff --git a/resources/views/jabatan/index.blade.php b/resources/views/jabatan/index.blade.php new file mode 100644 index 00000000..5fa83352 --- /dev/null +++ b/resources/views/jabatan/index.blade.php @@ -0,0 +1,114 @@ +@extends('layouts.app') + +@section('title', 'Data Jabatan') + +@section('content') +
+ + + + + +
+ + + No + Nama Jabatan + + Aksi + + + @forelse($jabatan as $key => $j) + + + {{ $jabatan->firstItem() + $key }} + + + {{ $j->nama_jabatan }} + + +
+ + + + + + + +
+ + + @empty + + @endforelse +
+ +
+
+ + +
+ @csrf +
+ +
+
+ + +
+
+
+ + +
+ @csrf + @method('PUT') + + + +
+ + +
+ +
+ + +
+
+
+ + + +@endsection diff --git a/resources/views/jadwal/create.blade.php b/resources/views/jadwal/create.blade.php new file mode 100644 index 00000000..bd477193 --- /dev/null +++ b/resources/views/jadwal/create.blade.php @@ -0,0 +1,259 @@ +@extends('layouts.app') + +@section('title', 'Generate Jadwal Kerja') + +@section('content') +
+ + + + + +
+
+ @csrf + +
+ + + + + @foreach($shifts as $shift) + + @endforeach + +
+ +
+
+ + +
+ + + @foreach($kantor as $k) + + @endforeach + + + + + @foreach($divisi as $d) + + @endforeach + +
+
+ +
+ + (0 pegawai dipilih) +
+ +
+ @foreach($pegawai as $p) + + @endforeach +
+ + @error('user_ids') +

{{ $message }}

+ @enderror +
+ +
+ Batal + +
+
+
+
+@endsection + +@section('script') + + +@endsection diff --git a/resources/views/jadwal/index.blade.php b/resources/views/jadwal/index.blade.php new file mode 100644 index 00000000..49744601 --- /dev/null +++ b/resources/views/jadwal/index.blade.php @@ -0,0 +1,835 @@ +@extends('layouts.app') + +@section('title', 'Penjadwalan Shift') + +@section('style') + + + +@endsection + +@section('content') +
+ + + + + @foreach($kantor as $k) + + @endforeach + + + + + @foreach($divisi as $d) + + @endforeach + + +
+ + + + + Hapus Massal + + + + + + Tukar Shift + + + + + + Generator + +
+
+ +
+ Keterangan Shift: + @foreach($shifts as $shift) +
+ + {{ $shift->nama_shift }} +
+ @endforeach +
+ + Hari Libur +
+
+ +
+ +
+ +
+ +
+
+ + + + + Menyinkronkan Jadwal... +
+
+ + +
+ +
+
+
+

Daftar Jadwal Harian

+

Daftar pegawai yang bertugas pada tanggal tertentu.

+
+
+ +
+ + + + + + + + + + + + + +
Nama PegawaiShiftJam KerjaKantorAksi
Silakan klik salah satu tanggal pada kalender untuk melihat daftar jadwal.
+
+
+
+ + +
+ @method('PUT') +
+
+
Pegawai:
+
Kantor:
+
Jabatan:
+
Tanggal:
+
+ + + +
+ + +
+
+ +
+ +
+ Batal + Simpan Perubahan +
+
+ + + +
+
+ + +
+

+

Daftar lengkap pegawai yang bertugas.

+
+ +
+ +
+ +
+ Tutup +
+
+ + +
+ @csrf +
+
+

Hati-hati! Tindakan ini akan menghapus semua jadwal shift pegawai pada rentang tanggal yang dipilih dan tidak dapat dikembalikan.

+
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ @foreach($pegawai as $p) + + @endforeach +
+

+ + Centang pegawai yang ingin dihapus jadwalnya pada rentang tanggal di atas. +

+
+
+ +
+ Batal + Ya, Hapus Terpilih +
+
+
+@endsection + +@section('script') + + +@endsection diff --git a/resources/views/jadwal/test_isolation.blade.php b/resources/views/jadwal/test_isolation.blade.php new file mode 100644 index 00000000..b6d10e9e --- /dev/null +++ b/resources/views/jadwal/test_isolation.blade.php @@ -0,0 +1,29 @@ + + + + + + + + + + +

Test Calendar Isolation

+
+ + + diff --git a/resources/views/kantor/index.blade.php b/resources/views/kantor/index.blade.php new file mode 100644 index 00000000..0eea04a0 --- /dev/null +++ b/resources/views/kantor/index.blade.php @@ -0,0 +1,204 @@ +@extends('layouts.app') + +@section('title', 'Data Kantor') + +@section('style') + + + +@endsection + +@section('script') + + + + +@endsection + +@section('content') +
+ + + + +
+ + + Nama Kantor + Koordinat + Radius + Aksi + + + @forelse($kantor as $k) + + +
{{ $k->nama_kantor }}
+
+ + {{ $k->tipe }} + +
+ + + + {{ number_format($k->latitude, 5) }}, {{ number_format($k->longitude, 5) }} + + + + + {{ $k->radius }} m + + + +
+ + + + + +
+ + + @empty + + @endforelse +
+ +
+
+ + +
+ @csrf +
+ +
+
+
+ + + + + +
+
+ + + +
+ +
+ + +
+ +
+ + +
+ @csrf @method('PUT') +
+
+
+
+ + + + + +
+
+ + + +
+ +
+ + +
+ +
+ +
+
+
+
+ +

+
+
+ +

+
+
+
+
+ +

+
+
+ +

+
+
+ +

+
+
+
+ +

+
+
+ +
+
+
+@endsection diff --git a/resources/views/laporan-lembur/index.blade.php b/resources/views/laporan-lembur/index.blade.php new file mode 100644 index 00000000..f45a67cf --- /dev/null +++ b/resources/views/laporan-lembur/index.blade.php @@ -0,0 +1,121 @@ +@extends('layouts.app') + +@section('title', 'Laporan Lembur') + +@section('content') +
+ + + + + + + +
+ +
+
+
+ + @for($i=1; $i<=12; $i++) + @php $val = str_pad($i, 2, '0', STR_PAD_LEFT); @endphp + + @endfor + +
+ +
+ + @for($i=date('Y'); $i>=2023; $i--) + + @endfor + +
+ +
+ + + @foreach($divisiList as $div) + + @endforeach + +
+ +
+ +
+ +
+ + Filter + +
+
+
+ +
+ + + Pegawai + Jml Hari Lembur + Durasi (Menit / Jam) + Poin Diperoleh + + + @forelse($pegawai as $p) + + +
+ +
+
{{ $p->nama_lengkap }}
+
{{ $p->nik ?? '-' }} • {{ $p->divisi->nama_divisi ?? '-' }}
+
+
+ + + + {{ $rekap[$p->id]['jumlah_hari'] }} Hari + + + +
+ {{ number_format($rekap[$p->id]['total_menit'], 0, ',', '.') }} Menit + ≈ {{ $rekap[$p->id]['format_jam'] }} +
+ + + @if($rekap[$p->id]['poin_diperoleh'] > 0) + + +{{ number_format($rekap[$p->id]['poin_diperoleh'], 0, ',', '.') }} Poin + + @else + - + @endif + + + @empty + + @endforelse +
+
+ + @if($pegawai->hasPages()) +
+ {{ $pegawai->links() }} +
+ @endif + +
+
+@endsection diff --git a/resources/views/laporan/index.blade.php b/resources/views/laporan/index.blade.php new file mode 100644 index 00000000..eed3eef1 --- /dev/null +++ b/resources/views/laporan/index.blade.php @@ -0,0 +1,138 @@ +@extends('layouts.app') + +@section('title', 'Laporan Bulanan') + +@section('content') +
+ + + +
+ +
+
+ + @for ($i = 1; $i <= 12; $i++) + + @endfor + +
+
+ + @for ($y = date('Y'); $y >= date('Y') - 2; $y--) + + @endfor + +
+
+ + + @foreach($divisiList as $div) + + @endforeach + +
+
+ + + + + + Tampilkan + + + + + + Ekspor Excel + + + + + + + Ekspor PDF + +
+ +
+ + + + Karyawan + Divisi + Total Hadir + Izin + Sakit + Terlambat (Kali) + Poin Lembur + Alpha + + + @forelse ($rekap as $row) + + +
+ +
+
{{ $row['user']->nama_lengkap }}
+
{{ $row['user']->jabatan->nama_jabatan ?? '-' }}
+
+
+ + + {{ $row['user']->divisi->nama_divisi ?? '-' }} + + + {{ $row['hadir'] }} Hari + + + @if($row['izin'] > 0) + {{ $row['izin'] }}x + @else + - + @endif + + + @if($row['sakit'] > 0) + {{ $row['sakit'] }}x + @else + - + @endif + + + @if($row['terlambat'] > 0) + {{ $row['terlambat'] }}x + @else + - + @endif + + + {{ $row['poin_lembur'] }} Poin + + + @if($row['alpha'] > 0) + {{ $row['alpha'] }} Hari + @else + - + @endif + + + @empty + + @endforelse +
+
+
+@endsection diff --git a/resources/views/laporan/izin.blade.php b/resources/views/laporan/izin.blade.php new file mode 100644 index 00000000..b96daf94 --- /dev/null +++ b/resources/views/laporan/izin.blade.php @@ -0,0 +1,127 @@ +@extends('layouts.app') + +@section('title', 'Laporan Izin & Cuti') + +@section('content') +
+ + + +
+ + + Export Excel + +
+
+
+ +
+ +
+
+
+ + @for($i=1; $i<=12; $i++) + @php $val = str_pad($i, 2, '0', STR_PAD_LEFT); @endphp + + @endfor + +
+ +
+ + @for($i=date('Y'); $i>=2023; $i--) + + @endfor + +
+ +
+ + + @foreach($divisiList as $div) + + @endforeach + +
+ +
+ +
+ +
+ + Filter + +
+
+
+ +
+ + + Pegawai + Tanggal + Kategori + Alasan + Status + + + @forelse($izinList as $izin) + + +
+ +
+
{{ $izin->user->nama_lengkap ?? '-' }}
+
{{ $izin->user->divisi->nama_divisi ?? '-' }}
+
+
+ + + @if($izin->tanggal_mulai == $izin->tanggal_selesai) + {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->translatedFormat('d M Y') }} + @else + {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->format('d M') }} - {{ \Carbon\Carbon::parse($izin->tanggal_selesai)->translatedFormat('d M Y') }} + @endif + + + + {{ $izin->jenisIzin->nama_izin ?? 'Izin Khusus' }} + + + +
{{ $izin->alasan }}
+ + + @php + $color = 'yellow'; $label = 'Menunggu'; + if($izin->status == 'disetujui') { $color = 'green'; $label = 'Disetujui'; } + elseif($izin->status == 'ditolak') { $color = 'red'; $label = 'Ditolak'; } + @endphp + + {{ $label }} + + + + @empty + + @endforelse +
+
+ + @if($izinList->hasPages()) +
+ {{ $izinList->links() }} +
+ @endif + +
+
+@endsection diff --git a/resources/views/laporan/pdf.blade.php b/resources/views/laporan/pdf.blade.php new file mode 100644 index 00000000..c9df04a9 --- /dev/null +++ b/resources/views/laporan/pdf.blade.php @@ -0,0 +1,65 @@ + + + + + Laporan Presensi Pegawai + + + + +
+

LAPORAN REKAPITULASI PRESENSI PEGAWAI

+

MPG HRIS ENTERPRISE SYSTEM

+

Periode: {{ \Carbon\Carbon::create()->month((int)$bulan)->translatedFormat('F') }} {{ $tahun }}

+
+ + + + + + + + + + + + + + + + @foreach($rekap as $index => $row) + + + + + + + + + + + @endforeach + +
NoNIKNama LengkapHadirIzin/CutiSakitAlphaTelat
{{ $index + 1 }}{{ $row['user']->nik ?? '-' }}{{ $row['user']->nama_lengkap }}{{ $row['hadir'] }} x{{ $row['izin'] }} x{{ $row['sakit'] }} x{{ $row['alpha'] }} x{{ $row['terlambat'] }} x
+ + + + + diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php new file mode 100644 index 00000000..3a515b4d --- /dev/null +++ b/resources/views/layouts/app.blade.php @@ -0,0 +1,553 @@ + + + + + + + + + {{ config('app.name', 'Laravel') }} + + + + + + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + + + + + + + + + + + + @yield('style') + + + + +
+ +
+ + @include('layouts.sidebar') + +
+ +
+
+
+ + +

+ @yield('title', 'Dashboard') +

+
+ +
+ + +
+ {{ substr(Auth::user()->nama_lengkap ?? 'U', 0, 1) }} +
+
+
+
+ +
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ @for($i = 0; $i < 5; $i++) +
+
+
+
+
+
+
+
+
+
+
+
+
+ @endfor +
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+ +
+
+
+ @for($i = 0; $i < 6; $i++) +
+
+
+
+
+
+
+
+
+
+
+ @endfor +
+
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ @for($i = 0; $i < 7; $i++) +
+ @endfor + @for($i = 0; $i < 35; $i++) +
+
+ @if($i % 3 == 0) +
+ @endif +
+ @endfor +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ @for($i = 0; $i < 6; $i++) +
+
+
+
+
+
+
+
+ @endfor +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + @yield('content') +
+ +
+
+ + + +
+
+ + + + + @yield('script') + + + + diff --git a/resources/views/layouts/sidebar.blade.php b/resources/views/layouts/sidebar.blade.php new file mode 100644 index 00000000..0093155a --- /dev/null +++ b/resources/views/layouts/sidebar.blade.php @@ -0,0 +1,310 @@ + + + diff --git a/resources/views/lembur/create.blade.php b/resources/views/lembur/create.blade.php new file mode 100644 index 00000000..901b2281 --- /dev/null +++ b/resources/views/lembur/create.blade.php @@ -0,0 +1,76 @@ +@extends('layouts.app') + +@section('title', 'Input Lembur Manual') + +@section('content') +
+ + + + +
+
+ @csrf + +
+ + + @foreach($pegawai as $p) + + @endforeach + + + + + + + + + + + @foreach($kompensasi as $k) + + @endforeach + +
+ +
+ {{ old('keterangan') }} +
+ +
+ Simpan Lembur +
+
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/lembur/index.blade.php b/resources/views/lembur/index.blade.php new file mode 100644 index 00000000..99923a07 --- /dev/null +++ b/resources/views/lembur/index.blade.php @@ -0,0 +1,131 @@ +@extends('layouts.app') + +@section('title', 'Lembur') + +@section('content') + + + + + Tambah Lembur Manual + + + + +
+ + + No + Tanggal Lembur + Nama Pegawai + Durasi + Kompensasi + Keterangan + Status + Aksi + + + @forelse ($lembur as $index => $item) + + + {{ $lembur->firstItem() + $index }} + + + {{ \Carbon\Carbon::parse($item->tanggal_lembur)->translatedFormat('d F Y') }} +
+ {{ \Carbon\Carbon::parse($item->jam_mulai)->format('H:i') }} - + {{ \Carbon\Carbon::parse($item->jam_selesai)->format('H:i') }} +
+ + +
{{ $item->user->nama_lengkap ?? '-' }}
+
{{ $item->user->jabatan->nama_jabatan ?? '' }}
+ + + {{ $item->durasi_menit }} Menit + + + @if($item->id_kompensasi == 2) + ⭐ Poin + @else + 💵 Uang + @endif + + + {{ Str::limit($item->keterangan, 30) }} + + + @php + $status = strtolower($item->status->nama_status ?? '-'); + $idStatus = $item->id_status; + $badgeColor = match ($idStatus) { + 1 => 'yellow', + 2 => 'green', + 3 => 'red', + default => 'gray' + }; + @endphp + + {{ $item->status->nama_status ?? '-' }} + + + + @if($item->id_status == 1) +
+ +
+ @csrf + @method('PUT') + + +
+ + +
+ @else + Selesai + @endif + + + @empty + + @endforelse +
+ + +
+ + +
+ @csrf + @method('PUT') + + +
+ +
+ +
+ Batal + Tolak Pengajuan +
+
+
+ + +@endsection diff --git a/resources/views/pegawai/create.blade.php b/resources/views/pegawai/create.blade.php new file mode 100644 index 00000000..b0a828eb --- /dev/null +++ b/resources/views/pegawai/create.blade.php @@ -0,0 +1,99 @@ +@extends('layouts.app') + +@section('title', 'Tambah Pegawai') + +@section('content') +
+ +
+
+

Tambah Pegawai Baru

+

Lengkapi data diri dan penempatan kerja pegawai.

+
+ +
+ +
+ @csrf +
+
+

Informasi Pribadi

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

Penempatan Kerja

+
+ + + @foreach($divisi as $d) + + @endforeach + + + + + @foreach($jabatan as $j) + + @endforeach + + + + + @foreach($kantor as $k) + + @endforeach + + + + + @foreach($roles as $r) + + @endforeach + + + +
+
+
+ +
+ + +
+ + Simpan Data Pegawai + + + Batalkan + +
+ +
+

+ Informasi: Kata sandi default untuk pegawai baru adalah Mpg123!. +

+
+
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/pegawai/edit.blade.php b/resources/views/pegawai/edit.blade.php new file mode 100644 index 00000000..f4a1aba9 --- /dev/null +++ b/resources/views/pegawai/edit.blade.php @@ -0,0 +1,102 @@ +@extends('layouts.app') + +@section('title', 'Edit Pegawai') + +@section('content') +
+
+
+

Edit Pegawai: {{ $pegawai->nama_lengkap }}

+

Perbarui informasi pribadi atau penempatan kerja pegawai.

+
+ +
+ +
+ @csrf + @method('PUT') + +
+
+

Informasi Pribadi

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

Penempatan & Status

+
+ + @foreach($divisi as $d) + + @endforeach + + + + @foreach($jabatan as $j) + + @endforeach + + + + @foreach($kantor as $k) + + @endforeach + + + + + + + + + @foreach($roles as $r) + + @endforeach + + +
+ +
+
+
+
+ +
+ + +
+ + Perbarui Data Pegawai + + + Batalkan + +
+
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/pegawai/index.blade.php b/resources/views/pegawai/index.blade.php new file mode 100644 index 00000000..4942ea35 --- /dev/null +++ b/resources/views/pegawai/index.blade.php @@ -0,0 +1,207 @@ +@extends('layouts.app') + +@section('title', 'Data Karyawan') + +@section('content') +
+ +
+

Data Karyawan

+ +
+ @foreach($stats['kantor_list'] as $kantor) + + + + + + + @endforeach + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ + + @foreach($allJabatan as $j) + + @endforeach + +
+ +
+ + + @foreach($allKantor as $k) + + @endforeach + +
+ +
+ + + + + +
+ + @if(request()->hasAny(['filter_jabatan', 'filter_kantor', 'filter_status', 'search'])) + + Reset + + @endif +
+ +
+ + + + + Tambah + +
+ +
+
+ +
+ + + Karyawan + Posisi & Penempatan + Status + Face ID + Aksi + + + @forelse($pegawai as $p) + + +
+ @if($p->foto) + + @else +
+ + + +
+ @endif +
+
{{ $p->nama_lengkap }}
+
+ {{ $p->email }} + + {{ $p->nik ?? '-' }} +
+
+
+ + + +
+ {{ $p->jabatan->nama_jabatan ?? '-' }} + {{ $p->divisi->nama_divisi ?? '-' }} +
+ + + + {{ $p->kantor->nama_kantor ?? '-' }} +
+
+ + + + + {{ $p->status_aktif ? 'Aktif' : 'Non-Aktif' }} + + + + + @if($p->is_face_registered) + + + + + Terdaftar + + @else + + + + + Belum + + @endif + + + +
+ + + + + + + + @if($p->status_aktif) + + @else + + @endif + +
+ + + @empty + + @endforelse +
+ +
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/pegawai/show.blade.php b/resources/views/pegawai/show.blade.php new file mode 100644 index 00000000..ba4ee54c --- /dev/null +++ b/resources/views/pegawai/show.blade.php @@ -0,0 +1,316 @@ +@extends('layouts.app') + +@section('title', 'Detail Pegawai') + +@section('content') +
+ +
+
+

Profil Pegawai

+

Informasi detail, jabatan, dan catatan waktu pegawai.

+
+ +
+ +
+ +
+
+
+
+ @if($pegawai->foto) + + @else +
+ + + +
+ @endif +
+
+
+

{{ $pegawai->nama_lengkap }}

+

{{ $pegawai->jabatan->nama_jabatan ?? '-' }}

+ +
+ + {{ $pegawai->status_aktif ? 'Aktif' : 'Non-Aktif' }} + + + {{ $pegawai->is_face_registered ? 'Wajah Terdaftar' : 'Belum Ada Wajah' }} + +
+ +
+ +
+
+ + + + + {{ $pegawai->nik ?? 'NIK belum diatur' }} +
+
+ + + + + {{ $pegawai->email }} +
+
+ + + + + {{ $pegawai->no_telp ?? '-' }} +
+
+ + + + + {{ $pegawai->alamat ?? 'Alamat belum diatur' }} +
+
+
+
+ +
+

Departemen

+
+
+

Divisi

+

{{ $pegawai->divisi->nama_divisi ?? '-' }}

+
+
+

Penempatan Kantor

+

{{ $pegawai->kantor->nama_kantor ?? '-' }}

+
+
+

Tanggal Bergabung

+

+ {{ \Carbon\Carbon::parse($pegawai->tgl_bergabung)->translatedFormat('d F Y') }}

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

Sisa Cuti Tahunan

+

Periode {{ date('Y') }} +

+
+
+ + + + +
+
+
+ {{ $pegawai->sisa_cuti ?? 0 }} + Hari +
+
+ + + + +
+ +
+
+
+
+

Saldo Poin Lembur

+

Dapat digunakan absen + telat/pulang

+
+
+ + + + +
+
+
+ {{ $sisaPoin }} + Poin +
+
+ + + + +
+
+ +
+
+

Riwayat Mutasi Poin

+
+
+ + + + Tanggal + + Keterangan + + Jumlah + + + @forelse($historyPoin as $history) + + + {{ \Carbon\Carbon::parse($history->tanggal)->format('d/m/Y') }} + + + {{ $history->keterangan ?? '-' }} + + + @if($history->tipe == 'penambahan') + + +{{ $history->jumlah }} Poin + + @else + + -{{ $history->jumlah }} Poin + + @endif + + + @empty + + @endforelse + +
+
+ +
+
+

Riwayat Presensi (30 Hari Terakhir)

+ Lihat Semua → +
+
+ + + Tanggal + Jadwal / Shift + Jam Kerja + Status + + + @forelse($riwayatPresensi as $presensi) + + + {{ \Carbon\Carbon::parse($presensi->tanggal)->translatedFormat('d M Y') }} + + + {{ $presensi->jadwal->shift->nama_shift ?? 'Shift Default' }}
+ {{ substr($presensi->jadwal->jam_masuk ?? '08:00', 0, 5) }} - {{ substr($presensi->jadwal->jam_pulang ?? '17:00', 0, 5) }} + + +
+ + {{ $presensi->jam_masuk ? \Carbon\Carbon::parse($presensi->jam_masuk)->format('H:i') : '--:--' }} + + s/d + + {{ $presensi->jam_pulang ? \Carbon\Carbon::parse($presensi->jam_pulang)->format('H:i') : '--:--' }} + +
+ + + + {{ $presensi->status->nama_status ?? 'Unknown' }} + + + + @empty + + @endforelse +
+
+
+ +
+
+

Riwayat Izin & Cuti (30 Hari Terakhir)

+
+
+ + + Tanggal + Kategori + Alasan + Status + + + @forelse($riwayatIzin as $izin) + + + @if($izin->tanggal_mulai == $izin->tanggal_selesai) + {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->translatedFormat('d M Y') }} + @else + {{ \Carbon\Carbon::parse($izin->tanggal_mulai)->format('d M') }} - {{ \Carbon\Carbon::parse($izin->tanggal_selesai)->translatedFormat('d M Y') }} + @endif + + + {{ $izin->jenisIzin->nama_izin ?? '-' }} + + +
{{ $izin->alasan }}
+ + + @php + $color = 'yellow'; $label = 'Menunggu'; + if($izin->status == 'disetujui') { $color = 'green'; $label = 'Disetujui'; } + elseif($izin->status == 'ditolak') { $color = 'red'; $label = 'Ditolak'; } + @endphp + + {{ $label }} + + + + @empty + + @endforelse +
+
+
+ +
+
+
+@endsection diff --git a/resources/views/penggunaan-poin/index.blade.php b/resources/views/penggunaan-poin/index.blade.php new file mode 100644 index 00000000..c1ed4501 --- /dev/null +++ b/resources/views/penggunaan-poin/index.blade.php @@ -0,0 +1,123 @@ +@extends('layouts.app') + +@section('title', 'Penggunaan Poin') + +@section('content') + + + +
+ + + No + Karyawan + Tanggal Penggunaan + Jenis Pengurangan + Jumlah Poin + Tanggal Diajukan + Status + Aksi + + + @forelse ($penggunaan as $index => $item) + + + {{ $penggunaan->firstItem() + $index }} + + +
{{ $item->user->nama_lengkap ?? '-' }}
+
{{ $item->user->divisi->nama_divisi ?? '' }}
+ + + {{ $item->tanggal_penggunaan ? $item->tanggal_penggunaan->translatedFormat('d F Y') : '-' }} + + + {{ $item->jenisPengurangan->nama_pengurangan ?? 'Lainnya' }} + + + {{ $item->jumlah_poin }} Poin + + + {{ $item->tanggal_diajukan ? $item->tanggal_diajukan->translatedFormat('d M Y H:i') : '-' }} + + + @php + $idStatus = $item->id_status; + $badgeColor = match ($idStatus) { + 1 => 'yellow', + 2 => 'green', + 3 => 'red', + default => 'gray' + }; + @endphp + + {{ $item->status->nama_status ?? '-' }} + + + + @if($item->id_status == 1) +
+ +
+ @csrf + @method('PUT') + + +
+ + +
+ @else + Selesai + @endif + + + @empty + + @endforelse +
+ + +
+ + +
+ @csrf + @method('PUT') + + +
+ +
+ +
+ Batal + Tolak Pengajuan +
+
+
+ + +@endsection diff --git a/resources/views/pengumuman/create.blade.php b/resources/views/pengumuman/create.blade.php new file mode 100644 index 00000000..1a3848f3 --- /dev/null +++ b/resources/views/pengumuman/create.blade.php @@ -0,0 +1,29 @@ +@extends('layouts.app') + +@section('content') + +
+ + +
+
+ @csrf +
+ + + + + + +
+ + + Simpan Pengumuman + +
+
+
+
+
+@endsection diff --git a/resources/views/pengumuman/edit.blade.php b/resources/views/pengumuman/edit.blade.php new file mode 100644 index 00000000..d09c2be0 --- /dev/null +++ b/resources/views/pengumuman/edit.blade.php @@ -0,0 +1,32 @@ +@extends('layouts.app') + +@section('content') + +
+ + +
+
+ @csrf + @method('PUT') +
+ + + + + + +
+ + + Simpan Perubahan + +
+
+
+
+
+@endsection diff --git a/resources/views/pengumuman/index.blade.php b/resources/views/pengumuman/index.blade.php new file mode 100644 index 00000000..18355266 --- /dev/null +++ b/resources/views/pengumuman/index.blade.php @@ -0,0 +1,60 @@ +@extends('layouts.app') + +@section('title', 'Pengumuman') + +@section('content') + + + + + + + Buat Pengumuman + + + +
+ + + No + Judul + Tanggal + Dibuat Oleh + Aksi + + @forelse ($pengumuman as $item) + + + {{ $loop->iteration + ($pengumuman->currentPage() - 1) * $pengumuman->perPage() }} + + +
{{ $item->judul }}
+
{{ Str::limit($item->isi, 50) }}
+ + + {{ $item->tanggal->format('d M Y') }} + + + {{ $item->pembuat->nama_lengkap ?? 'System' }} + + +
+ + + + +
+ + + @empty + + @endforelse +
+ + +
+@endsection diff --git a/resources/views/permission/index.blade.php b/resources/views/permission/index.blade.php new file mode 100644 index 00000000..c50c42f9 --- /dev/null +++ b/resources/views/permission/index.blade.php @@ -0,0 +1,59 @@ +@extends('layouts.app') + +@section('content') +
+
+

Manajemen Hak Akses

+

Konfigurasi izin spesifik untuk setiap peran pengguna.

+
+ +
+
+
+

Tambah Izin Baru

+
+ @csrf +
+ + +
+ +
+
+
+ +
+
+ @foreach($roles as $role) +
+
+

{{ $role->nama_role }}

+ Role ID: {{ $role->id_role }} +
+
+ @csrf +
+ @foreach($permissions as $perm) +
+ +
+

{{ $perm->nama_permission }}

+

{{ $perm->slug }}

+
+
+ @endforeach +
+ +
+
+ @endforeach +
+
+
+
+@endsection diff --git a/resources/views/presensi/create.blade.php b/resources/views/presensi/create.blade.php new file mode 100644 index 00000000..cf63edba --- /dev/null +++ b/resources/views/presensi/create.blade.php @@ -0,0 +1,107 @@ +@extends('layouts.app') + +@section('title', 'Input Presensi Manual') + +@section('content') +
+ +
+ + + + +
+ + @if(session('error')) +
+ +

{{ session('error') }}

+
+ @endif + +
+
+ @csrf + +
+ +
+ + + + @foreach($pegawai as $p) + + @endforeach + + @error('id_user')

{{ $message }}

@enderror +
+ +
+ +
+ +
+ + + @foreach($statuses as $status) + + @endforeach + + @error('id_status')

{{ $message }}

@enderror +
+ + + + + +
+ + +

Status Validasi otomatis diset menjadi Disetujui.

+
+ +
+ +
+ Batal + + Simpan Presensi + +
+
+
+
+@endsection + +@section('script') + + +@endsection diff --git a/resources/views/presensi/edit.blade.php b/resources/views/presensi/edit.blade.php new file mode 100644 index 00000000..f06d09e5 --- /dev/null +++ b/resources/views/presensi/edit.blade.php @@ -0,0 +1,108 @@ +@extends('layouts.app') + +@section('title', 'Koreksi Presensi') + +@section('content') +
+ +
+ + + + +
+ + @if(session('error')) +
+ +

{{ session('error') }}

+
+ @endif + +
+ +
+
+ +
+

{{ $presensi->user->nama_lengkap }}

+

{{ $presensi->user->nik }} • {{ $presensi->user->divisi->nama_divisi ?? '-' }}

+
+
+
+
Tanggal Presensi
+
{{ \Carbon\Carbon::parse($presensi->tanggal)->translatedFormat('l, d F Y') }}
+
+
+ +
+ @csrf + @method('PUT') + +
+ +
+ + + @foreach($statuses as $status) + + @endforeach + + @error('id_status')

{{ $message }}

@enderror +
+ + + +
+ @php $jm = $presensi->jam_masuk ? \Carbon\Carbon::parse($presensi->jam_masuk)->format('H:i') : ''; @endphp + +
+ +
+ @php $jp = $presensi->jam_pulang ? \Carbon\Carbon::parse($presensi->jam_pulang)->format('H:i') : ''; @endphp + +
+ +
+ + +

Status Validasi akan otomatis diset menjadi Disetujui setelah dikoreksi.

+
+ +
+ +
+ Batal + + Simpan Perubahan + +
+
+
+
+@endsection + +@section('script') + + +@endsection diff --git a/resources/views/presensi/index.blade.php b/resources/views/presensi/index.blade.php new file mode 100644 index 00000000..99a0bba5 --- /dev/null +++ b/resources/views/presensi/index.blade.php @@ -0,0 +1,558 @@ +@extends('layouts.app') + +@section('title', 'Monitoring Presensi') + +@section('style') + +@endsection + +@section('content') +
+ + +
+ @if(Auth::user()->roles->contains(fn($role) => in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']))) + + + + + Input Manual + + @endif + +
+ + + + + @foreach($divisi as $d) + + @endforeach + + + + + + + + + +
+
+ + @if($pendingDates->count() > 0) +
+
+
+ + + +
+
+

+ {{ $pendingDates->sum('jumlah') }} presensi menunggu validasi +

+

Klik tanggal di bawah untuk langsung melihat presensi yang perlu ditinjau:

+ +
+
+
+ @endif + +
+ @csrf + +
+ + + + + + Pegawai + Jam Kerja + Lokasi + Status + Validasi + Aksi + + + @forelse($presensi as $p) + + + @if($p->id_validasi == 2) + + @endif + + +
+ @if($p->foto_wajah_masuk) + + @else +
+ {{ substr($p->user->nama_lengkap ?? 'U', 0, 2) }} +
+ @endif +
+
{{ $p->user->nama_lengkap ?? '-' }}
+
{{ $p->user->nik ?? '-' }} • {{ $p->nama_shift }}
+
+
+ + +
+
+ Masuk: + @if($p->is_adjusted && strpos($p->adjustment_note, 'Datang') !== false) +
+ {{ substr($p->jam_jadwal_masuk_original, 0, 5) }} + + {{ substr($p->jam_jadwal_masuk, 0, 5) }}* + +
+ @else + + {{ \Carbon\Carbon::parse($p->jam_masuk)->format('H:i') }} + + @endif +
+
+ Pulang: + @if($p->is_adjusted && strpos($p->adjustment_note, 'Pulang') !== false) +
+ {{ $p->jam_jadwal_pulang_original != '-' ? substr($p->jam_jadwal_pulang_original, 0, 5) : '-' }} + + {{ $p->jam_jadwal_pulang != '-' ? substr($p->jam_jadwal_pulang, 0, 5) : '-' }}* + +
+ @else + + {{ $p->jam_pulang ? \Carbon\Carbon::parse($p->jam_pulang)->format('H:i') : '--:--' }} + + @endif +
+ @if($p->is_adjusted) +
+ + {{ $p->adjustment_note }} + +
+ @endif +
+ + + @if($p->jarak_masuk !== null) + @if($p->dalam_radius) + + + + + Di Kantor + +
{{ round($p->jarak_masuk) }}m
+ @else + + + + + Luar Radius + +
{{ round($p->jarak_masuk) }}m
+ @endif + @else + - + @endif + + + + {{ $p->status_keterlambatan }} + + + + + {{ $p->validasi_label }} + + + +
+ @if ($p->id_validasi == 2) + + + @endif + + @if(Auth::user()->roles->contains(fn($role) => in_array(strtolower($role->nama_role), ['admin', 'super admin', 'super_admin']))) + + + + @endif + + + Detail + +
+ + + @empty + + + + @endforelse +
+ + +
+
+ + +
+ + +
+ +
+
+ NN +
+
+

Nama Pegawai

+

NIK • Jabatan

+
+
+ +
+
+ +
+ + Tidak ada foto +
+
+ - +
+
+
+ +
+ + Belum Pulang +
+
+ - +
+
+
+ +
+ + + + + + + + Lihat Lokasi di Google Maps + +
+ +
+ Tutup + + + + + + Tolak + + + Setujui Absensi + +
+
+
+ + + +@endsection + +@section('script') + +@endsection diff --git a/resources/views/profile/edit.blade.php b/resources/views/profile/edit.blade.php new file mode 100644 index 00000000..206316bf --- /dev/null +++ b/resources/views/profile/edit.blade.php @@ -0,0 +1,112 @@ +@extends('layouts.app') + +@section('title', 'Pengaturan Akun') + +@section('content') +
+ + + +
+
+
+ @csrf + @method('PUT') + +
+ +
+
+ @if(auth()->user()->foto) + + @else +
+ + + +
+ + @endif + + + +
+
+

{{ auth()->user()->nama_lengkap }}

+

{{ auth()->user()->role->nama_role ?? 'User' }}

+
+
+ +
+ + + + +
+

Ganti Password +

+

Kosongkan jika tidak ingin mengubah password.

+ +
+ + +
+
+
+
+ +
+ +
+ +
+
+
+
+ + +@endsection diff --git a/resources/views/role/index.blade.php b/resources/views/role/index.blade.php new file mode 100644 index 00000000..d43710c6 --- /dev/null +++ b/resources/views/role/index.blade.php @@ -0,0 +1,145 @@ +@extends('layouts.app') + +@section('title', 'Manajemen Role') + +@section('content') +
+ + + +
+ + + + +
+ + +
+ +
+ + + No + Nama Role + Jumlah User + Aksi + + + @forelse($roles as $index => $role) + + {{ $roles->firstItem() + $index }} + + {{ $role->nama_role }} + + {{ $role->users_count }} + Orang + +
+ + + + + +
+ + + @empty + + @endforelse +
+ +
+
+ + +
+ @csrf +
+ +
+ +
+ +
+ @foreach($allPermissions as $permission) +
+ +
+ @endforeach +
+
+ +
+ + +
+
+
+ + +
+ @csrf @method('PUT') +
+ +
+ +
+ +
+ @foreach($allPermissions as $permission) +
+ +
+ @endforeach +
+
+ +
+ + +
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/shift/index.blade.php b/resources/views/shift/index.blade.php new file mode 100644 index 00000000..daa34a1b --- /dev/null +++ b/resources/views/shift/index.blade.php @@ -0,0 +1,139 @@ +@extends('layouts.app') + +@section('title', 'Master Shift Kerja') + +@section('content') +
+ + + +
+ + + + +
+ + +
+ +
+ + + No + Nama Shift + Jam Masuk + Jam Pulang + Terjadwal + Aksi + + + @forelse($shifts as $index => $shift) + + {{ $shifts->firstItem() + $index }} + + {{ $shift->nama_shift }} + + + {{ \Carbon\Carbon::parse($shift->jam_mulai)->format('H:i') }} + + + {{ \Carbon\Carbon::parse($shift->jam_selesai)->format('H:i') }} + + + {{ $shift->jadwal_kerja_count ?? 0 }} Jadwal + + +
+ + + + + +
+ + + @empty + + @endforelse +
+ +
+
+ + +
+ @csrf +
+ +
+ + +
+
+
+ + +
+
+
+ + +
+ @csrf @method('PUT') +
+ +
+ + +
+
+
+ + +
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/signature/show.blade.php b/resources/views/signature/show.blade.php new file mode 100644 index 00000000..2afcf019 --- /dev/null +++ b/resources/views/signature/show.blade.php @@ -0,0 +1,133 @@ +@extends('layouts.app') + +@section('title', 'Tanda Tangan Digital') + +@section('content') +
+ + +
+ +
+

Tanda Tangan Aktif

+
+ @if($ttd) + Tanda Tangan + @else +
+ + + + +

Belum ada tanda tangan

+
+ @endif +
+ @if($ttd) +

Dibuat: {{ $ttd->created_at?->format('d M Y H:i') }}

+ @endif +
+ +
+

Buat Tanda Tangan Baru

+ +
+ +
+ +
+ + +
+ @csrf + + + Simpan Tanda Tangan + +
+
+
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/surat-izin/index.blade.php b/resources/views/surat-izin/index.blade.php new file mode 100644 index 00000000..354526ff --- /dev/null +++ b/resources/views/surat-izin/index.blade.php @@ -0,0 +1,104 @@ +@extends('layouts.app') + +@section('title', 'Surat Izin') + +@section('content') +
+ + +
+
+ + + + + + + + + + + + + + + + +
+
+ +
+ + + No. Surat + Pegawai + Jenis Izin + Tanggal + Status + Aksi + + + @forelse($suratList as $surat) + + + {{ $surat->nomor_surat }} + + +
+
+ {{ substr($surat->user->nama_lengkap ?? 'U', 0, 2) }} +
+
+
{{ $surat->user->nama_lengkap ?? '-' }}
+
{{ $surat->user->nik ?? '-' }}
+
+
+ + + + {{ $surat->pengajuanIzin->jenisIzin->nama_izin ?? 'N/A' }} + + + + {{ $surat->created_at?->format('d M Y') }} + + + @php + $badgeColor = match ($surat->status_surat) { + 'disetujui' => 'green', + 'ditolak' => 'red', + 'menunggu_hrd' => 'blue', + default => 'yellow' + }; + $statusLabel = match ($surat->status_surat) { + 'menunggu_manajer' => 'Menunggu Manajer', + 'menunggu_hrd' => 'Menunggu HRD', + 'disetujui' => 'Disetujui', + 'ditolak' => 'Ditolak', + default => $surat->status_surat + }; + @endphp + {{ $statusLabel }} + + + + Detail + + + + @empty + + @endforelse +
+
+ + +
+@endsection diff --git a/resources/views/surat-izin/show.blade.php b/resources/views/surat-izin/show.blade.php new file mode 100644 index 00000000..c018c60f --- /dev/null +++ b/resources/views/surat-izin/show.blade.php @@ -0,0 +1,219 @@ +@extends('layouts.app') + +@section('title', 'Detail Surat Izin') + +@section('content') +
+
+

Detail Surat Izin

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

Progress Approval

+ @php + $badgeColor = match ($surat->status_surat) { + 'disetujui' => 'green', + 'ditolak' => 'red', + 'menunggu_hrd' => 'blue', + default => 'yellow' + }; + $statusLabel = match ($surat->status_surat) { + 'menunggu_manajer' => 'Menunggu Manajer', + 'menunggu_hrd' => 'Menunggu HRD', + 'disetujui' => 'Disetujui', + 'ditolak' => 'Ditolak', + default => $surat->status_surat + }; + @endphp + {{ $statusLabel }} +
+ +
+ +
+
+ +
+

Pengaju

+

{{ $surat->user->nama_lengkap ?? '-' }}

+
+ +
+ + @php $approvalManajer = $surat->approvals->firstWhere('tahap', 1); @endphp +
+
+ @if($approvalManajer && $approvalManajer->status === 'disetujui') + + @elseif($approvalManajer && $approvalManajer->status === 'ditolak') + + @else + + @endif +
+

Manajer

+

{{ $approvalManajer?->approver?->nama_lengkap ?? 'Menunggu' }}

+
+ +
+ + @php $approvalHrd = $surat->approvals->firstWhere('tahap', 2); @endphp +
+
+ @if($approvalHrd && $approvalHrd->status === 'disetujui') + + @elseif($approvalHrd && $approvalHrd->status === 'ditolak') + + @else + + @endif +
+

HRD

+

{{ $approvalHrd?->approver?->nama_lengkap ?? 'Menunggu' }}

+
+
+
+ +
+
+ +
+

SURAT IZIN

+

No: {{ $surat->nomor_surat }}

+
+ +
{{ $surat->isi_surat }}
+ + @if($surat->pengajuanIzin) +
+

Detail Izin

+ + + + + +
Jenis Izin:{{ $surat->pengajuanIzin->jenisIzin->nama_izin ?? '-' }}
Tanggal Mulai:{{ \Carbon\Carbon::parse($surat->pengajuanIzin->tanggal_mulai)->format('d F Y') }}
Tanggal Selesai:{{ \Carbon\Carbon::parse($surat->pengajuanIzin->tanggal_selesai)->format('d F Y') }}
Alasan:{{ $surat->pengajuanIzin->alasan ?? '-' }}
+
+ @endif + +

Demikian surat ini dibuat, atas perhatiannya saya ucapkan terima kasih.

+ +
+ +
+

Yang Mengajukan

+
+ @if($surat->tandaTanganPengaju) + TTD + @else + Tanpa TTD + @endif +
+

{{ $surat->user->nama_lengkap ?? '-' }}

+

{{ $surat->user->jabatan->nama_jabatan ?? '' }}

+
+ +
+

Manajer

+
+ @if($approvalManajer && $approvalManajer->tandaTanganApprover) + TTD + @elseif($approvalManajer && $approvalManajer->status === 'ditolak') + DITOLAK + @else + Menunggu + @endif +
+

{{ $approvalManajer?->approver?->nama_lengkap ?? '...................' }}

+

{{ $approvalManajer?->approver?->jabatan?->nama_jabatan ?? 'Manajer' }}

+
+ +
+

HRD

+
+ @if($approvalHrd && $approvalHrd->tandaTanganApprover) + TTD + @elseif($approvalHrd && $approvalHrd->status === 'ditolak') + DITOLAK + @else + Menunggu + @endif +
+

{{ $approvalHrd?->approver?->nama_lengkap ?? '...................' }}

+

{{ $approvalHrd?->approver?->jabatan?->nama_jabatan ?? 'HRD' }}

+
+
+
+
+ + @if($canApprove) +
+

Tindakan

+ + @if(!$ttdApprover) +
+

+ Perhatian: Anda belum memiliki tanda tangan digital. + Buat tanda tangan dulu sebelum menyetujui surat. +

+
+ @endif + +
+ + + + + Tolak + + + Setujui & Tanda Tangani + +
+
+ @endif +
+ + @section('style') + + @endsection +@endsection diff --git a/resources/views/tukar-shift/create.blade.php b/resources/views/tukar-shift/create.blade.php new file mode 100644 index 00000000..212be7a4 --- /dev/null +++ b/resources/views/tukar-shift/create.blade.php @@ -0,0 +1,323 @@ +@extends('layouts.app') + +@section('title', 'Tukar Shift Kerja') + +@section('content') +
+ +
+

Tukar Shift Kerja

+
+ + +
+ +
+
+ +
+
+ @csrf + + @if ($errors->any()) +
+
+ + + + Terdapat kesalahan: +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+ + + @foreach($kantor as $k) + + @endforeach + +

+ + Pilih kantor untuk menampilkan daftar pegawai yang tersedia. +

+
+ +
+
+
+
+ +
+
+ Akses Terkunci + Pilih kantor terlebih dahulu untuk melanjutkan +
+
+
+ +
+ +
+
+ Pihak Pertama
+ +
+ + + @foreach($pegawai as $p) + + @endforeach + +
+ +
+ +
+ +
+ +
+ +
+
+
+ + + +
+
+ Pihak Kedua
+ +
+ + + @foreach($pegawai as $p) + + @endforeach + +
+ +
+ +
+ +
+ +
+ +
+
+
+ +
+
+
+ {{ old('keterangan') }} +
+ +
+ Batal + + + + + Eksekusi Tukar Shift + +
+
+
+
+@endsection + +@section('script') + + +@endsection diff --git a/resources/views/tukar-shift/index.blade.php b/resources/views/tukar-shift/index.blade.php new file mode 100644 index 00000000..a882b74b --- /dev/null +++ b/resources/views/tukar-shift/index.blade.php @@ -0,0 +1,231 @@ +@extends('layouts.app') + +@section('title', 'Riwayat Tukar Shift') + +@section('content') +
+ +
+

Riwayat Tukar Shift

+
+ + + + + +
+ + + + Terjadi Pada + Pegawai 1 + Pegawai 2 + Keterangan + Dieksekusi Oleh + Aksi + + + @forelse($riwayat as $log) + + + + {{ $log->created_at->translatedFormat('d F Y, H:i') }} + + + +
{{ optional($log->user1)->nama_lengkap }}
+
{{ optional($log->jadwal1)->tanggal }}
+ @if(optional($log->jadwal1)->shift) + + {{ $log->jadwal1->shift->nama_shift }} + + @endif + + +
{{ optional($log->user2)->nama_lengkap }}
+
{{ optional($log->jadwal2)->tanggal }}
+ @if(optional($log->jadwal2)->shift) + + {{ $log->jadwal2->shift->nama_shift }} + + @endif + + + {{ $log->keterangan ?? '-' }} + + + + {{ optional($log->execAdmin)->nama_lengkap }} + + + + @php + $detailData = [ + 'tanggal_eksekusi' => $log->created_at->translatedFormat('d F Y, H:i'), + 'admin' => optional($log->execAdmin)->nama_lengkap, + 'keterangan' => $log->keterangan ?? '-', + 'user1' => optional($log->user1)->nama_lengkap, + 'user1_nik' => optional($log->user1)->nik ?? '-', + 'tanggal1' => optional($log->jadwal1)->tanggal ? \Carbon\Carbon::parse($log->jadwal1->tanggal)->translatedFormat('d F Y') : '-', + 'shift1' => optional(optional($log->jadwal1)->shift)->nama_shift ?? '-', + 'jam1' => optional(optional($log->jadwal1)->shift)->jam_mulai ? substr(optional($log->jadwal1)->shift->jam_mulai, 0, 5) . ' - ' . substr(optional($log->jadwal1)->shift->jam_selesai, 0, 5) : '-', + 'user2' => optional($log->user2)->nama_lengkap, + 'user2_nik' => optional($log->user2)->nik ?? '-', + 'tanggal2' => optional($log->jadwal2)->tanggal ? \Carbon\Carbon::parse($log->jadwal2->tanggal)->translatedFormat('d F Y') : '-', + 'shift2' => optional(optional($log->jadwal2)->shift)->nama_shift ?? '-', + 'jam2' => optional(optional($log->jadwal2)->shift)->jam_mulai ? substr(optional($log->jadwal2)->shift->jam_mulai, 0, 5) . ' - ' . substr(optional($log->jadwal2)->shift->jam_selesai, 0, 5) : '-', + ]; + @endphp + + + Detail + + + + @empty + + +
+ + + + Belum ada riwayat tukar shift. +
+ + + @endforelse +
+
+
+ + +
+
+
+

Dieksekusi Oleh

+

+
+
+

Tanggal Eksekusi

+

+
+
+ +
+ + + +
+

Pegawai 1

+
+
+ +
+
+

+

+
+
+
+
+ Tanggal + +
+
+ Shift + +
+
+ Jam Kerja + +
+
+
+ +
+

Pegawai 2

+
+
+ +
+
+

+

+
+
+
+
+ Tanggal + +
+
+ Shift + +
+
+ Jam Kerja + +
+
+
+
+ +
+

Keterangan / Alasan

+
+
+
+ +
+ Tutup +
+
+
+@endsection + +@section('script') + +@endsection diff --git a/resources/views/vendor/l5-swagger/.gitkeep b/resources/views/vendor/l5-swagger/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resources/views/vendor/l5-swagger/index.blade.php b/resources/views/vendor/l5-swagger/index.blade.php new file mode 100644 index 00000000..10735036 --- /dev/null +++ b/resources/views/vendor/l5-swagger/index.blade.php @@ -0,0 +1,173 @@ + + + + + {{ $documentationTitle }} + + + + + @if(config('l5-swagger.defaults.ui.display.dark_mode')) + + @endif + + + +
+ + + + + + diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 00000000..eee976ec --- /dev/null +++ b/routes/api.php @@ -0,0 +1,71 @@ +group(function () { + + Route::post('/logout', [AuthController::class, 'logout']); + Route::get('/dashboard', [\App\Http\Controllers\Api\DashboardController::class, 'index']); + Route::post('/profile/update', [ProfileController::class, 'update']); + Route::post('/profile/password', [ProfileController::class, 'password']); + Route::post('/profile/update2', [AuthController::class, 'updateProfile']); + + Route::post('/face/enroll', [FaceEnrollmentController::class, 'enrollFace']); + Route::get('/face/status', [FaceEnrollmentController::class, 'getFaceStatus']); + Route::post('/face/verify', [FaceEnrollmentController::class, 'verifyFace']); + + Route::get('/poin/expiring', [PoinController::class, 'getExpiringPoints']); + Route::get('/poin/history', [PoinController::class, 'getPointHistory']); + Route::post('/poin/redeem', [PoinController::class, 'redeem']); + + Route::get('/schedule/today', [ScheduleController::class, 'today']); + Route::get('/schedule/check', [ScheduleController::class, 'checkScheduleByDate']); + Route::get('/schedule/monthly', [ScheduleController::class, 'getMonthlySchedule']); + + Route::get('/presensi', [PresensiController::class, 'index']); + Route::post('/presensi', [PresensiController::class, 'store']); + Route::get('/presensi/history', [PresensiController::class, 'history']); + Route::post('/presensi/{id}/resubmit', [PresensiController::class, 'resubmit']); + + Route::get('/submission/types', [SubmissionController::class, 'types']); + Route::post('/submission', [SubmissionController::class, 'store']); + Route::put('/submission/{id}', [SubmissionController::class, 'update']); + Route::get('/submission/history', [SubmissionController::class, 'history']); + + Route::get('/jenis-izin', [SubmissionController::class, 'types']); + Route::post('/pengajuan-izin', [SubmissionController::class, 'store']); + Route::put('/pengajuan-izin/{id}', [SubmissionController::class, 'update']); + + Route::post('/lembur', [LemburController::class, 'store']); + Route::put('/lembur/{id}', [LemburController::class, 'update']); + Route::get('/lembur/history', [LemburController::class, 'history']); + + Route::get('/kompensasi', [KompensasiController::class, 'index']); + + Route::get('/pengumuman', [\App\Http\Controllers\Api\PengumumanApiController::class, 'index']); + + Route::get('/user', [AuthController::class, 'user']); + + Route::post('/signature', [SignatureApiController::class, 'store']); + Route::get('/signature', [SignatureApiController::class, 'show']); + Route::delete('/signature/{id}', [SignatureApiController::class, 'destroy']); + + Route::get('/surat-izin', [SuratIzinApiController::class, 'index']); + Route::post('/surat-izin', [SuratIzinApiController::class, 'store']); + Route::get('/surat-izin/{id}', [SuratIzinApiController::class, 'show']); + +}); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 00000000..9c701110 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote')->hourly(); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 00000000..0f43750b --- /dev/null +++ b/routes/web.php @@ -0,0 +1,109 @@ +name('home')->middleware('guest'); +Route::get('/login', [AuthController::class, 'index'])->name('login')->middleware('guest'); +Route::post('/login', [AuthController::class, 'login'])->name('login.post'); +Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); + +Route::middleware(['auth'])->group(function () { + + Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); + + Route::get('/presensi', [PresensiController::class, 'index'])->name('presensi.index'); + Route::post('/presensi/bulk-action', [PresensiController::class, 'bulkAction'])->name('presensi.bulkAction'); + Route::post('/presensi/{id}/approve', [PresensiController::class, 'approve'])->name('presensi.approve'); + Route::post('/presensi/{id}/reject', [PresensiController::class, 'reject'])->name('presensi.reject'); + + Route::get('/izin', [App\Http\Controllers\PengajuanIzinController::class, 'index'])->name('izin.index'); + Route::post('/izin', [App\Http\Controllers\PengajuanIzinController::class, 'store'])->name('izin.store'); + Route::post('/izin/{id}/approve', [App\Http\Controllers\PengajuanIzinController::class, 'approve'])->name('izin.approve'); + Route::post('/izin/{id}/reject', [App\Http\Controllers\PengajuanIzinController::class, 'reject'])->name('izin.reject'); + + Route::get('/permissions', [PermissionController::class, 'index'])->name('permission.index'); + Route::post('/permissions', [PermissionController::class, 'store'])->name('permission.store'); + Route::post('/permissions/sync/{id_role}', [PermissionController::class, 'sync'])->name('permission.sync'); + + Route::resource('pengumuman', PengumumanController::class); + + Route::resource('lembur', LemburController::class)->only(['index', 'update', 'create', 'store']); + + Route::get('/penggunaan-poin', [App\Http\Controllers\PenggunaanPoinController::class, 'index'])->name('penggunaan-poin.index'); + Route::put('/penggunaan-poin/{id}', [App\Http\Controllers\PenggunaanPoinController::class, 'update'])->name('penggunaan-poin.update'); + + Route::get('/laporan', [LaporanController::class, 'index'])->name('laporan.index'); + Route::get('/laporan/izin', [LaporanController::class, 'cuti'])->name('laporan.izin'); + Route::get('/laporan/export', [LaporanController::class, 'exportExcel'])->name('laporan.export'); + Route::get('/laporan/export-pdf', [LaporanController::class, 'exportPdf'])->name('laporan.exportPdf'); + + Route::get('/face-approval', [FaceApprovalController::class, 'index'])->name('face.index'); + Route::get('/face-approval/photo/{userId}/{pose}', [FaceApprovalController::class, 'showPhoto'])->name('face.photo'); + Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve'); + Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject'); + + Route::prefix('jadwal')->name('jadwal.')->group(function () { + Route::get('/generate', [App\Http\Controllers\JadwalController::class, 'generateForm'])->name('generate'); + Route::post('/generate', [App\Http\Controllers\JadwalController::class, 'processGenerate'])->name('process'); + Route::get('/events', [App\Http\Controllers\JadwalController::class, 'getEvents'])->name('events'); + Route::post('/check-conflicts', [App\Http\Controllers\JadwalController::class, 'checkConflicts'])->name('check-conflicts'); + Route::post('/bulk-delete', [App\Http\Controllers\JadwalController::class, 'bulkDestroy'])->name('bulk-delete'); + Route::get('/test-isolation', function () { + return view('jadwal.test_isolation'); + }); + }); + + Route::get('/profile', [App\Http\Controllers\ProfileController::class, 'edit'])->name('profile.edit'); + Route::put('/profile', [App\Http\Controllers\ProfileController::class, 'update'])->name('profile.update'); + + Route::get('/surat-izin', [SuratIzinController::class, 'index'])->name('surat-izin.index'); + Route::get('/surat-izin/{id}', [SuratIzinController::class, 'show'])->name('surat-izin.show'); + Route::post('/surat-izin/{id}/approve', [SuratIzinController::class, 'approve'])->name('surat-izin.approve'); + Route::post('/surat-izin/{id}/reject', [SuratIzinController::class, 'reject'])->name('surat-izin.reject'); + + Route::get('/tanda-tangan', [SignatureController::class, 'show'])->name('signature.show'); + Route::post('/tanda-tangan', [SignatureController::class, 'store'])->name('signature.store'); +}); + +Route::middleware(['auth', 'role:admin'])->group(function () { + Route::resource('divisi', DivisiController::class)->except(['create', 'edit', 'show']); + Route::resource('jabatan', JabatanController::class); + Route::resource('kantor', KantorController::class)->except(['create', 'show', 'edit']); + Route::resource('role', RoleController::class)->except(['create', 'edit']); + Route::resource('shift', \App\Http\Controllers\ShiftController::class)->except(['create', 'edit', 'show']); + Route::resource('jadwal', \App\Http\Controllers\JadwalController::class)->except(['edit', 'show']); + Route::resource('pegawai', PegawaiController::class); + Route::post('/hari-libur/sync', [\App\Http\Controllers\HariLiburController::class, 'syncHolidays'])->name('hari-libur.sync'); + Route::resource('hari-libur', \App\Http\Controllers\HariLiburController::class)->except(['create', 'edit', 'show']); + + Route::get('/presensi/create', [PresensiController::class, 'create'])->name('presensi.create'); + Route::post('/presensi/manual', [PresensiController::class, 'storeManual'])->name('presensi.storeManual'); + Route::get('/presensi/{id}/edit', [PresensiController::class, 'edit'])->name('presensi.edit'); + Route::put('/presensi/{id}/manual', [PresensiController::class, 'updateManual'])->name('presensi.updateManual'); + + Route::get('/cuti', [CutiController::class, 'index'])->name('cuti.index'); + Route::put('/cuti/{id}', [CutiController::class, 'update'])->name('cuti.update'); + Route::post('/cuti/reset', [CutiController::class, 'resetMassal'])->name('cuti.reset'); + + Route::get('/laporan-lembur', [LaporanLemburController::class, 'index'])->name('laporan-lembur.index'); + Route::get('/laporan-lembur/export-excel', [LaporanLemburController::class, 'exportExcel'])->name('laporan-lembur.exportExcel'); + + Route::get('/tukar-shift/jadwal-user', [\App\Http\Controllers\TukarShiftController::class, 'getJadwalUser'])->name('tukar-shift.jadwal-user'); + Route::resource('tukar-shift', \App\Http\Controllers\TukarShiftController::class)->only(['index', 'create', 'store']); +}); diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 00000000..fedb287f --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 00000000..05c4471f --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 00000000..01e4a6cd --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 00000000..f35b4e79 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,18 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.js'], + refresh: true, + }), + tailwindcss(), + ], + server: { + watch: { + ignored: ['**/storage/framework/views/**'], + }, + }, +});