30 Commits
lpj ... master

Author SHA1 Message Date
664c793eff Merge pull request 'refactor(usermanagement): streamline UsersSeeder by utilizing RolesSeeder and improving user creation logic' (#1) from new_adk into master
Reviewed-on: #1
2025-10-31 16:56:12 +07:00
Sholahuddin Al Ayubi
a4aab54735 refactor(usermanagement): streamline UsersSeeder by utilizing RolesSeeder and improving user creation logic 2025-10-31 16:53:50 +07:00
c9bd6664f2 refactor(usermanagement): tidy up User model code structure and improve attribute casting 2025-10-03 09:07:42 +07:00
Daeng Deni Mardaeni
59721337a8 feat(ui): Perbaikan tampilan dan fungsionalitas halaman index untuk manajemen pengguna
- Memperbaiki fungsi export data pengguna dengan format yang lebih rapi
- Update semua controller (Permissions, Positions, Roles, Users) untuk konsistensi response dan error handling
- Tambahan fungsi `goPage(1)` pada event listener pencarian untuk langsung ke halaman pertama saat melakukan pencarian
2025-08-20 09:41:52 +07:00
Daeng Deni Mardaeni
c348af2484 (usermanagement): tambah fitur export users dengan filter pencarian
- Modifikasi `UsersExport` untuk menerima parameter search
- Tambah filter whereAny (name, email) di `collection()`
- Tambah kolom "Roles" dengan mapping roles (pluck + implode)
- Null safety branch pakai optional chaining
- Update formatting kolom export
- Modifikasi `UsersController@export` untuk terima & teruskan search
- Batasi role berdasarkan role user login
- Konsistensikan pencarian di `index()` pakai whereAny
- Hapus validasi NIK di profile update
- Tambah ID pada tombol export di `index.blade.php`
- Tambah fungsi JS `updateExportUrl()` untuk sinkronisasi search
- Null safety render branch & role di DataTable
- Tambah listener untuk update URL export saat search
- Perbaiki formatting & indentasi kode
2025-08-15 08:35:06 +07:00
Daeng Deni Mardaeni
e3c7bf711c fix(usermanagement): perbaiki kondisi izin pada metode datatables di UsersController
- Mengubah kunci izin dari `usermanagement.view` menjadi `usermanagement.read` untuk konsistensi naming convention di seluruh sistem.
2025-06-22 21:31:55 +07:00
Daeng Deni Mardaeni
7cb2f798d0 refactor(usermanagement): gunakan middleware untuk autentikasi dan inisialisasi user di semua controller
- Mengubah properti `user` dari `public` menjadi `protected` di semua controller terkait:
  - **PermissionsController**
  - **PositionsController**
  - **RolesController**
  - **UsersController**

- Menghapus inisialisasi manual user dengan `Auth::guard('web')->user()` dari konstruktor.

- Menambahkan middleware:
  - `auth` untuk memastikan bahwa hanya pengguna yang telah login dapat mengakses controller.
  - Middleware tambahan anonim untuk mengassign properti `user` setelah middleware `auth` dijalankan.
2025-06-22 20:52:51 +07:00
Daeng Deni Mardaeni
21521b384e feat(usermanagement): ubah respons abort menjadi JSON untuk akses terlarang di beberapa controller
- Mengganti respons `abort(403)` menjadi respons JSON yang lebih informatif di beberapa controller:
  - **PermissionsController**
    - Penyesuaian pada metode penghapusan dan pengambilan data untuk datatable.
    - Menampilkan pesan error dan status success=false jika pengguna tidak memiliki izin.
  - **PositionsController**
    - Perubahan serupa diterapkan pada metode penghapusan posisi dan pengambilan data untuk datatable.
  - **RolesController**
    - Penyesuaian dilakukan pada metode pengambilan data untuk datatable.
  - **UsersController**
    - Ubah logika pada metode penghapusan dan pengambilan data pengguna ke JSON.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 18:56:06 +07:00
Daeng Deni Mardaeni
0a2add800e feat(usermanagement): perbarui seeders untuk grup permission & tambah slug izin restore
- **Perubahan pada Seeder**:
  - Menghapus grup permission `basic-data` dari `PermissionGroupSeeder`, menyisakan hanya `usermanagement` untuk konsistensi.
  - Menambahkan slug baru `restore` ke daftar izin di `PermissionsSeeder` untuk mendukung operasi pemulihan data pada fitur yang relevan.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 18:48:43 +07:00
Daeng Deni Mardaeni
fdbef3a5e8 feat(usermanagement): perbaiki otorisasi, tambah fitur ekspor, dan optimalkan logika pada beberapa controller
- **Perbaikan Izin Akses**:
  - Mengganti kunci permission pada beberapa metode agar lebih konsisten:
    - `usermanagement.store` menjadi `usermanagement.create` (store).
    - `usermanagement.edit` menjadi `usermanagement.update` (edit/update).
    - `usermanagement.read` tetap diatur sesuai context (index/view).
  - Menambahkan `abort(403)` pada metode yang belum memiliki pengecekan izin untuk memastikan keamanan.

- **Peningkatan Fitur**:
  - Menambahkan fitur ekspor pada `PermissionsController`, `PositionsController`, `RolesController`, dan `UsersController`:
    - Cek validasi izin sebelum melakukan ekspor.
    - Mendukung pengunduhan file Excel.

- **Optimalisasi Logika**:
  - Menggabungkan properti `user` di semua controller dengan mendefinisikannya melalui konstruktor.
  - Menghapus redundansi load user menggunakan `Auth::guard('web')->user()` di setiap metode.
  - Menyederhanakan pengaturan logging aktivitas untuk setiap operasi CRUD.

- **Penyesuaian & Penambahan**:
  - Menambahkan slug `restore` ke daftar permission terkait untuk operasi pemulihan yang diimplementasikan.
  - Menghapus komentar kode yang tidak digunakan dan mendokumentasikan ulang logika penting untuk lebih jelas.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 18:11:14 +07:00
Daeng Deni Mardaeni
becbf8aee2 feat(usermanagement): tambahkan helper untuk cek role and permission
- Perbarui file `module.json`:
  - Menambahkan path baru pada array `files` untuk memuat helper: `app/Helpers/RolePermission.php`.

- Tambahkan helper baru `RolePermission.php`:
  - Fungsi `check_permission`:
    - Mengecek apakah pengguna yang terautentikasi memiliki permission tertentu.
    - Mendukung opsi untuk mengembalikan respons `abort(403)` jika permission tidak valid.
  - Fungsi `user_has_role`:
    - Mengecek apakah pengguna yang terautentikasi memiliki salah satu role dari daftar yang diberikan.
    - Membandingkan nama role pengguna dengan array input menggunakan mekanisme intersect.

Signed-off-by: Daeng Deni Mardaeni <ddeni05@gmail.com>
2025-06-22 10:59:30 +07:00
Daeng Deni Mardaeni
df2f91cc57 fix(models): ganti paket userstamps ke dependensi baru
- Mengganti penggunaan `Wildside\Userstamps\Userstamps` dengan `Mattiverse\Userstamps\Traits\Userstamps`:
  - Pada model `User`.
  - Pada model `Base`.
- Perubahan ini mengatasi masalah kompatibilitas dengan dependensi lama dan memastikan penggunaan fitur userstamps tetap berfungsi dengan baik.
2025-06-05 16:54:02 +07:00
Daeng Deni Mardaeni
1968c14f68 feat(usermanagement): enhance user management features and implement automated tests
- Memperbarui UsersController:
  - Mengaktifkan middleware untuk menginisialisasi pengguna yang terautentikasi.
  - Mengubah nama izin dari pola 'users.*' menjadi 'usermanagement.*' untuk konsistensi.
  - Menggunakan Storage Facade untuk operasi penyimpanan file tanda tangan.
  - Menambahkan validasi untuk direktori sebelum menyimpan file baru.
  - Mengubah metode untuk memberikan respons JSON pada penghapusan pengguna.

- Memperbarui views/users/index.blade.php:
  - Menghapus dropdown filter yang tidak digunakan.
  - Menambahkan tombol Export to Excel dan Add User dengan styling yang diperbarui.

- Menambahkan file `UsersControllerTest` untuk memastikan kelengkapan pengujian:
  - Pengujian CRUD (Create, Read, Update, Delete) pengguna.
  - Pengujian pagination, sorting, dan filtering untuk datatable.
  - Pengujian pengelolaan file tanda tangan pengguna (penyimpanan baru dan penghapusan tanda tangan lama).
  - Pengujian pemulihan untuk soft-deleted users.
  - Pengujian validasi peran dan izin untuk setiap tindakan.

- Memastikan konsistensi dan reliabilitas proses pengelolaan pengguna melalui pengujian otomatis.
2025-05-18 20:06:15 +07:00
Daeng Deni Mardaeni
1e958c9dd7 feat(permissions): implement permission management enhancements
- Mengubah logika akses `PermissionsController` dengan:
  - Menambahkan konstruktor untuk mendefinisikan user yang terautentikasi.
  - Mengganti logika permission check dari `permissions.*` ke `usermanagement.*`.
  - Menambahkan validasi `abort` untuk operasi CRUD jika user tidak memiliki hak akses.
  - Memperbarui respons penghapusan permission menjadi JSON yang lebih semantik.

- Memperbarui `PermissionGroup` untuk:
  - Menambahkan mekanisme auto-generated slug saat membuat instance baru.

- Memperbaiki export logic pada `PermissionExport` dengan:
  - Mengonversi array `roles` ke collection sebelum menggunakan fungsi `pluck`.

- Menambahkan soft delete pada model `Permission`, memungkinkan penghapusan data non-permanen.

- Menghapus elemen filter dropdown di view `permissions.index` untuk meningkatkan clarity UI.

- Menambahkan comprehensive test suite pada `PermissionsControllerTest` untuk:
  - Menguji validasi CRUD pada permission dengan role dan tanpa role.
  - Menguji restore permissions yang terhapus.
  - Menguji filter, pencarian, dan sorting pada datatables.
  - Menguji export permissions.

Perubahan ini meningkatkan pengelolaan permission, validasi akses, dan memperkaya pengujian untuk memastikan kualitas fitur permission management.
2025-05-18 18:23:06 +07:00
Daeng Deni Mardaeni
8bd31cf54f feat(roles): perbaikan logika, validasi, dan manajemen perizinan pada RolesController
- Menambahkan pengecekan lebih rinci ketika pengguna mencoba mengakses, membuat, mengubah, atau menghapus role berdasarkan perizinan spesifik (usermanagement.read, usermanagement.create, dll.).
- Menghapus komentar kode yang tidak digunakan pada metode middleware.
- Menggunakan class Exception secara konsisten untuk penanganan error dan pengembalian pesan error di seluruh metode.
- Memperbaiki komentar dan dokumentasi kode agar lebih relevan dengan implementasi saat ini.
- Menyederhanakan dan mengoptimasi pengaturan sorting pada data roles, termasuk sorting case-insensitive.
- Menyesuaikan kunci perizinan dari 'roles.*' menjadi 'usermanagement.*' untuk kesesuaian dengan grup perizinan.

test(roles): menambah RolesControllerTest dengan cakupan pengujian mendalam

- Membuat pengujian lengkap untuk semua metode di RolesController, termasuk pengujian izin akses, proses penyimpanan, penghapusan, dan pemulihan role.
- Menambahkan pengujian untuk fitur datatables: pencarian, penyortiran, dan pengambilan data yang benar.
- Menguji validasi perizinan granular untuk membuat, mengedit, menghapus, dan memulihkan role.
- Memastikan respon API sesuai ekspektasi saat pengguna tidak memiliki izin yang dibutuhkan.
- Memastikan penghapusan lunak dan mekanisme pemulihan role berfungsi seperti yang diharapkan.
2025-05-18 17:47:40 +07:00
Daeng Deni Mardaeni
6fae6928e7 refactor(positions): perbaikan fungsi otorisasi, optimasi logika, dan penambahan pengujian
- Menghapus konstruktor PositionsController untuk middleware autentikasi yang tidak digunakan.
- Mengganti izin `positions.*` menjadi `usermanagement.*` di semua fungsi untuk konsistensi otorisasi.
- Menambahkan validasi untuk memastikan posisi tidak dapat dihapus jika memiliki role terkait.
- Mengoptimalkan logika pada paginasi di API datatables, termasuk default page dan size.
- Menyesuaikan response error saat gagal hapus posisi dengan redirect ke halaman index.
- Menambahkan pengujian unit dan fitur terhadap CRUD dan otorisasi PositionsController:
  - Menguji akses halaman index, create, edit, dan delete posisi.
  - Validasi penyimpanan, pembaruan, dan penghapusan posisi terhadap izin pengguna.
  - Menguji akses eksport dan fungsionalitas datatables API.
  - Validasi posisi tidak terhapus jika terikat dengan role.
2025-05-18 16:34:56 +07:00
Daeng Deni Mardaeni
a6c79c72b5 feat(roles): tambahkan fitur pengelolaan posisi dan tingkat jabatan pada modul role
- Modifikasi form pembuatan role:
  - Tambahkan class `tomselect` pada elemen dropdown posisi.
  - Update label tingkat jabatan pada tampilan opsi dropdown.

- Pembaruan tabel pada halaman list role:
  - Tambah kolom baru: "Position" dan "Tingkat Jabatan".
  - Kolom baru dapat diurutkan.

- Update logika pencarian dan pengurutan:
  - Izinkan pencarian berdasarkan nama posisi dan tingkat jabatan.
  - Tambahkan pengurutan data berdasarkan nama posisi dan tingkat jabatan dengan join table `positions`.

- Perbaikan pada paginasi dan penghitungan data:
  - Revisi query agar menghindari duplikasi data akibat join tabel.

- Ekspor data:
  - Tambahkan informasi kolom baru "Position" dan "Tingkat Jabatan" pada file Excel hasil ekspor.
  - Perbarui header dan pengaturan format kolom pada file Excel.

Perubahan ini memperluas fleksibilitas pada manajemen role dengan menambahkan dimensi posisi dan tingkat jabatan baik dalam tampilan UI maupun data backend.
2025-05-17 15:07:09 +07:00
Daeng Deni Mardaeni
e9fa45a808 feat(roles): tambah fitur relasi posisi pada role
- Tambahkan relasi posisi dengan menambahkan kolom `position_id` pada tabel roles melalui migrasi.
- Perbarui fungsi pada `RolesController` untuk menyertakan posisi dalam proses CRUD.
  - Gunakan model `Position` untuk mendapatkan daftar posisi baik saat membuat maupun mengedit role.
  - Sesuaikan nama permission dari `roles.view` ke `roles.read`, `roles.store` ke `roles.create`, dan `roles.edit` ke `roles.update` agar konsisten.
- Perbarui validasi di `RoleRequest` untuk mendukung input `position_id`.
- Tambahkan properti `position_id` ke atribut `fillable` di model Role untuk mendukung mass assignment.
- Buat fungsi relasi `position()` pada model Role untuk mereferensikan ke model Position.
- Perbarui tampilan form role (`create.blade.php`):
  - Tambahkan dropdown untuk memilih posisi dalam form input.
  - Tampilkan informasi level posisi bersama dengan nama posisi dalam dropdown.
  - Sinkronisasi validasi dan nilai default sesuai dengan pengaturan posisi.
- Perbaikan minor pada query pencarian data roles, menggunakan `whereRaw` untuk pencarian case-insensitive.
2025-05-17 14:12:48 +07:00
Daeng Deni Mardaeni
1007515faa feat(usermanagement): optimalkan pencarian dan tambahkan UserFactory
- Ubah pencarian pada `PermissionsController` dan `UsersController`:
  - Gunakan metode `whereRaw` dengan `LOWER` untuk pencocokan case-insensitive pada kolom `name` dan `email`.
- Tambahkan `HasFactory` pada model `User` agar mendukung pembuatan data dummy menggunakan factory.
- Implementasi fungsi `newFactory()` pada model `User` untuk merujuk ke UserFactory.
- Tambahkan file factory baru (`UserFactory`) untuk model `User`:
  - Definisikan state default seperti `name`, `email`, `password`, dan `nik` untuk keperluan pembuatan data dummy.
  - Gunakan password secara default terenkripsi dengan `bcrypt`.
2025-05-17 14:12:25 +07:00
Daeng Deni Mardaeni
33fe30b443 feat(usermanagement): tambahkan fitur manajemen posisi
- Tambahkan model `Position` dengan atribut `code`, `name`, `level`, serta logging aktivitas dan relasi ke `roles`.
- Tambahkan migrasi untuk membuat tabel `positions` dengan soft deletes.
- Tambahkan `PositionExport` untuk kebutuhan ekspor data posisi ke Excel dengan penyaringan berdasarkan kata kunci.
- Buat `PositionsController` untuk CRUD posisi, termasuk validasi, ekspor, dan data API untuk DataTables.
  - Metode: `index`, `create`, `store`, `edit`, `update`, `destroy`, `export`, `dataForDatatables`.
- Tambahkan validasi berbasis request `PositionRequest` untuk memastikan data valid sebelum disimpan/diubah.
- Tambahkan tampilan blade untuk daftar posisi (`index.blade.php`) dan form tambah/edit posisi (`create.blade.php`) dengan dukungan DataTables dan SweetAlert.
- Perbarui file `module.json` untuk menambahkan menu "Positions" di User Management.
- Tambahkan breadcrumbs untuk halaman posisi (daftar, tambah, edit) di file `breadcrumbs.php`.
- Perbarui `routes/web.php` untuk menambahkan route terkait posisi.

Fitur ini memungkinkan pengelolaan posisi lengkap termasuk CRUD, ekspor, dan integrasi dengan DataTables.
2025-05-17 14:10:41 +07:00
Daeng Deni Mardaeni
63f2ac25c9 fix(seeder): perbarui data pada PermissionGroupSeeder dan UsersSeeder
- Hapus beberapa entri yang tidak diperlukan dari PermissionGroupSeeder.
- Perbaiki indentasi dan struktur kode di UsersSeeder.
- Pastikan semua entri pengguna memiliki atribut yang konsisten.
2025-04-27 12:26:22 +07:00
Daeng Deni Mardaeni
3d88868da1 fix(seeder): perbaiki email dan password pada UsersSeeder
- Ubah domain email dari '@lpj.id' menjadi '@ag.co.id'
- Ganti password default dari 'lpj' menjadi 'bagbag'
2025-01-15 09:38:20 +07:00
Daeng Deni Mardaeni
f7ae8ea294 fix(migration): perbaiki model Branch dan struktur migrasi pengguna
- Memperbaiki import model Branch dari Modules\Basicdata\Models\Branch.
- Menambahkan kolom 'nik' yang nullable setelah kolom 'email'.
- Menambahkan foreign key untuk 'branch_id' yang nullable setelah kolom 'nik'.
- Memperbaiki metode up dan down untuk migrasi pengguna.
2025-01-15 09:38:08 +07:00
Daeng Deni Mardaeni
b5c115a67e Perbaiki import model Branch di User.php
- Mengubah impor model Branch dari `Modules\Lpj\Models\Branch` menjadi `Modules\Basicdata\Models\Branch`.
- Perubahan ini memastikan file menggunakan namespace yang benar agar sesuai dengan struktur modul yang telah diperbarui.
2024-12-29 19:58:58 +07:00
Daeng Deni Mardaeni
c76630a5ea - Perbarui ikon warna pada menu User Management
- Menambahkan kelas "text-primary" pada ikon menu User Management di file `module.json`. Perubahan ini bertujuan untuk meningkatkan konsistensi warna dan tata letak ikon pada aplikasi. Tidak ada perubahan logika atau fungsi lainnya yang terpengaruh.
2024-12-22 16:16:27 +07:00
Daeng Deni Mardaeni
5678255090 Tambah fungsi update profil dan ganti password
Menambahkan fungsi update profil dan ganti password di UsersController. Menyesuaikan rute dan formulir di tampilan profil untuk mendukung fitur ini.
2024-11-17 12:48:52 +07:00
Daeng Deni Mardaeni
a2bff61998 Tambahkan kolom role di tabel pengguna
Mengubah query di UsersController untuk mengambil data roles. Memperbarui tampilan di users/index.blade.php untuk menampilkan kolom role dengan informasi yang sesuai.
2024-11-17 11:43:19 +07:00
Daeng Deni Mardaeni
f362bdd32f Tambah sorting dan perbaiki lokasi perhitungan total record
Menambahkan logika sorting berdasarkan `sortOrder` dan `sortField` jika ada pada permintaan. Selain itu, memindahkan perhitungan total record ke posisi yang lebih tepat untuk menghindari pengaruh dari pagination.
2024-11-08 09:59:03 +07:00
Daeng Deni Mardaeni
a25194dc07 Handle undefined user scenario in role input
Added a conditional check to handle cases where the user is undefined when rendering role input fields. This ensures that the role selection logic works correctly for both new user creation and existing user updates.
2024-10-31 11:39:34 +07:00
Daeng Deni Mardaeni
b185a60b53 Fix role assignment check on user creation form
Replaced Auth user roles check with the roles of the user being created. This ensures the correct role is pre-selected based on the user’s roles, rather than the creator's roles.
2024-10-31 11:27:34 +07:00
40 changed files with 3569 additions and 663 deletions

View File

@@ -21,7 +21,8 @@ class PermissionExport implements WithColumnFormatting, WithHeadings, FromCollec
}
public function map($row): array{
$role = $row->roles->pluck('name')->toArray();
// Convert the array to a collection before using pluck
$role = collect($row->roles)->pluck('name')->toArray();
return [
$row->id,
$row->name,

View File

@@ -0,0 +1,66 @@
<?php
namespace Modules\Usermanagement\Exports;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Modules\Usermanagement\Models\Position;
class PositionExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping
{
protected $search;
public function __construct($search = null)
{
$this->search = $search;
}
public function collection()
{
$query = Position::query();
if (!empty($this->search)) {
$search = $this->search;
$query->where(function ($q) use ($search) {
$q->whereRaw('LOWER(code) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('CAST(level AS TEXT) LIKE ?', ['%' . $search . '%']);
});
}
return $query->get();
}
public function map($row): array
{
return [
$row->id,
$row->code,
$row->name,
$row->level,
$row->created_at
];
}
public function headings(): array
{
return [
'ID',
'Code',
'Name',
'Tingkat Jabatan',
'Created At'
];
}
public function columnFormats(): array
{
return [
'A' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER,
'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER,
'E' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME
];
}
}

View File

@@ -11,13 +11,15 @@ use Modules\Usermanagement\Models\Role;
class RolesExport implements WithColumnFormatting, WithHeadings, FromCollection, withMapping
{
public function collection(){
return Role::all();
return Role::with('position')->get();
}
public function map($row): array{
return [
$row->id,
$row->name,
$row->position ? $row->position->name : '-',
$row->position ? $row->position->level : '-',
$row->created_at
];
}
@@ -25,6 +27,8 @@ class RolesExport implements WithColumnFormatting, WithHeadings, FromCollection,
return [
'ID',
'Role',
'Position',
'Tingkat Jabatan',
'Created At'
];
}
@@ -32,7 +36,8 @@ class RolesExport implements WithColumnFormatting, WithHeadings, FromCollection,
public function columnFormats(): array{
return [
'A' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER,
'C' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME
'D' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER,
'E' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME
];
}
}

View File

@@ -9,10 +9,21 @@ use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use Maatwebsite\Excel\Concerns\WithColumnFormatting;
use Modules\Usermanagement\Models\User;
class UsersExport implements WithColumnFormatting, WithHeadings, FromCollection, withMapping
class UsersExport implements WithColumnFormatting, WithHeadings, FromCollection, WithMapping
{
protected $search;
public function __construct($search = null)
{
$this->search = $search;
}
public function collection(){
return User::all();
return User::query()
->when($this->search, function ($query) {
$query->whereAny(['name','email'],'like','%'.$this->search.'%');
})
->get();
}
public function map($row): array{
@@ -21,7 +32,8 @@ class UsersExport implements WithColumnFormatting, WithHeadings, FromCollection,
$row->name,
$row->email,
$row->nik,
$row->branch->name,
$row->branch?->name,
$row->roles?->pluck('name')->implode(', '),
$row->created_at
];
}
@@ -32,6 +44,7 @@ class UsersExport implements WithColumnFormatting, WithHeadings, FromCollection,
'Email',
'NIK',
'Branch',
'Roles',
'Created At'
];
}
@@ -39,7 +52,7 @@ class UsersExport implements WithColumnFormatting, WithHeadings, FromCollection,
public function columnFormats(): array{
return [
'A' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER,
'F' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME
'G' => \PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_DATE_DATETIME
];
}
}

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Support\Facades\Auth;
if (!function_exists('check_permission')) {
function check_permission(string $permission, bool $abort = true): bool
{
$user = Auth::user();
if (!$user || !$user->can($permission)) {
if ($abort) {
abort(403, 'Unauthorized');
}
return false;
}
return true;
}
}
if (!function_exists('user_has_role')) {
function user_has_role(array $roles): bool
{
$user = Auth::user();
if (!$user) return false;
return $user->roles->pluck('name')->intersect($roles)->isNotEmpty();
}
}

View File

@@ -23,7 +23,24 @@
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
protected $user;
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*/
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
/**
* Display a listing of the resource.
@@ -34,8 +51,8 @@
public function index()
{
// Check if the authenticated user has the required permission to view permissions
if (is_null($this->user) || !$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view permissions.');
}
// Return the view for displaying the permissions
@@ -53,8 +70,8 @@
public function store(PermissionRequest $request)
{
// Check if the authenticated user has the required permission to store permissions
if (is_null($this->user) || !$this->user->can('permissions.store')) {
//abort(403, 'Sorry! You are not allowed to store permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create permissions.');
}
$validate = $request->validated();
@@ -70,7 +87,8 @@
$group_name . '.delete',
$group_name . '.export',
$group_name . '.authorize',
$group_name . '.report'
$group_name . '.report',
$group_name . '.restore'
];
foreach ($data as $permission) {
@@ -97,24 +115,14 @@
public function create()
{
// Check if the authenticated user has the required permission to create permissions
if (is_null($this->user) || !$this->user->can('permissions.create')) {
//abort(403, 'Sorry! You are not allowed to create permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create permissions.');
}
// Return the view for creating a new role
return view('usermanagement::permissions.create');
}
public function show($id){
// Check if the authenticated user has the required permission to view permissions
if (is_null($this->user) ||!$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view permissions.');
}
// Return the view for editing the role
return view('usermanagement::permissions.create');
}
/**
* Show the form for editing the specified resource.
*
@@ -126,8 +134,8 @@
public function edit($id)
{
// Check if the authenticated user has the required permission to edit permissions
if (is_null($this->user) || !$this->user->can('permissions.edit')) {
//abort(403, 'Sorry! You are not allowed to edit permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit permissions.');
}
$permission = PermissionGroup::find($id);
@@ -150,8 +158,8 @@
public function update(PermissionRequest $request, $id)
{
// Check if the authenticated user has the required permission to update permissions
if (is_null($this->user) || !$this->user->can('permissions.update')) {
//abort(403, 'Sorry! You are not allowed to update permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update permissions.');
}
$validated = $request->validated();
@@ -173,7 +181,8 @@
$group_name . '.delete',
$group_name . '.export',
$group_name . '.authorize',
$group_name . '.report'
$group_name . '.report',
$group_name . '.restore'
];
$i = 0;
@@ -202,8 +211,8 @@
public function destroy($id)
{
// Check if the authenticated user has the required permission to delete permissions
if (is_null($this->user) || !$this->user->can('permissions.delete')) {
//abort(403, 'Sorry! You are not allowed to delete permissions.');
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['message' => 'Sorry! You are not allowed to delete permissions.','success' => false]);
}
$permission = PermissionGroup::find($id);
@@ -214,7 +223,7 @@
}
// Redirect back to the permissions index with a success message
echo json_encode(['message' => 'Permission deleted successfully.', 'success' => true]);
return response()->json(['message' => 'Permission deleted successfully.','success' => true]);
}
/**
@@ -228,7 +237,7 @@
public function restore($id)
{
// Check if the authenticated user has the required permission to restore permissions
if (is_null($this->user) || !$this->user->can('permissions.restore')) {
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore permissions.');
}
@@ -257,8 +266,8 @@
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('permissions.view')) {
//abort(403, 'Sorry! You are not allowed to view users.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json(['message' => 'Sorry! You are not allowed to view permissions.','success' => false]);
}
// Retrieve data from the database
@@ -267,9 +276,7 @@
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'LIKE', "%$search%");
});
$query->where('name', 'like', '%' . $search . '%');
}
// Apply sorting if provided
@@ -294,14 +301,11 @@
// Get the filtered count of records
$filteredRecords = $query->count();
// Get the data for the current page
$permissions = $query->get();
$data = $query->get();
$permissions = $permissions->map(function ($permission) {
$data = $data->map(function ($permission) {
$permission->roles = $permission->roles($permission);
return $permission;
});
@@ -319,12 +323,17 @@
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $permissions,
'data' => $data,
]);
}
public function export()
{
// Check if the authenticated user has the required permission to export permissions
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export permissions.');
}
return Excel::download(new PermissionExport, 'permissions.xlsx');
}
}

View File

@@ -0,0 +1,291 @@
<?php
namespace Modules\Usermanagement\Http\Controllers;
use App\Http\Controllers\Controller;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Usermanagement\Exports\PositionExport;
use Modules\Usermanagement\Http\Requests\PositionRequest;
use Modules\Usermanagement\Models\Position;
/**
* Class PositionsController
*
* This controller is responsible for managing positions within the application.
*
* @package Modules\Usermanagement\Http\Controllers
*/
class PositionsController extends Controller
{
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
protected $user;
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*/
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function index()
{
// Check if the authenticated user has the required permission to view positions
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view positions.');
}
// Fetch all positions from the database
$positions = Position::all();
// Return the view for displaying the positions
return view('usermanagement::positions.index', compact('positions'));
}
/**
* Store a newly created resource in storage.
*
* @param \Modules\Usermanagement\Http\Requests\PositionRequest $request
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(PositionRequest $request)
{
// Check if the authenticated user has the required permission to store positions
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create positions.');
}
// Get validated data
$validated = $request->validated();
try {
// If no errors, save the position to the database
$position = Position::create($validated);
// Redirect to the positions index page with a success message
return redirect()->route('users.positions.index')
->with('success', 'Position created successfully.');
} catch (Exception $e) {
// If an error occurs, redirect back with an error message
return redirect()->back()
->with('error', 'An error occurred while creating the position: ' . $e->getMessage())
->withInput();
}
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function create()
{
// Check if the authenticated user has the required permission to create positions
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create positions.');
}
// Return the view for creating a new position
return view('usermanagement::positions.create');
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function edit($id)
{
// Check if the authenticated user has the required permission to edit positions
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit positions.');
}
// Find the position by ID
$position = Position::findOrFail($id);
// Return the view for editing the position
return view('usermanagement::positions.create', compact('position'));
}
/**
* Update the specified resource in storage.
*
* @param \Modules\Usermanagement\Http\Requests\PositionRequest $request
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
public function update(PositionRequest $request, $id)
{
// Check if the authenticated user has the required permission to update positions
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update positions.');
}
// Find the position by ID
$position = Position::findOrFail($id);
// Get validated data
$validated = $request->validated();
try {
// If no errors, update the position in the database
$position->update($validated);
// Redirect to the positions index page with a success message
return redirect()->route('users.positions.index')
->with('success', 'Position updated successfully.');
} catch (Exception $e) {
// If an error occurs, redirect back with an error message
return redirect()->back()
->with('error', 'An error occurred while updating the position: ' . $e->getMessage())
->withInput();
}
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy($id)
{
// Check if the authenticated user has the required permission to delete positions
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['message' => 'Sorry! You are not allowed to delete positions.','success' => false]);
}
// Find the position by ID
$position = Position::findOrFail($id);
// Check if the position has associated roles
if ($position->roles()->count() > 0) {
return redirect()->route('users.positions.index')
->with('error', 'Cannot delete position because it has associated roles.');
}
try {
// If no errors, delete the position from the database
$position->delete();
// Redirect to the positions index page with a success message
return redirect()->route('users.positions.index')
->with('success', 'Position deleted successfully.');
} catch (Exception $e) {
// If an error occurs, redirect back with an error message
return redirect()->route('users.positions.index')
->with('error', 'An error occurred while deleting the position: ' . $e->getMessage());
}
}
/**
* Process support datatables ajax request.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\JsonResponse
*/
public function dataForDatatables(Request $request)
{
// Check if the authenticated user has the required permission to view positions
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json(['message' => 'Sorry! You are not allowed to view positions.','success' => false]);
}
// Retrieve data from the database
$query = Position::query();
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->whereAny(['code', 'name', 'level'], 'like', '%' . $search . '%');
}
// Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
$query->orderBy($column, $order);
}
// Get the total count of records
$totalRecords = $query->count();
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
$page = $request->get('page');
$size = $request->get('size');
$offset = ($page - 1) * $size; // Calculate the offset
$query->skip($offset)->take($size);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Get the data for the current page
$data = $query->get();
// Calculate the page count
$size = $request->get('size', 10); // Default to 10 if not set
$pageCount = $size > 0 ? ceil($totalRecords / $size) : 0;
// Calculate the current page number
$currentPage = $request->get('page', 1); // Default to page 1 if not set
// Return the response data as a JSON object
return response()->json([
'draw' => $request->get('draw'),
'recordsTotal' => $totalRecords,
'recordsFiltered' => $filteredRecords,
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $data,
]);
}
/**
* Export positions to Excel.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function export(Request $request)
{
// Check if the authenticated user has the required permission to export positions
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export positions.');
}
// Get search parameter from request
$search = $request->get('search');
return Excel::download(new PositionExport($search), 'positions.xlsx');
}
}

View File

@@ -10,7 +10,9 @@
use Modules\Usermanagement\Http\Requests\RoleRequest;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Modules\Usermanagement\Models\Position;
use Modules\Usermanagement\Models\Role;
use Exception;
/**
* Class RolesController
@@ -24,20 +26,24 @@
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
protected $user;
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*
*/
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::guard('web')->user();
$this->user = Auth::user();
return $next($request);
});
}*/
}
/**
* Display a listing of the resource.
@@ -48,8 +54,8 @@
public function index()
{
// Check if the authenticated user has the required permission to view roles
if (is_null($this->user) || !$this->user->can('roles.view')) {
//abort(403, 'Sorry! You are not allowed to view roles.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view roles.');
}
// Fetch all roles from the database
@@ -70,14 +76,14 @@
public function store(RoleRequest $request)
{
// Check if the authenticated user has the required permission to store roles
if (is_null($this->user) || !$this->user->can('roles.store')) {
//abort(403, 'Sorry! You are not allowed to store roles.');
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to store roles.');
}
$validated = $request->validated();
if($validated){
try{
if ($validated) {
try {
// If no errors, save the role to the database
$role = Role::create($validated);
@@ -85,18 +91,22 @@
$permissions = Permission::whereIn('id', $permissions)->pluck('name')->toArray();
if (!empty($permissions)) {
$role = Role::find($role->id);
try{
try {
$role->syncPermissions($permissions);
} catch (\Exception $e) {
echo json_encode(['message' => $e->getMessage(), 'success']);exit;
} catch (Exception $e) {
return redirect()
->route('users.roles.index')
->with('error', 'Failed to sync permissions: ' . $e->getMessage());
}
}
// Redirect back to the roles index with a success message
return redirect()->route('users.roles.index')->with('success', 'Role created successfully.');
} catch (\Exception $e) {
} catch (Exception $e) {
// Redirect back to the roles index with an error message
return redirect()->route('users.roles.index')->with('error', 'Failed to create role. Please try again.');
return redirect()
->route('users.roles.index')
->with('error', 'Failed to create role. Please try again.');
}
}
}
@@ -110,37 +120,14 @@
public function create()
{
// Check if the authenticated user has the required permission to create roles
if (is_null($this->user) || !$this->user->can('roles.create')) {
//abort(403, 'Sorry! You are not allowed to create roles.');
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create roles.');
}
$permissiongroups = PermissionGroup::all();
$positions = Position::all();
// Return the view for creating a new role
return view('usermanagement::roles.create',compact('permissiongroups'));
}
/**
* Display the specified resource.
*
* @param int $id
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function show($id)
{
// Check if the authenticated user has the required permission to view roles
if (is_null($this->user) || !$this->user->can('roles.view')) {
abort(403, 'Sorry! You are not allowed to view roles.');
}
// Fetch the specified role from the database
$role = Role::find($id);
// Return the view for displaying the role
return view('usermanagement::roles.show', compact('role'));
return view('usermanagement::roles.create', compact('permissiongroups', 'positions'));
}
/**
@@ -154,16 +141,17 @@
public function edit($id)
{
// Check if the authenticated user has the required permission to edit roles
if (is_null($this->user) || !$this->user->can('roles.edit')) {
//abort(403, 'Sorry! You are not allowed to edit roles.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit roles.');
}
// Fetch the specified role from the database
$role = Role::find($id);
$permissions = Permission::all();
$permissiongroups = PermissionGroup::all();
$positions = Position::all();
// Return the view for editing the role
return view('usermanagement::roles.create', compact('role','permissions','permissiongroups'));
return view('usermanagement::roles.create', compact('role', 'permissions', 'permissiongroups', 'positions'));
}
@@ -180,13 +168,13 @@
public function update(RoleRequest $request, $id)
{
// Check if the authenticated user has the required permission to update roles
if (is_null($this->user) || !$this->user->can('roles.update')) {
//abort(403, 'Sorry! You are not allowed to update roles.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update roles.');
}
$validated = $request->validated();
if($validated){
try{
if ($validated) {
try {
// If no errors, update the role in the database
$role = Role::find($id);
$role->update($request->all());
@@ -195,19 +183,23 @@
$permissions = Permission::whereIn('id', $permissions)->pluck('name')->toArray();
if (!empty($permissions)) {
$role = Role::find($role->id);
try{
try {
$role->syncPermissions($permissions);
} catch (\Exception $e) {
echo json_encode(['message' => $e->getMessage(), 'success']);exit;
} catch (Exception $e) {
return redirect()
->route('users.roles.index')
->with('error', 'Failed to sync permissions: ' . $e->getMessage());
}
}
// Redirect back to the roles index with a success message
return redirect()->route('users.roles.index')->with('success', 'Role updated successfully.');
} catch (\Exception $e) {
} catch (Exception $e) {
// Redirect back to the roles index with an error message
return redirect()->route('users.roles.index')->with('error', 'Failed to update role. Please try again.');
return redirect()
->route('users.roles.index')
->with('error', 'Failed to update role. Please try again.');
}
}
}
@@ -220,21 +212,23 @@
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
// Check if the authenticated user has the required permission to delete roles
if (is_null($this->user) || !$this->user->can('roles.delete')) {
//abort(403, 'Sorry! You are not allowed to delete roles.');
// Check if the authenticated user has the required permission to delete currencies
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['success' => false, 'message' => 'Sorry! You are not allowed to delete roles.'], 403);
}
// Fetch the specified role from the database
$role = Role::find($id);
try {
// Delete from database
$currency = Role::find($id);
$currency->delete();
// Delete the role
$role->delete();
// Redirect back to the roles index with a success message
echo json_encode(['message' => 'Role deleted successfully.', 'success' => true]);
return response()->json(['success' => true, 'message' => 'Role deleted successfully.']);
} catch (Exception $e) {
return response()->json(['success' => false, 'message' => 'Failed to delete role.']);
}
}
/**
@@ -248,7 +242,7 @@
public function restore($id)
{
// Check if the authenticated user has the required permission to restore roles
if (is_null($this->user) || !$this->user->can('roles.restore')) {
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore roles.');
}
@@ -272,18 +266,25 @@
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('roles.view')) {
//abort(403, 'Sorry! You are not allowed to view users.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json(['message' => 'Sorry! You are not allowed to view roles.','success' => false]);
}
// Retrieve data from the database
$query = Role::query();
if(!$this->user->hasRole('administrator')){
$query->where('name', '!=', 'administrator');
}
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->where('name', 'LIKE', "%$search%");
$q->where('name', 'like', '%' . $search . '%')
->orWhereHas('position', function ($query) use ($search) {
$query->whereAny(['name', 'level'], 'like','%'.$search.'%');
});
});
}
@@ -291,11 +292,29 @@
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
if ($column === 'position_name') {
$query->leftJoin('positions', 'roles.position_id', '=', 'positions.id')
->orderBy('positions.name', $order)
->select('roles.*'); // Select only from roles table to avoid column conflicts
} else if ($column === 'level') {
$query->leftJoin('positions', 'roles.position_id', '=', 'positions.id')
->orderBy('positions.level', $order)
->select('roles.*'); // Select only from roles table to avoid column conflicts
} else {
if ($column === 'name') {
$query->orderBy('roles.name', $order);
} else {
$query->orderBy($column, $order);
}
}
}
// Get the total count of records
$totalRecords = $query->count();
// Create a copy of the query for counting
$countQuery = clone $query;
// Get the total count of records (without joins to avoid duplicates)
$totalRecords = Role::count();
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
@@ -306,17 +325,18 @@
$query->skip($offset)->take($size);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Get the filtered count of records - use distinct to avoid duplicates from joins
$filteredRecords = $countQuery->distinct()->count('roles.id');
// Get the data for the current page
$roles = $query->get();
$data = $query->with('position')->get();
// Calculate the page count
$pageCount = ceil($totalRecords/$request->get('size'));
// Calculate the page count - ensure we don't divide by zero
$pageSize = $request->get('size', 10); // Default to 10 if not provided
$pageCount = $pageSize > 0 ? ceil($totalRecords / $pageSize) : 0;
// Calculate the current page number
$currentPage = 0 + 1;
$currentPage = $request->get('page') ?: 1;
// Return the response data as a JSON object
return response()->json([
@@ -326,12 +346,16 @@
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $roles,
'data' => $data,
]);
}
public function export()
{
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export roles.');
}
return Excel::download(new RolesExport, 'roles.xlsx');
}
}

View File

@@ -6,12 +6,15 @@
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Modules\Lpj\Models\Branch;
use Modules\Basicdata\Models\Branch;
use Modules\Usermanagement\Exports\UsersExport;
use Modules\Usermanagement\Http\Requests\User as UserRequest;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\User;
use Illuminate\Support\Facades\Storage;
/**
* Class UsersController
@@ -25,20 +28,24 @@
/**
* @var \Illuminate\Contracts\Auth\Authenticatable|null
*/
public $user;
protected $user;
/**
* UsersController constructor.
*
* Initializes the user property with the authenticated user.
*/
// public function __construct()
// {
// $this->middleware(function ($request, $next) {
// $this->user = Auth::guard('web')->user();
// return $next($request);
// });
// }
public function __construct()
{
// Mengatur middleware auth
$this->middleware('auth');
// Mengatur user setelah middleware auth dijalankan
$this->middleware(function ($request, $next) {
$this->user = Auth::user();
return $next($request);
});
}
/**
* Display a listing of the resource.
@@ -48,8 +55,8 @@
*/
public function index()
{
if (is_null($this->user) || !$this->user->can('users.view')) {
//abort(403, 'Sorry! You are not allowed to view users.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
abort(403, 'Sorry! You are not allowed to view users.');
}
return view('usermanagement::users.index');
@@ -65,23 +72,35 @@
*/
public function dataForDatatables(Request $request)
{
if (is_null($this->user) || !$this->user->can('users.view')) {
//abort(403, 'Sorry! You are not allowed to view users.');
if (is_null($this->user) || !$this->user->can('usermanagement.read')) {
return response()->json(['message' => 'Sorry! You are not allowed to view users.','success' => false]);
}
// Retrieve data from the database
$query = User::query();
if(!$this->user->hasRole('administrator')){
$query->whereHas('roles', function($q){
$q->where('name', '!=', 'administrator');
});
}
// Apply search filter if provided
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q
->where('name', 'LIKE', "%$search%")
->orWhere('email', 'LIKE', "%$search%");
});
$query->whereAny(['name', 'email'], 'like', '%'.$search.'%');
}
// Apply sorting if provided
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
$query->orderBy($column, $order);
}
// Get the total count of records
$totalRecords = $query->count();
// Apply pagination if provided
if ($request->has('page') && $request->has('size')) {
$page = $request->get('page');
@@ -91,21 +110,11 @@
$query->skip($offset)->take($size);
}
// Get the total count of records
$totalRecords = $query->count();
// Apply pagination if provided
if ($request->has('start') && $request->has('length')) {
$start = $request->get('start');
$length = $request->get('length');
$query->skip($start)->take($length);
}
// Get the filtered count of records
$filteredRecords = $query->count();
// Get the data for the current page
$users = $query->with('branch')->get();
$data = $query->with(['branch', 'roles'])->get();
// Calculate the page count
$pageCount = ceil($totalRecords / $request->get('size'));
@@ -121,7 +130,7 @@
'pageCount' => $pageCount,
'page' => $currentPage,
'totalCount' => $totalRecords,
'data' => $users,
'data' => $data,
]);
}
@@ -135,16 +144,191 @@
*/
public function edit($id)
{
if (is_null($this->user) || !$this->user->can('users.edit')) {
//abort(403, 'Sorry! You are not allowed to edit users.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to edit users.');
}
$user = User::find($id);
$roles = Role::all();
if(!$this->user->hasRole('administrator')){
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('user', 'roles', 'branches'));
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.delete')) {
return response()->json(['message' => 'Sorry! You are not allowed to delete users.','success' => false]);
}
$user = User::find($id);
$user->delete();
return response()->json(['message' => 'User deleted successfully.', 'success' => true]);
}
/**
* Restore the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($id)
{
if (is_null($this->user) || !$this->user->can('usermanagement.restore')) {
abort(403, 'Sorry! You are not allowed to restore users.');
}
$user = User::withTrashed()->find($id);
$user->restore();
return redirect()->route('users.index')->with('success', 'User restored successfully.');
}
/**
* Store a newly created resource in storage.
*
* This function handles the creation of a new user in the application. It validates the incoming request data,
* creates a new user record in the database, and redirects the user to the users index page with a success message.
*
* @param \Modules\Usermanagement\Http\Requests\User $request The incoming request containing the user data.
*
* @return \Illuminate\Http\RedirectResponse Redirects to the users index page with a success message upon successful creation.
* @return \Illuminate\Http\RedirectResponse Redirects to the users create page upon validation failure.
*/
public function store(UserRequest $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$validated = $request->validated();
if ($validated) {
$user = User::create($validated);
if ($user) {
if ($request->roles) {
$user->assignRole($request->roles);
}
return redirect()->route('users.index')->with('success', 'User created successfully.');
}
}
return redirect()->route('users.create');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
if (is_null($this->user) || !$this->user->can('usermanagement.create')) {
abort(403, 'Sorry! You are not allowed to create a user.');
}
$roles = Role::all();
if(!$this->user->hasRole('administrator')){
$roles = $roles->where('name', '!=', 'administrator');
}
$branches = Branch::all();
return view('usermanagement::users.create', compact('roles', 'branches'));
}
public function export(Request $request)
{
if (is_null($this->user) || !$this->user->can('usermanagement.export')) {
abort(403, 'Sorry! You are not allowed to export users.');
}
// Get search parameter from request
$search = $request->get('search');
return Excel::download(new UsersExport($search), 'users.xlsx');
}
public function profile()
{
$user = Auth::user();
return view('usermanagement::users.profile', compact('user'));
}
public function updateProfile(Request $request)
{
$user = Auth::user();
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
'sign' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
]);
$user->name = $validatedData['name'];
$user->email = $validatedData['email'];
$user->nik = $validatedData['nik'];
if ($request->hasFile('sign')) {
// Delete old e-sign if exists
if ($user->sign) {
Storage::disk('public')->delete('signatures/' . $user->id . '/' . $user->sign);
}
$sign = $request->file('sign');
$signName = time() . '.' . $sign->getClientOriginalExtension();
// Make sure the directory exists
Storage::disk('public')->makeDirectory('signatures/' . $user->id);
// Store the file
$sign->storeAs('signatures/' . $user->id, $signName, 'public');
$user->sign = $signName;
}
$user->save();
return redirect()->route('users.profile')->with('success', 'Profile updated successfully.');
}
public function changePassword(Request $request)
{
$validator = Validator::make($request->all(), [
'current_password' => 'required',
'password' => 'required|string|min:8|confirmed',
], [
'password_confirmation' => 'The new password confirmation does not match.',
]);
if ($validator->fails()) {
return back()->withErrors($validator)->withInput();
}
$user = Auth::user();
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'The current password is incorrect.']);
}
$user->password = Hash::make($request->password);
$user->save();
return redirect()->route('users.profile')->with('success', 'Password changed successfully.');
}
/**
* Update the specified resource in storage.
*
@@ -156,14 +340,14 @@
*/
public function update(UserRequest $request, $id)
{
if (is_null($this->user) || !$this->user->can('users.update')) {
//abort(403, 'Sorry! You are not allowed to update users.');
if (is_null($this->user) || !$this->user->can('usermanagement.update')) {
abort(403, 'Sorry! You are not allowed to update users.');
}
$validated = $request->validated();
if($validated) {
try{
if ($validated) {
try {
$user = User::find($id);
if ($request->hasFile('sign')) {
$sign = $request->file('sign');
@@ -190,101 +374,4 @@
return redirect()->route('users.index')->with('success', 'User updated successfully.');
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function destroy($id)
{
if (is_null($this->user) || !$this->user->can('users.delete')) {
//abort(403, 'Sorry! You are not allowed to delete users.');
}
$user = User::find($id);
$user->delete();
echo json_encode(['message' => 'User deleted successfully.', 'success' => true]);
}
/**
* Restore the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\RedirectResponse
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function restore($id)
{
if (is_null($this->user) || !$this->user->can('users.restore')) {
abort(403, 'Sorry! You are not allowed to restore users.');
}
$user = User::withTrashed()->find($id);
$user->restore();
return redirect()->route('users.index')->with('success', 'User restored successfully.');
}
/**
* Store a newly created resource in storage.
*
* This function handles the creation of a new user in the application. It validates the incoming request data,
* creates a new user record in the database, and redirects the user to the users index page with a success message.
*
* @param \Modules\Usermanagement\Http\Requests\User $request The incoming request containing the user data.
*
* @return \Illuminate\Http\RedirectResponse Redirects to the users index page with a success message upon successful creation.
* @return \Illuminate\Http\RedirectResponse Redirects to the users create page upon validation failure.
*/
public function store(UserRequest $request)
{
$validated = $request->validated();
if ($validated) {
$user = User::create($validated);
if ($user) {
if ($request->roles) {
$user->assignRole($request->roles);
}
return redirect()->route('users.index')->with('success', 'User created successfully.');
}
}
return redirect()->route('users.create');
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
public function create()
{
if (is_null($this->user) || !$this->user->can('users.create')) {
//abort(403, 'Sorry! You are not allowed to create a user.');
}
$roles = Role::all();
$branches = Branch::all();
return view('usermanagement::users.create', compact('roles', 'branches'));
}
public function export()
{
return Excel::download(new UsersExport, 'users.xlsx');
}
public function profile()
{
$user = Auth::user();
return view('usermanagement::users.profile', compact('user'));
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Modules\Usermanagement\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PositionRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
$rules = [
'name' => 'required|string',
'level' => 'required|integer',
];
if ($this->method() === 'PUT') {
$rules['code'] = 'required|string|unique:positions,code,' . $this->id;
} else {
$rules['code'] = 'required|string|unique:positions,code';
}
return $rules;
}
}

View File

@@ -23,6 +23,7 @@
$rules = [
'guard_names' => 'required|string|in:web,api',
'position_id' => 'nullable|exists:positions,id',
];
if ($this->method() === 'PUT') {
@@ -41,6 +42,3 @@
]);
}
}

View File

@@ -6,7 +6,7 @@
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
use Wildside\Userstamps\Userstamps;
use Mattiverse\Userstamps\Traits\Userstamps;
/**

View File

@@ -2,13 +2,14 @@
namespace Modules\Usermanagement\Models;
use Spatie\Activitylog\LogOptions;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
use Spatie\Permission\Models\Permission as SpatiePermission;
class Permission extends SpatiePermission
{
use LogsActivity;
use LogsActivity, SoftDeletes;
/**
* Retrieve the activity log options for this permission.

View File

@@ -12,6 +12,17 @@
'slug'
];
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
if (!$model->slug) {
$model->slug = \Str::slug($model->name);
}
});
}
/**
* Retrieves all permissions associated with a given permission group ID.
*

42
app/Models/Position.php Normal file
View File

@@ -0,0 +1,42 @@
<?php
namespace Modules\Usermanagement\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Spatie\Activitylog\LogOptions;
use Spatie\Activitylog\Traits\LogsActivity;
class Position extends Model
{
use SoftDeletes, LogsActivity;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'code',
'name',
'level',
];
/**
* Retrieve the activity log options for this position.
*
* @return LogOptions The activity log options.
*/
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()->logAll()->useLogName('User Management|Positions : ');
}
/**
* Get the roles associated with this position.
*/
public function roles()
{
return $this->hasMany(Role::class);
}
}

View File

@@ -11,6 +11,17 @@
{
use softDeletes, LogsActivity;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'guard_name',
'position_id',
];
/**
* Retrieve the activity log options for this role.
*
@@ -22,4 +33,11 @@
return LogOptions::defaults()->logAll()->useLogName('User Management|Roles : ');
}
/**
* Get the position that owns the role.
*/
public function position()
{
return $this->belongsTo(Position::class);
}
}

View File

@@ -1,15 +1,17 @@
<?php
namespace Modules\Usermanagement\Models;
namespace Modules\Usermanagement\Models;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Modules\Lpj\Models\Branch;
use Spatie\Permission\Traits\HasRoles;
use Wildside\Userstamps\Userstamps;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Modules\Basicdata\Models\Branch;
use Modules\Adk\Models\Appointment;
use Spatie\Permission\Traits\HasRoles;
use Mattiverse\Userstamps\Traits\Userstamps;
/**
/**
* Class User
*
* This class extends the Laravel's Authenticatable class and represents a User in the application.
@@ -22,9 +24,9 @@
*
* @package App\Models
*/
class User extends Authenticatable
{
use Notifiable, Userstamps, HasRoles, softDeletes;
class User extends Authenticatable
{
use HasFactory, Notifiable, Userstamps, HasRoles, softDeletes;
protected $guard_name = ['web'];
@@ -67,8 +69,7 @@
*
* @return array<string, string>
*/
protected function casts()
: array
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
@@ -77,8 +78,28 @@
];
}
public function branch(){
public function branch()
{
return $this->belongsTo(Branch::class);
}
/**
* Create a new factory instance for the model.
*
* @return \Illuminate\Database\Eloquent\Factories\Factory
*/
protected static function newFactory()
{
return \Modules\Usermanagement\Database\Factories\UserFactory::new();
}
/**
* Get all of the appointments for the User
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function appointments()
{
return $this->hasMany(Appointment::class, 'admin_id');
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Modules\Usermanagement\Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Modules\Usermanagement\Models\User;
class UserFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = User::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => $this->faker->name(),
'email' => $this->faker->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => bcrypt('password'), // Default password for testing
'remember_token' => Str::random(10),
'nik' => $this->faker->unique()->numerify('##########'),
];
}
}

View File

@@ -1,16 +1,16 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Modules\Lpj\Models\Branch;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Modules\Basicdata\Models\Branch;
return new class extends Migration
{
return new class extends Migration {
/**
* Run the migrations.
*/
public function up(): void
public function up()
: void
{
Schema::table('users', function (Blueprint $table) {
$table->string('nik')->nullable()->after('email');
@@ -21,7 +21,8 @@ use Illuminate\Support\Facades\Schema;
/**
* Reverse the migrations.
*/
public function down(): void
public function down()
: void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('nik');
@@ -29,4 +30,4 @@ use Illuminate\Support\Facades\Schema;
$table->dropColumn('branch_id');
});
}
};
};

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('positions', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('code')->unique();
$table->string('name');
$table->integer('level');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('positions');
}
};

View File

@@ -0,0 +1,45 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
}
Schema::table($tableNames['roles'], function (Blueprint $table) {
$table->unsignedBigInteger('position_id')->nullable()->after('guard_name');
$table->foreign('position_id')
->references('id')
->on('positions')
->onDelete('set null');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$tableNames = config('permission.table_names');
if (empty($tableNames)) {
throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
}
Schema::table($tableNames['roles'], function (Blueprint $table) {
$table->dropForeign(['position_id']);
$table->dropColumn('position_id');
});
}
};

View File

@@ -28,13 +28,7 @@
public function data()
{
return [
['name' => 'usermanagement'],
['name' => 'basic-data'],
['name' => 'permohonan'],
['name' => 'admin'],
['name' => 'senior-officer'],
['name' => 'penilai'],
['name' => 'surveyor']
['name' => 'usermanagement']
];
}
}

View File

@@ -53,7 +53,7 @@
{
$actions = [];
// list of permission actions
$crud = ['create', 'read', 'update', 'delete','export', 'authorize', 'report'];
$crud = ['create', 'read', 'update', 'delete','export', 'authorize', 'report','restore'];
foreach ($crud as $value) {

View File

@@ -27,13 +27,7 @@
public function data()
{
return [
['name' => 'administrator'],
['name' => 'admin'],
['name' => 'senior-officer'],
['name' => 'penilai'],
['name' => 'surveyor'],
['name' => 'pemohon-ao'],
['name' => 'pemohon-eo']
['name' => 'administrator']
];
}
}

View File

@@ -1,33 +1,42 @@
<?php
namespace Modules\Usermanagement\Database\Seeders;
namespace Modules\Usermanagement\Database\Seeders;
use Faker\Generator;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Modules\Usermanagement\Models\User;
use Spatie\Permission\Models\Role;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Database\Seeders\RolesSeeder;
class UsersSeeder extends Seeder
{
class UsersSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run(Generator $faker)
public function run(): void
{
$roles = Role::all();
$roleSeeder = new RolesSeeder();
$rolesData = $roleSeeder->data();
foreach ($roles as $role) {
$user = User::create([
'name' => $role->name,
'email' => $role->name . '@lpj.id',
'password' => Hash::make('lpj'),
foreach ($rolesData as $roleData) {
if ($roleData['name'] === 'administrator') {
$user = User::firstOrCreate(
['email' => $roleData['name'] . '@ag.co.id'],
[
'name' => $roleData['name'],
'password' => Hash::make('bagbag'),
'branch_id' => 1,
'nik' => '000000',
'email_verified_at' => now(),
]);
]
);
$role = \Spatie\Permission\Models\Role::firstOrCreate(
['name' => $roleData['name']],
['guard_name' => 'web']
);
$user->assignRole($role);
}
}
}
}

View File

@@ -8,7 +8,9 @@
"providers": [
"Modules\\Usermanagement\\Providers\\UsermanagementServiceProvider"
],
"files": [],
"files": [
"app/Helpers/RolePermission.php"
],
"menu": {
"main": [],
"master": [],
@@ -16,7 +18,7 @@
{
"title": "User Management",
"path": "users",
"icon": "ki-filled ki-users text-lg",
"icon": "ki-filled ki-users text-lg text-primary",
"classes": "",
"attributes": [],
"permission": "",
@@ -34,6 +36,16 @@
"administrator"
]
},
{
"title": "Positions",
"path": "users.positions",
"classes": "",
"attributes": [],
"permission": "",
"roles": [
"administrator"
]
},
{
"title": "Roles",
"path": "users.roles",

View File

@@ -7,8 +7,10 @@
@section('content')
<div class="container-fluid">
<div class="grid">
<div class="card card-grid min-w-full" data-datatable="false" data-datatable-page-size="5" data-datatable-state-save="true" id="permissions-table" data-api-url="{{ route('users.permissions.datatables') }}">
<div class="card-header py-5 flex-wrap">
<div class="min-w-full card card-grid" data-datatable="false" data-datatable-page-size="5"
data-datatable-state-save="true" id="permissions-table"
data-api-url="{{ route('users.permissions.datatables') }}">
<div class="flex-wrap py-5 card-header">
<h3 class="card-title">
List of Permissions
</h3>
@@ -19,42 +21,23 @@
</label>
</div>
<div class="flex flex-wrap gap-2.5">
<select class="select select-sm w-28">
<option value="1">
Active
</option>
<option value="2">
Disabled
</option>
<option value="2">
Pending
</option>
</select>
<select class="select select-sm w-28">
<option value="desc">
Latest
</option>
<option value="asc">
Oldest
</option>
</select>
<button class="btn btn-sm btn-outline btn-primary">
<i class="ki-filled ki-setting-4"> </i> <Filters></Filters>
</button>
<div class="flex flex-wrap gap-2.5 lg:gap-5">
<div class="h-[24px] border border-r-gray-200"> </div>
<a class="btn btn-sm btn-light" href="{{ route('users.permissions.export') }}"> Export to Excel </a>
<a class="btn btn-sm btn-primary" href="{{ route('users.permissions.create') }}"> Add Permission </a>
<a class="btn btn-sm btn-light" href="{{ route('users.permissions.export') }}"> Export to Excel
</a>
<a class="btn btn-sm btn-primary" href="{{ route('users.permissions.create') }}"> Add Permission
</a>
</div>
</div>
</div>
<div class="card-body">
<div class="scrollable-x-auto">
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border"
data-datatable-table="true">
<thead>
<tr>
<th class="w-14">
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" />
</th>
<th class="min-w-[250px]" data-datatable-column="name">
<span class="sort"> <span class="sort-label"> Permission </span>
@@ -69,12 +52,14 @@
</thead>
</table>
</div>
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
<div class="flex items-center gap-2">
<div
class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
<div class="flex gap-2 items-center">
Show
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"> </select> per page
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"> </select> per
page
</div>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<span data-datatable-info="true"> </span>
<div class="pagination" data-datatable-pagination="true">
</div>
@@ -92,7 +77,7 @@
function deleteData(data) {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!" ,
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
@@ -109,7 +94,7 @@
$.ajax(`permissions/${data}`, {
type: 'DELETE'
}).then((response) => {
swal.fire('Deleted!', 'User has been deleted.','success').then(() => {
swal.fire('Deleted!', 'User has been deleted.', 'success').then(() => {
window.location.reload();
});
}).catch((error) => {
@@ -124,7 +109,7 @@
const element = document.querySelector('#permissions-table');
const searchInput = document.getElementById('search');
const apiUrl = element.getAttribute('data-api-url');
const colors = ['','badge-primary', 'badge-success', 'badge-info', 'badge-danger', 'badge-warning', 'badge-dark'];
const colors = ['', 'badge-primary', 'badge-success', 'badge-info', 'badge-danger', 'badge-warning', 'badge-dark'];
const dataTableOptions = {
@@ -147,7 +132,7 @@
roles: {
title: 'Roles',
render: (item, data) => {
const _render = data.roles.map((role) =>{
const _render = data.roles.map((role) => {
const randomColor = colors[Math.floor(Math.random() * colors.length)];
return `<span class="badge ${randomColor} badge-sm">${role.name}</span>`;
});
@@ -173,10 +158,10 @@
let dataTable = new KTDataTable(element, dataTableOptions);
// Custom search functionality
searchInput.addEventListener('input', function () {
searchInput.addEventListener('input', function() {
const searchValue = this.value.trim();
dataTable.search(searchValue, true);
dataTable.goPage(1);
});
</script>
@endpush

View File

@@ -0,0 +1,68 @@
@extends('layouts.main')
@section('breadcrumbs')
{{ Breadcrumbs::render(request()->route()->getName()) }}
@endsection
@section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<form action="{{ isset($position->id) ? route('users.positions.update', $position->id) : route('users.positions.store') }}" method="POST" id="position_form">
@csrf
@if(isset($position->id))
<input type="hidden" name="id" value="{{ $position->id }}">
@method('PUT')
@endif
<div class="card pb-2.5">
<div class="card-header" id="basic_settings">
<h3 class="card-title">
{{ isset($position->id) ? 'Edit' : 'Add' }} Position
</h3>
<div class="flex items-center gap-2">
<a href="{{ route('users.positions.index') }}" class="btn btn-xs btn-info">Back</a>
</div>
</div>
<div class="card-body grid gap-5">
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Code
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('code') border-danger @enderror" type="text" name="code" value="{{ $position->code ?? '' }}">
@error('code')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Name
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('name') border-danger @enderror" type="text" name="name" value="{{ $position->name ?? '' }}">
@error('name')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Level
</label>
<div class="flex flex-wrap items-baseline w-full">
<input class="input @error('level') border-danger @enderror" type="number" name="level" value="{{ $position->level ?? '' }}">
@error('level')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex justify-end">
<button type="submit" class="btn btn-primary">
Save
</button>
</div>
</div>
</div>
</form>
</div>
@endsection

View File

@@ -0,0 +1,172 @@
@extends('layouts.main')
@section('breadcrumbs')
{{ Breadcrumbs::render('users.positions') }}
@endsection
@section('content')
<div class="container-fluid">
<div class="grid">
<div class="min-w-full card card-grid" data-datatable="false" data-datatable-page-size="5"
data-datatable-state-save="true" id="positions-table" data-api-url="{{ route('users.positions.datatables') }}">
<div class="flex-wrap py-5 card-header">
<h3 class="card-title">
List of Positions
</h3>
<div class="flex flex-wrap gap-2 lg:gap-5">
<div class="flex">
<label class="input input-sm"> <i class="ki-filled ki-magnifier"> </i>
<input placeholder="Search positions" id="search" type="text" value="">
</label>
</div>
<div class="flex flex-wrap gap-2.5 lg:gap-5">
<div class="h-[100%] border border-r-gray-200"> </div>
<a class="btn btn-sm btn-light" id="export-btn" href="{{ route('users.positions.export') }}">
Export to Excel </a>
<a class="btn btn-sm btn-primary" href="{{ route('users.positions.create') }}"> Add Position
</a>
</div>
</div>
</div>
<div class="card-body">
<div class="scrollable-x-auto">
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border"
data-datatable-table="true">
<thead>
<tr>
<th class="w-14">
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" />
</th>
<th class="min-w-[150px]" data-datatable-column="code">
<span class="sort"> <span class="sort-label"> Code </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[250px]" data-datatable-column="name">
<span class="sort"> <span class="sort-label"> Name </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[100px]" data-datatable-column="level">
<span class="sort"> <span class="sort-label"> Tingkat Jabatan </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr>
</thead>
</table>
</div>
<div
class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
<div class="flex gap-2 items-center">
Show
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"> </select> per
page
</div>
<div class="flex gap-4 items-center">
<span data-datatable-info="true"> </span>
<div class="pagination" data-datatable-pagination="true">
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script type="text/javascript">
function deleteData(data) {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
cancelButtonColor: '#d33',
confirmButtonText: 'Yes, delete it!'
}).then((result) => {
if (result.isConfirmed) {
$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
});
$.ajax(`positions/${data}`, {
type: 'DELETE'
}).then((response) => {
swal.fire('Deleted!', 'Position has been deleted.', 'success').then(() => {
window.location.reload();
});
}).catch((error) => {
console.error('Error:', error);
Swal.fire('Error!', 'An error occurred while deleting the position.', 'error');
});
}
})
}
</script>
<script type="module">
const element = document.querySelector('#positions-table');
const searchInput = document.getElementById('search');
const apiUrl = element.getAttribute('data-api-url');
const dataTableOptions = {
apiEndpoint: apiUrl,
pageSize: 5,
columns: {
select: {
render: (item, data, context) => {
const checkbox = document.createElement('input');
checkbox.className = 'checkbox checkbox-sm';
checkbox.type = 'checkbox';
checkbox.value = data.id.toString();
checkbox.setAttribute('data-datatable-row-check', 'true');
return checkbox.outerHTML.trim();
},
},
code: {
title: 'Code',
},
name: {
title: 'Name',
},
level: {
title: 'Level',
},
actions: {
title: 'Status',
render: (item, data) => {
return `<div class="flex flex-nowrap justify-center">
<a class="btn btn-sm btn-icon btn-clear btn-info" href="positions/${data.id}/edit">
<i class="ki-outline ki-notepad-edit"></i>
</a>
<a onclick="deleteData(${data.id})" class="delete btn btn-sm btn-icon btn-clear btn-danger">
<i class="ki-outline ki-trash"></i>
</a>
</div>`;
},
}
},
};
let dataTable = new KTDataTable(element, dataTableOptions);
const exportBtn = document.getElementById('export-btn');
const baseExportUrl = exportBtn.getAttribute('href');
// Custom search functionality
searchInput.addEventListener('input', function() {
const searchValue = this.value.trim();
dataTable.search(searchValue, true);
dataTable.goPage(1);
// Update export URL with search parameter
if (searchValue) {
exportBtn.setAttribute('href', `${baseExportUrl}?search=${encodeURIComponent(searchValue)}`);
} else {
exportBtn.setAttribute('href', baseExportUrl);
}
});
</script>
@endpush

View File

@@ -6,14 +6,12 @@
@section('content')
<div class="w-full grid gap-5 lg:gap-7.5 mx-auto">
<form action="{{ isset($role->id) ? route('users.roles.update', $role->id) : route('users.roles.store') }}" method="POST" id="role_form">
@csrf
@if(isset($role->id))
<form action="{{ route('users.roles.update', $role->id) }}" method="POST" id="role_form">
<input type="hidden" name="id" value="{{ $role->id }}">
@method('PUT')
@else
<form method="POST" action="{{ route('users.roles.store') }}">
@endif
@csrf
<div class="card pb-2.5">
<div class="card-header" id="basic_settings">
<h3 class="card-title">
@@ -35,6 +33,24 @@
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Position
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="select tomselect @error('position_id') border-danger @enderror" name="position_id">
<option value="">Select Position</option>
@foreach($positions as $position)
<option value="{{ $position->id }}" {{ (isset($role) && $role->position_id == $position->id) ? 'selected' : '' }}>
{{ $position->name }} | Tingkat Jabatan: {{ $position->level }}
</option>
@endforeach
</select>
@error('position_id')
<em class="alert text-danger text-sm">{{ $message }}</em>
@enderror
</div>
</div>
<div class="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
<label class="form-label max-w-56">
Administrator/Superuser Access

View File

@@ -7,8 +7,9 @@
@section('content')
<div class="container-fluid">
<div class="grid">
<div class="card card-grid min-w-full" data-datatable="false" data-datatable-page-size="5" data-datatable-state-save="true" id="roles-table" data-api-url="{{ route('users.roles.datatables') }}">
<div class="card-header py-5 flex-wrap">
<div class="min-w-full card card-grid" data-datatable="false" data-datatable-page-size="5"
data-datatable-state-save="true" id="roles-table" data-api-url="{{ route('users.roles.datatables') }}">
<div class="flex-wrap py-5 card-header">
<h3 class="card-title">
List of Roles
</h3>
@@ -19,29 +20,7 @@
</label>
</div>
<div class="flex flex-wrap gap-2.5">
<select class="select select-sm w-28">
<option value="1">
Active
</option>
<option value="2">
Disabled
</option>
<option value="2">
Pending
</option>
</select>
<select class="select select-sm w-28">
<option value="desc">
Latest
</option>
<option value="asc">
Oldest
</option>
</select>
<button class="btn btn-sm btn-outline btn-primary">
<i class="ki-filled ki-setting-4"> </i> <Filters></Filters>
</button>
<div class="flex flex-wrap gap-2.5 lg:gap-5">
<div class="h-[24px] border border-r-gray-200"> </div>
<a class="btn btn-sm btn-light" href="{{ route('users.roles.export') }}"> Export to Excel </a>
<a class="btn btn-sm btn-primary" href="{{ route('users.roles.create') }}"> Add Role </a>
@@ -50,27 +29,38 @@
</div>
<div class="card-body">
<div class="scrollable-x-auto">
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border"
data-datatable-table="true">
<thead>
<tr>
<th class="w-14">
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" />
</th>
<th class="min-w-[250px]" data-datatable-column="name">
<span class="sort"> <span class="sort-label"> Role </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[200px]" data-datatable-column="position_name">
<span class="sort"> <span class="sort-label"> Position </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[100px]" data-datatable-column="level">
<span class="sort"> <span class="sort-label"> Tingkat Jabatan </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr>
</thead>
</table>
</div>
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
<div class="flex items-center gap-2">
<div
class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
<div class="flex gap-2 items-center">
Show
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"> </select> per page
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"> </select> per
page
</div>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<span data-datatable-info="true"> </span>
<div class="pagination" data-datatable-pagination="true">
</div>
@@ -88,7 +78,7 @@
function deleteData(data) {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!" ,
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
@@ -105,7 +95,7 @@
$.ajax(`roles/${data}`, {
type: 'DELETE'
}).then((response) => {
swal.fire('Deleted!', 'User has been deleted.','success').then(() => {
swal.fire('Deleted!', 'User has been deleted.', 'success').then(() => {
window.location.reload();
});
}).catch((error) => {
@@ -138,6 +128,20 @@
name: {
title: 'Role',
},
position_name: {
title: 'Position',
render: (item, data) => {
return data.position ? data.position.name : '-';
},
sortable: true,
},
level: {
title: 'Level',
render: (item, data) => {
return data.position ? data.position.level : '-';
},
sortable: true,
},
actions: {
title: 'Status',
render: (item, data) => {
@@ -156,10 +160,10 @@
let dataTable = new KTDataTable(element, dataTableOptions);
// Custom search functionality
searchInput.addEventListener('input', function () {
searchInput.addEventListener('input', function() {
const searchValue = this.value.trim();
dataTable.search(searchValue, true);
dataTable.goPage(1);
});
</script>
@endpush

View File

@@ -169,7 +169,11 @@
</div>
</div>
<div class="switch switch-sm">
<input {{ in_array($role->name,Auth()->user()->roles->pluck('name')->toArray()) ? 'checked' : '' }} name="roles" type="radio" value="{{ $role->name }}">
@if(isset($user))
<input {{ in_array($role->name,$user->roles->pluck("name")->toArray()) ? 'checked' : '' }} name="roles" type="radio" value="{{ $role->name }}">
@else
<input name="roles" type="radio" value="{{ $role->name }}">
@endif
</div>
</div>
@endforeach

View File

@@ -7,8 +7,9 @@
@section('content')
<div class="container-fluid">
<div class="grid">
<div class="card card-grid min-w-full" data-datatable="false" data-datatable-page-size="5" data-datatable-state-save="true" id="users-table" data-api-url="{{ route('users.datatables') }}">
<div class="card-header py-5 flex-wrap">
<div class="min-w-full card card-grid" data-datatable="false" data-datatable-page-size="5"
data-datatable-state-save="true" id="users-table" data-api-url="{{ route('users.datatables') }}">
<div class="flex-wrap py-5 card-header">
<h3 class="card-title">
List of Users
</h3>
@@ -19,42 +20,22 @@
</label>
</div>
<div class="flex flex-wrap gap-2.5">
<select class="select select-sm w-28">
<option value="1">
Active
</option>
<option value="2">
Disabled
</option>
<option value="2">
Pending
</option>
</select>
<select class="select select-sm w-28">
<option value="desc">
Latest
</option>
<option value="asc">
Oldest
</option>
</select>
<button class="btn btn-sm btn-outline btn-primary">
<i class="ki-filled ki-setting-4"> </i> Filters
</button>
<div class="flex flex-wrap gap-2.5 lg:gap-5">
<div class="h-[24px] border border-r-gray-200"> </div>
<a class="btn btn-sm btn-light" href="{{ route('users.export') }}"> Export to Excel </a>
<a class="btn btn-sm btn-light" id="export-btn" href="{{ route('users.export') }}"> Export to
Excel </a>
<a class="btn btn-sm btn-primary" href="{{ route('users.create') }}"> Add User </a>
</div>
</div>
</div>
<div class="card-body">
<div class="scrollable-x-auto">
<table class="table table-auto table-border align-middle text-gray-700 font-medium text-sm" data-datatable-table="true">
<table class="table text-sm font-medium text-gray-700 align-middle table-auto table-border"
data-datatable-table="true">
<thead>
<tr>
<th class="w-14">
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox"/>
<input class="checkbox checkbox-sm" data-datatable-check="true" type="checkbox" />
</th>
<th class="min-w-[250px]" data-datatable-column="name">
<span class="sort"> <span class="sort-label"> Name </span>
@@ -72,17 +53,23 @@
<span class="sort"> <span class="sort-label"> Branch </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[185px]" data-datatable-column="role">
<span class="sort"> <span class="sort-label"> Role </span>
<span class="sort-icon"> </span> </span>
</th>
<th class="min-w-[50px] text-center" data-datatable-column="actions">Action</th>
</tr>
</thead>
</table>
</div>
<div class="card-footer justify-center md:justify-between flex-col md:flex-row gap-3 text-gray-600 text-2sm font-medium">
<div class="flex items-center gap-2">
<div
class="flex-col gap-3 justify-center font-medium text-gray-600 card-footer md:justify-between md:flex-row text-2sm">
<div class="flex gap-2 items-center">
Show
<select class="select select-sm w-16" data-datatable-size="true" name="perpage"> </select> per page
<select class="w-16 select select-sm" data-datatable-size="true" name="perpage"> </select> per
page
</div>
<div class="flex items-center gap-4">
<div class="flex gap-4 items-center">
<span data-datatable-info="true"> </span>
<div class="pagination" data-datatable-pagination="true">
</div>
@@ -100,7 +87,7 @@
function deleteData(data) {
Swal.fire({
title: 'Are you sure?',
text: "You won't be able to revert this!" ,
text: "You won't be able to revert this!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#3085d6',
@@ -117,7 +104,7 @@
$.ajax(`users/${data}`, {
type: 'DELETE'
}).then((response) => {
swal.fire('Deleted!', 'User has been deleted.','success').then(() => {
swal.fire('Deleted!', 'User has been deleted.', 'success').then(() => {
window.location.reload();
});
}).catch((error) => {
@@ -131,6 +118,20 @@
<script type="module">
const element = document.querySelector('#users-table');
const searchInput = document.getElementById('search');
const exportBtn = document.getElementById('export-btn');
// Update export URL with filters
function updateExportUrl() {
let url = new URL(exportBtn.href);
if (searchInput.value) {
url.searchParams.set('search', searchInput.value);
} else {
url.searchParams.delete('search');
}
exportBtn.href = url.toString();
};
const apiUrl = element.getAttribute('data-api-url');
const dataTableOptions = {
@@ -159,7 +160,14 @@
branch: {
title: 'Branch',
render: (item, data) => {
return data.branch.name;
return data.branch?.name || '-';
},
},
role: {
title: 'Role',
render: (item, data) => {
console.log(data);
return data.roles.map(role => role.name).join(', ');
},
},
actions: {
@@ -180,10 +188,11 @@
let dataTable = new KTDataTable(element, dataTableOptions);
// Custom search functionality
searchInput.addEventListener('input', function () {
searchInput.addEventListener('input', function() {
const searchValue = this.value.trim();
dataTable.search(searchValue, true);
updateExportUrl();
dataTable.goPage(1);
});
</script>
@endpush

View File

@@ -59,6 +59,101 @@
</div>
<!-- End of Container -->
</div>
<div class="container-fluid mt-8 w-full">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Edit Profile Form -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Edit Profile</h3>
</div>
<div class="card-body">
<form action="{{ route('users.update-profile') }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="mb-4">
<label for="name" class="form-label">Name</label>
<input type="text" class="input @error('name') border-danger @enderror" id="name" name="name" value="{{ Auth::user()->name }}">
@error('name')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<div class="mb-4">
<label for="email" class="form-label">Email</label>
<input type="email" class="input @error('email') border-danger @enderror" id="email" name="email" value="{{ Auth::user()->email }}">
@error('email')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<div class="mb-4">
<label for="nik" class="form-label">NIK</label>
<input type="text" class="input @error('nik') border-danger @enderror" id="nik" name="nik" value="{{ Auth::user()->nik }}">
@error('nik')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<div class="mb-4">
<label for="sign" class="form-label">E-Sign</label>
<input type="file" class="file-input @error('sign') border-danger @enderror" id="sign" name="sign" accept="image/*">
@if(Auth::user()->sign)
<div class="mt-2">
<p>Current E-Sign:</p>
<img src="{{ asset('storage/signatures/' . Auth::user()->id . '/' . Auth::user()->sign) }}"
alt="E-Sign"
class="mt-2 max-w-xs border border-gray-200 rounded">
</div>
@endif
@error('sign')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Update Profile</button>
</form>
</div>
</div>
<!-- Change Password Form -->
<div class="card">
<div class="card-header">
<h3 class="card-title">Change Password</h3>
</div>
<div class="card-body">
<form action="{{ route('users.change-password') }}" method="POST">
@csrf
@method('PUT')
<div class="mb-4">
<label for="current_password" class="form-label">Current Password</label>
<input type="password" class="input @error('current_password') border-danger @enderror" id="current_password" name="current_password">
@error('current_password')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<div class="mb-4">
<label for="password" class="form-label">New Password</label>
<input type="password" class="input @error('password') border-danger @enderror" id="password" name="password">
@error('password')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<div class="mb-4">
<label for="password_confirmation" class="form-label">Confirm New Password</label>
<input type="password" class="input @error('password_confirmation') border-danger @enderror" id="password_confirmation" name="password_confirmation">
@error('password_confirmation')
<div class="text-danger mt-2">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary">Change Password</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@@ -53,3 +53,18 @@
$trail->parent('users.permissions');
$trail->push('Edit Permission');
});
Breadcrumbs::for('users.positions', function (BreadcrumbTrail $trail) {
$trail->parent('users');
$trail->push('Positions', route('users.positions.index'));
});
Breadcrumbs::for('users.positions.create', function (BreadcrumbTrail $trail) {
$trail->parent('users.positions');
$trail->push('Add Position', route('users.positions.create'));
});
Breadcrumbs::for('users.positions.edit', function (BreadcrumbTrail $trail) {
$trail->parent('users.positions');
$trail->push('Edit Position');
});

View File

@@ -2,6 +2,7 @@
use Illuminate\Support\Facades\Route;
use Modules\Usermanagement\Http\Controllers\PermissionsController;
use Modules\Usermanagement\Http\Controllers\PositionsController;
use Modules\Usermanagement\Http\Controllers\RolesController;
use Modules\Usermanagement\Http\Controllers\UsersController;
@@ -15,29 +16,39 @@
| contains the "web" middleware group. Now create something great!
|
*/
Route::middleware(['auth'])->group(function () {
Route::middleware(['auth'])->group(function () {
Route::name('users.')->prefix('users')->group(function () {
Route::get('restore/{id}', [UsersController::class,'restore'])->name('restore');
Route::get('restore/{id}', [UsersController::class, 'restore'])->name('restore');
Route::get('datatables', [UsersController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [UsersController::class, 'export'])->name('export');
Route::get('profile', [UsersController::class, 'profile'])->name('profile');
Route::put('/profile/update', [UsersController::class, 'updateProfile'])->name('update-profile');
Route::put('/profile/change-password', [UsersController::class, 'changePassword'])->name(
'change-password',
);
});
Route::resource('users', UsersController::class);
Route::name('users.')->group(function () {
Route::name('roles.')->prefix('roles')->group(function () {
Route::get('restore/{id}', [RolesController::class,'restore'])->name('restore');
Route::get('restore/{id}', [RolesController::class, 'restore'])->name('restore');
Route::get('datatables', [RolesController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [RolesController ::class, 'export'])->name('export');
});
Route::resource('roles', RolesController::class);
Route::name('permissions.')->prefix('permissions')->group(function () {
Route::get('restore/{id}', [PermissionsController::class,'restore'])->name('restore');
Route::get('restore/{id}', [PermissionsController::class, 'restore'])->name('restore');
Route::get('datatables', [PermissionsController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [PermissionsController ::class, 'export'])->name('export');
});
Route::resource('permissions', PermissionsController::class);
});
});
Route::name('positions.')->prefix('positions')->group(function () {
Route::get('datatables', [PositionsController::class, 'dataForDatatables'])->name('datatables');
Route::get('export', [PositionsController::class, 'export'])->name('export');
});
Route::resource('positions', PositionsController::class);
});
});

View File

@@ -0,0 +1,474 @@
<?php
namespace Modules\Usermanagement\Tests\Feature;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\User;
use Illuminate\Support\Facades\Auth;
class PermissionsControllerTest extends TestCase
{
use RefreshDatabase, WithFaker;
protected $user;
protected $adminRole;
protected $permissionGroup;
/**
* Setup the test environment.
*/
protected function setUp(): void
{
parent::setUp();
// Create admin role
$this->adminRole = Role::create([
'name' => 'Admin',
'guard_name' => 'web'
]);
// Create a permission group for usermanagement permissions
$usermanagementGroup = PermissionGroup::create(['name' => 'Usermanagement']);
// Create permissions
$permissions = [
'usermanagement.create',
'usermanagement.read',
'usermanagement.update',
'usermanagement.delete',
'usermanagement.export',
'usermanagement.store',
'usermanagement.edit',
'usermanagement.view',
'usermanagement.restore'
];
foreach ($permissions as $permission) {
Permission::create([
'name' => $permission,
'guard_name' => 'web',
'permission_group_id' => $usermanagementGroup->id
]);
}
// Assign permissions to admin role
$this->adminRole->givePermissionTo($permissions);
// Create user with admin role
$this->user = User::create([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => bcrypt('password')
]);
$this->user->assignRole($this->adminRole);
// Create a test permission group
$this->permissionGroup = PermissionGroup::create(['name' => 'Test Group']);
}
/**
* Test user with permission can view permissions index.
*/
public function test_user_with_permission_can_view_permissions_index(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.index'));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.index');
}
/**
* Test user without permission cannot view permissions index.
*/
public function test_user_without_permission_cannot_view_permissions_index(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.index'));
$response->assertStatus(403);
}
/**
* Test user with permission can create permission.
*/
public function test_user_with_permission_can_create_permission(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.create'));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.create');
}
/**
* Test user without permission cannot create permission.
*/
public function test_user_without_permission_cannot_create_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user2@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.create'));
$response->assertStatus(403);
}
/**
* Test user with permission can store permission.
*/
public function test_user_with_permission_can_store_permission(): void
{
$this->actingAs($this->user);
$data = [
'name' => 'TestPermission'
];
$response = $this->post(route('users.permissions.store'), $data);
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', ['name' => 'TestPermission']);
// Check if all the required permissions were created
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.create']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.read']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.update']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.delete']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.export']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.authorize']);
$this->assertDatabaseHas('permissions', ['name' => 'testpermission.report']);
}
/**
* Test user without permission cannot store permission.
*/
public function test_user_without_permission_cannot_store_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user3@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$data = [
'name' => 'TestPermission2'
];
$response = $this->post(route('users.permissions.store'), $data);
$response->assertStatus(403);
$this->assertDatabaseMissing('permission_groups', ['name' => 'TestPermission2']);
}
/**
* Test user with permission can edit permission.
*/
public function test_user_with_permission_can_edit_permission(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.edit', $this->permissionGroup->id));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::permissions.create');
$response->assertViewHas('permission', $this->permissionGroup);
}
/**
* Test user without permission cannot edit permission.
*/
public function test_user_without_permission_cannot_edit_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user4@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.edit', $this->permissionGroup->id));
$response->assertStatus(403);
}
/**
* Test user with permission can update permission.
*/
public function test_user_with_permission_can_update_permission(): void
{
$this->actingAs($this->user);
// Create permissions for the test group
$permissions = [
'test group.create',
'test group.read',
'test group.update',
'test group.delete',
'test group.export',
'test group.authorize',
'test group.report'
];
foreach ($permissions as $permission) {
Permission::create([
'name' => $permission,
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
}
$data = [
'name' => 'Updated Group'
];
$response = $this->put(route('users.permissions.update', $this->permissionGroup->id), $data);
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', [
'id' => $this->permissionGroup->id,
'name' => 'Updated Group'
]);
// Check if all the permissions were updated
$this->assertDatabaseHas('permissions', ['name' => 'updated group.create']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.read']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.update']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.delete']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.export']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.authorize']);
$this->assertDatabaseHas('permissions', ['name' => 'updated group.report']);
}
/**
* Test user without permission cannot update permission.
*/
public function test_user_without_permission_cannot_update_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user5@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$data = [
'name' => 'Should Not Update'
];
$response = $this->put(route('users.permissions.update', $this->permissionGroup->id), $data);
$response->assertStatus(403);
$this->assertDatabaseMissing('permission_groups', [
'id' => $this->permissionGroup->id,
'name' => 'Should Not Update'
]);
}
/**
* Test user with permission can delete permission.
*/
public function test_user_with_permission_can_delete_permission(): void
{
$this->actingAs($this->user);
$response = $this->delete(route('users.permissions.destroy', $this->permissionGroup->id));
$response->assertJson([
'message' => 'Permission deleted successfully.',
'success' => true
]);
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
}
/**
* Test user without permission cannot delete permission.
*/
public function test_user_without_permission_cannot_delete_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user6@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->delete(route('users.permissions.destroy', $this->permissionGroup->id));
$response->assertStatus(403);
$this->assertDatabaseHas('permission_groups', ['id' => $this->permissionGroup->id, 'deleted_at' => null]);
}
/**
* Test user with permission can restore permission.
*/
public function test_user_with_permission_can_restore_permission(): void
{
$this->actingAs($this->user);
// First delete the permission group
$this->permissionGroup->delete();
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
$response = $this->get(route('users.permissions.restore', $this->permissionGroup->id));
$response->assertRedirect(route('users.permissions.index'));
$response->assertSessionHas('success');
$this->assertDatabaseHas('permission_groups', ['id' => $this->permissionGroup->id, 'deleted_at' => null]);
}
/**
* Test user without permission cannot restore permission.
*/
public function test_user_without_permission_cannot_restore_permission(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user7@example.com',
'password' => bcrypt('password')
]);
// First delete the permission group
$this->permissionGroup->delete();
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
$this->actingAs($user);
$response = $this->get(route('users.permissions.restore', $this->permissionGroup->id));
$response->assertStatus(403);
$this->assertSoftDeleted('permission_groups', ['id' => $this->permissionGroup->id]);
}
/**
* Test user with permission can access datatables data.
*/
public function test_user_with_permission_can_access_datatables_data(): void
{
$this->actingAs($this->user);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10');
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
}
/**
* Test user without permission cannot access datatables data.
*/
public function test_user_without_permission_cannot_access_datatables_data(): void
{
// Create user without permissions
$user = User::create([
'name' => 'Regular User',
'email' => 'user8@example.com',
'password' => bcrypt('password')
]);
$this->actingAs($user);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10');
$response->assertStatus(403);
}
/**
* Test datatables search filters permissions correctly.
*/
public function test_datatables_search_filters_permissions_correctly(): void
{
$this->actingAs($this->user);
// Create additional permission groups for testing search
PermissionGroup::create(['name' => 'SearchTest1']);
PermissionGroup::create(['name' => 'SearchTest2']);
PermissionGroup::create(['name' => 'DifferentName']);
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&search=SearchTest');
$response->assertStatus(200);
$response->assertJsonCount(2, 'data');
$response->assertJsonPath('data.0.name', 'SearchTest1');
$response->assertJsonPath('data.1.name', 'SearchTest2');
}
/**
* Test datatables sorting works correctly.
*/
public function test_datatables_sorting_works_correctly(): void
{
$this->actingAs($this->user);
// Create additional permission groups for testing sorting
PermissionGroup::create(['name' => 'A-Group']);
PermissionGroup::create(['name' => 'Z-Group']);
// Test ascending order
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&sortField=name&sortOrder=asc');
$response->assertStatus(200);
$response->assertJsonPath('data.0.name', 'A-Group');
// Test descending order
$response = $this->getJson(route('users.permissions.datatables') . '?page=1&size=10&sortField=name&sortOrder=desc');
$response->assertStatus(200);
$response->assertJsonPath('data.0.name', 'Z-Group');
}
/**
* Test export functionality.
*/
public function test_export_functionality(): void
{
$this->actingAs($this->user);
$response = $this->get(route('users.permissions.export'));
$response->assertStatus(200);
$response->assertHeader('content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
}
}

View File

@@ -0,0 +1,344 @@
<?php
namespace Modules\Usermanagement\Tests\Feature;
use Tests\TestCase;
use Modules\Usermanagement\Models\Position;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class PositionsControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected $position;
protected function setUp(): void
{
parent::setUp();
// Create permission group first
$permissionGroup = PermissionGroup::create([
'name' => 'usermanagement',
'slug' => 'usermanagement'
]);
// Create permissions with permission_group_id
Permission::create([
'name' => 'usermanagement.create',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.read',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.update',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.delete',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.export',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create admin role with all permissions
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo(Permission::all());
// Create a user with admin role
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
// Create a position for testing
$this->position = Position::create([
'code' => 'TEST',
'name' => 'Test Position',
'level' => 1
]);
}
#[Test]
public function user_with_permission_can_view_positions_index()
{
$response = $this->actingAs($this->user)
->get(route('users.positions.index'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_view_positions_index()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.positions.index'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_create_position()
{
$response = $this->actingAs($this->user)
->get(route('users.positions.create'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_create_position()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.positions.create'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_store_position()
{
$positionData = [
'code' => 'NEW',
'name' => 'New Position',
'level' => 2
];
$response = $this->actingAs($this->user)
->post(route('users.positions.store'), $positionData);
$response->assertRedirect(route('users.positions.index'));
$this->assertDatabaseHas('positions', $positionData);
}
#[Test]
public function user_without_permission_cannot_store_position()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$positionData = [
'code' => 'NEW',
'name' => 'New Position',
'level' => 2
];
$response = $this->actingAs($user)
->post(route('users.positions.store'), $positionData);
$response->assertStatus(403);
$this->assertDatabaseMissing('positions', $positionData);
}
#[Test]
public function user_with_permission_can_edit_position()
{
$response = $this->actingAs($this->user)
->get(route('users.positions.edit', $this->position->id));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_edit_position()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.positions.edit', $this->position->id));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_update_position()
{
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Position',
'level' => 3
];
$response = $this->actingAs($this->user)
->put(route('users.positions.update', $this->position->id), $updatedData);
$response->assertRedirect(route('users.positions.index'));
$this->assertDatabaseHas('positions', $updatedData);
}
#[Test]
public function user_without_permission_cannot_update_position()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$updatedData = [
'code' => 'UPD',
'name' => 'Updated Position',
'level' => 3
];
$response = $this->actingAs($user)
->put(route('users.positions.update', $this->position->id), $updatedData);
$response->assertStatus(403);
$this->assertDatabaseMissing('positions', $updatedData);
}
#[Test]
public function user_with_permission_can_delete_position()
{
$response = $this->actingAs($this->user)
->delete(route('users.positions.destroy', $this->position->id));
$response->assertRedirect(route('users.positions.index'));
$this->assertSoftDeleted($this->position);
}
#[Test]
public function user_without_permission_cannot_delete_position()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->delete(route('users.positions.destroy', $this->position->id));
$response->assertStatus(403);
$this->assertDatabaseHas('positions', ['id' => $this->position->id, 'deleted_at' => null]);
}
#[Test]
public function user_with_permission_can_access_datatables_data()
{
$response = $this->actingAs($this->user)
->get(route('users.positions.datatables'));
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
}
#[Test]
public function user_without_permission_cannot_access_datatables_data()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.positions.datatables'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_export_positions()
{
$response = $this->actingAs($this->user)
->get(route('users.positions.export'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_export_positions()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.positions.export'));
$response->assertStatus(403);
}
#[Test]
public function cannot_delete_position_if_it_has_associated_roles()
{
// Create a role associated with the position
$role = Role::create([
'name' => 'Position-Linked Role',
'guard_name' => 'web',
'position_id' => $this->position->id
]);
// Attempt to delete the position
$response = $this->actingAs($this->user)
->delete(route('users.positions.destroy', $this->position->id));
// Assert that the request is redirected back with an error message
$response->assertRedirect(route('users.positions.index'));
$response->assertSessionHas('error');
// Assert that the position still exists in the database (not deleted)
$this->assertDatabaseHas('positions', [
'id' => $this->position->id,
'deleted_at' => null
]);
}
}

View File

@@ -0,0 +1,467 @@
<?php
namespace Modules\Usermanagement\Tests\Feature;
use Tests\TestCase;
use Modules\Usermanagement\Models\Position;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
class RolesControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected $position;
protected $testRole;
protected $permissionGroup;
protected function setUp(): void
{
parent::setUp();
// Create permission group first
$this->permissionGroup = PermissionGroup::create([
'name' => 'usermanagement',
'slug' => 'usermanagement'
]);
// Create permissions with permission_group_id
Permission::create([
'name' => 'usermanagement.create',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.read',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.update',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.delete',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.export',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
Permission::create([
'name' => 'usermanagement.restore',
'guard_name' => 'web',
'permission_group_id' => $this->permissionGroup->id
]);
// Create admin role with all permissions
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo(Permission::all());
// Create a user with admin role
$this->user = User::factory()->create();
$this->user->assignRole($this->adminRole);
// Create a position for testing
$this->position = Position::create([
'code' => 'TEST',
'name' => 'Test Position',
'level' => 1
]);
// Create a test role for testing
$this->testRole = Role::create([
'name' => 'test-role',
'guard_name' => 'web',
'position_id' => $this->position->id
]);
$this->testRole->givePermissionTo('usermanagement.read');
}
#[Test]
public function user_with_permission_can_view_roles_index()
{
$response = $this->actingAs($this->user)
->get(route('users.roles.index'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_view_roles_index()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.roles.index'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_create_role()
{
$response = $this->actingAs($this->user)
->get(route('users.roles.create'));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_create_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.roles.create'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_store_role()
{
$permissions = Permission::where('name', 'usermanagement.read')->pluck('id')->toArray();
$roleData = [
'name' => 'New Role',
'guard_name' => 'web',
'position_id' => $this->position->id,
'permissions' => $permissions
];
$response = $this->actingAs($this->user)
->post(route('users.roles.store'), $roleData);
$response->assertRedirect(route('users.roles.index'));
$this->assertDatabaseHas('roles', [
'name' => 'New Role',
'position_id' => $this->position->id
]);
// Check if permission was assigned
$newRole = Role::where('name', 'New Role')->first();
$this->assertTrue($newRole->hasPermissionTo('usermanagement.read'));
}
#[Test]
public function user_without_permission_cannot_store_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$permissions = Permission::where('name', 'usermanagement.read')->pluck('id')->toArray();
$roleData = [
'name' => 'New Role',
'guard_name' => 'web',
'position_id' => $this->position->id,
'permissions' => $permissions
];
$response = $this->actingAs($user)
->post(route('users.roles.store'), $roleData);
$response->assertStatus(403);
$this->assertDatabaseMissing('roles', ['name' => 'New Role']);
}
#[Test]
public function user_with_permission_can_edit_role()
{
$response = $this->actingAs($this->user)
->get(route('users.roles.edit', $this->testRole->id));
$response->assertStatus(200);
}
#[Test]
public function user_without_permission_cannot_edit_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.roles.edit', $this->testRole->id));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_update_role()
{
$permissions = Permission::whereIn('name', ['usermanagement.read', 'usermanagement.update'])->pluck('id')->toArray();
$updatedData = [
'name' => 'Updated Role',
'guard_name' => 'web',
'position_id' => $this->position->id,
'permissions' => $permissions
];
$response = $this->actingAs($this->user)
->put(route('users.roles.update', $this->testRole->id), $updatedData);
$response->assertRedirect(route('users.roles.index'));
$this->assertDatabaseHas('roles', [
'id' => $this->testRole->id,
'name' => 'Updated Role',
'position_id' => $this->position->id
]);
// Check if permissions were updated
$updatedRole = Role::find($this->testRole->id);
$this->assertTrue($updatedRole->hasPermissionTo('usermanagement.read'));
$this->assertTrue($updatedRole->hasPermissionTo('usermanagement.update'));
}
#[Test]
public function user_without_permission_cannot_update_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$permissions = Permission::whereIn('name', ['usermanagement.read', 'usermanagement.update'])->pluck('id')->toArray();
$updatedData = [
'name' => 'Updated Role',
'guard_name' => 'web',
'position_id' => $this->position->id,
'permissions' => $permissions
];
$response = $this->actingAs($user)
->put(route('users.roles.update', $this->testRole->id), $updatedData);
$response->assertStatus(403);
$this->assertDatabaseMissing('roles', [
'id' => $this->testRole->id,
'name' => 'Updated Role'
]);
}
#[Test]
public function user_with_permission_can_delete_role()
{
$response = $this->actingAs($this->user)
->delete(route('users.roles.destroy', $this->testRole->id));
// The destroy method returns JSON response
$response->assertJson([
'message' => 'Role deleted successfully.',
'success' => true
]);
$this->assertSoftDeleted($this->testRole);
}
#[Test]
public function user_without_permission_cannot_delete_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->delete(route('users.roles.destroy', $this->testRole->id));
$response->assertStatus(403);
$this->assertDatabaseHas('roles', [
'id' => $this->testRole->id,
'deleted_at' => null
]);
}
#[Test]
public function user_with_permission_can_restore_role()
{
// First soft delete the role
$this->testRole->delete();
$this->assertSoftDeleted($this->testRole);
$response = $this->actingAs($this->user)
->get(route('users.roles.restore', $this->testRole->id));
$response->assertRedirect(route('users.roles.index'));
$this->assertDatabaseHas('roles', [
'id' => $this->testRole->id,
'deleted_at' => null
]);
}
#[Test]
public function user_without_permission_cannot_restore_role()
{
// Create a role with only read permission
$role = Role::create(['name' => 'reader', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.read');
// Create a user with the reader role
$user = User::factory()->create();
$user->assignRole($role);
// First soft delete the role
$this->testRole->delete();
$this->assertSoftDeleted($this->testRole);
$response = $this->actingAs($user)
->get(route('users.roles.restore', $this->testRole->id));
$response->assertStatus(403);
$this->assertSoftDeleted($this->testRole);
}
#[Test]
public function user_with_permission_can_access_datatables_data()
{
$response = $this->actingAs($this->user)
->get(route('users.roles.datatables'));
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
}
#[Test]
public function user_without_permission_cannot_access_datatables_data()
{
// Create a role without permissions
$role = Role::create(['name' => 'viewer', 'guard_name' => 'web']);
// Create a user with the viewer role
$user = User::factory()->create();
$user->assignRole($role);
$response = $this->actingAs($user)
->get(route('users.roles.datatables'));
$response->assertStatus(403);
}
#[Test]
public function user_with_permission_can_export_roles()
{
$response = $this->actingAs($this->user)
->get(route('users.roles.export'));
$response->assertStatus(200);
$response->assertHeader('content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
}
#[Test]
public function datatables_search_filters_roles_correctly()
{
// Create additional roles for testing search
Role::create([
'name' => 'searchable-role',
'guard_name' => 'web',
'position_id' => $this->position->id
]);
$response = $this->actingAs($this->user)
->get(route('users.roles.datatables', ['search' => 'searchable']));
$response->assertStatus(200);
$responseData = json_decode($response->getContent(), true);
// Check that the search returned the correct role
$this->assertGreaterThan(0, $responseData['recordsFiltered']);
$foundSearchableRole = false;
foreach ($responseData['data'] as $role) {
if ($role['name'] === 'searchable-role') {
$foundSearchableRole = true;
break;
}
}
$this->assertTrue($foundSearchableRole);
}
#[Test]
public function datatables_sorting_works_correctly()
{
// Create additional roles for testing sorting
Role::create([
'name' => 'A-role', // Should come first in ascending order
'guard_name' => 'web',
'position_id' => $this->position->id
]);
Role::create([
'name' => 'Z-role', // Should come last in ascending order
'guard_name' => 'web',
'position_id' => $this->position->id
]);
// Test ascending order
$response = $this->actingAs($this->user)
->get(route('users.roles.datatables', [
'sortField' => 'name',
'sortOrder' => 'asc'
]));
$response->assertStatus(200);
$responseData = json_decode($response->getContent(), true);
// Check that the first role is 'A-role'
$this->assertEquals('A-role', $responseData['data'][0]['name']);
// Test descending order
$response = $this->actingAs($this->user)
->get(route('users.roles.datatables', [
'sortField' => 'name',
'sortOrder' => 'desc'
]));
$response->assertStatus(200);
$responseData = json_decode($response->getContent(), true);
// Check that the first role is 'Z-role'
$this->assertEquals('Z-role', $responseData['data'][0]['name']);
}
}

View File

@@ -0,0 +1,466 @@
<?php
namespace Modules\Usermanagement\Tests\Feature;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;
use Modules\Usermanagement\Models\User;
use Modules\Usermanagement\Models\Role;
use Modules\Usermanagement\Models\Permission;
use Modules\Usermanagement\Models\PermissionGroup;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use PHPUnit\Framework\Attributes\Test;
class UsersControllerTest extends TestCase
{
use RefreshDatabase;
protected $user;
protected $adminRole;
protected function setUp(): void
{
parent::setUp();
// Create permission group
$permissionGroup = PermissionGroup::create([
'name' => 'usermanagement',
'slug' => 'usermanagement'
]);
// Create usermanagement.read permission
Permission::create([
'name' => 'usermanagement.read',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create role with usermanagement.read permission
$this->adminRole = Role::create(['name' => 'admin', 'guard_name' => 'web']);
$this->adminRole->givePermissionTo('usermanagement.read');
// Create a user with admin role
// Create a user for testing
$this->user = User::factory()->create([
'name' => 'Original Name',
'email' => 'original@example.com',
'nik' => '123456',
'sign' => 'old-signature.jpg'
]);
// Mock the storage
Storage::fake('public');
$this->user->assignRole($this->adminRole);
// Create test role for assignment to new user
Role::create(['name' => 'operator', 'guard_name' => 'web']);
}
#[Test]
public function should_display_users_index_page_when_user_has_users_view_permission()
{
$response = $this->actingAs($this->user)
->get(route('users.index'));
$response->assertStatus(200);
$response->assertViewIs('usermanagement::users.index');
}
#[Test]
public function should_return_json_response_with_correct_pagination_data_for_datatables()
{
// Create some test users
$testUsers = User::factory()->count(15)->create();
// Set up the request parameters
$requestData = [
'draw' => 1,
'page' => 1, // Changed from 2 to 1 to match the controller logic
'size' => 5,
'search' => '',
'sortField' => 'name',
'sortOrder' => 'asc'
];
// Make the request
$response = $this->actingAs($this->user)
->getJson(route('users.datatables') . '?' . http_build_query($requestData));
// Assert response status and structure
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
// Get total count of users for verification (16 = 15 created + 1 from setup)
$totalUsers = User::count();
// Verify the pagination data
$responseData = $response->json();
$this->assertEquals(1, $responseData['draw']);
$this->assertEquals($totalUsers, $responseData['recordsTotal']);
$this->assertEquals($totalUsers, $responseData['recordsFiltered']);
$this->assertEquals(ceil($totalUsers / $requestData['size']), $responseData['pageCount']);
$this->assertEquals($requestData['page'], $responseData['page']);
// Verify that we have the correct number of users in the response
$this->assertCount(5, $responseData['data']);
// Verify that the data is ordered correctly - get first page of sorted data
$this->assertEquals(
User::orderBy('name', 'asc')->take(5)->pluck('id')->toArray(),
collect($responseData['data'])->pluck('id')->toArray()
);
}
#[Test]
public function should_filter_users_by_search_term_when_search_parameter_is_provided()
{
// Create test users with specific names for testing search
$matchingUser1 = User::factory()->create(['name' => 'Test User One']);
$matchingUser2 = User::factory()->create(['email' => 'test@example.com']);
$nonMatchingUser = User::factory()->create(['name' => 'Different User', 'email' => 'different@example.com']);
// Set up the request parameters with search term
$requestData = [
'draw' => 1,
'page' => 1,
'size' => 10,
'search' => 'test',
'sortField' => 'name',
'sortOrder' => 'asc'
];
// Make the request
$response = $this->actingAs($this->user)
->getJson(route('users.datatables') . '?' . http_build_query($requestData));
// Assert response status and structure
$response->assertStatus(200);
$response->assertJsonStructure([
'draw',
'recordsTotal',
'recordsFiltered',
'pageCount',
'page',
'totalCount',
'data'
]);
// Get the response data
$responseData = $response->json();
// Verify that only matching users are returned
$this->assertEquals(2, $responseData['recordsFiltered']);
// Extract user IDs from the response
$returnedUserIds = collect($responseData['data'])->pluck('id')->toArray();
// Verify the correct users are returned
$this->assertContains($matchingUser1->id, $returnedUserIds);
$this->assertContains($matchingUser2->id, $returnedUserIds);
$this->assertNotContains($nonMatchingUser->id, $returnedUserIds);
}
#[Test]
public function should_correctly_sort_users_when_sortField_and_sortOrder_parameters_are_specified()
{
// Create test users with varying names to test different sort orders
$userA = User::factory()->create(['name' => 'Adam Smith']);
$userB = User::factory()->create(['name' => 'Brian Jones']);
$userC = User::factory()->create(['name' => 'Charlie Brown']);
// Test ascending order
$requestDataAsc = [
'draw' => 1,
'page' => 1,
'size' => 10,
'search' => '',
'sortField' => 'name',
'sortOrder' => 'asc'
];
$responseAsc = $this->actingAs($this->user)
->getJson(route('users.datatables') . '?' . http_build_query($requestDataAsc));
$responseAsc->assertStatus(200);
$responseDataAsc = $responseAsc->json();
// Check if sorted ascending by name
$userIdsAsc = collect($responseDataAsc['data'])->pluck('id')->toArray();
$expectedOrderAsc = User::orderBy('name', 'asc')->pluck('id')->toArray();
$this->assertEquals($expectedOrderAsc, $userIdsAsc);
// Test descending order
$requestDataDesc = [
'draw' => 1,
'page' => 1,
'size' => 10,
'search' => '',
'sortField' => 'name',
'sortOrder' => 'desc'
];
$responseDesc = $this->actingAs($this->user)
->getJson(route('users.datatables') . '?' . http_build_query($requestDataDesc));
$responseDesc->assertStatus(200);
$responseDataDesc = $responseDesc->json();
// Check if sorted descending by name
$userIdsDesc = collect($responseDataDesc['data'])->pluck('id')->toArray();
$expectedOrderDesc = User::orderBy('name', 'desc')->pluck('id')->toArray();
$this->assertEquals($expectedOrderDesc, $userIdsDesc);
// Test sorting by a different field (email)
$requestDataEmail = [
'draw' => 1,
'page' => 1,
'size' => 10,
'search' => '',
'sortField' => 'email',
'sortOrder' => 'asc'
];
$responseEmail = $this->actingAs($this->user)
->getJson(route('users.datatables') . '?' . http_build_query($requestDataEmail));
$responseEmail->assertStatus(200);
$responseDataEmail = $responseEmail->json();
// Check if sorted by email
$userIdsEmail = collect($responseDataEmail['data'])->pluck('id')->toArray();
$expectedOrderEmail = User::orderBy('email', 'asc')->pluck('id')->toArray();
$this->assertEquals($expectedOrderEmail, $userIdsEmail);
}
#[Test]
public function should_successfully_create_a_new_user_and_assign_roles_when_valid_data_is_submitted()
{
// Prepare valid user data
$userData = [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password123',
'password_confirmation' => 'password123',
'nik' => '789234',
'roles' => ['operator']
];
// Submit the request to create a new user
$response = $this->actingAs($this->user)
->post(route('users.store'), $userData);
// Assert redirect to users index page with success message
$response->assertRedirect(route('users.index'));
$response->assertSessionHas('success', 'User created successfully.');
// Assert the user was created in the database
$this->assertDatabaseHas('users', [
'name' => 'Test User',
'email' => 'test@example.com',
'nik' => '789234'
]);
// Assert the user was assigned the correct role
$newUser = User::where('email', 'test@example.com')->first();
$this->assertTrue($newUser->hasRole('operator'));
}
#[Test]
public function should_successfully_update_existing_user_information_and_role_assignments()
{
// Create a test user with admin role
$userToUpdate = User::factory()->create([
'name' => 'Original Name',
'email' => 'originalee@example.com',
'nik' => '987654'
]);
$userToUpdate->assignRole($this->adminRole);
// Create an additional role for the update test
$newRole = Role::create(['name' => 'editor', 'guard_name' => 'web']);
// Prepare update data
$updateData = [
'name' => 'Updated Name',
'email' => 'updated@example.com',
'nik' => '654321',
'roles' => ['operator'] // Change role from admin to operator
];
// Make the request to update the user
$response = $this->actingAs($this->user)
->put(route('users.update', $userToUpdate->id), $updateData);
// Assert redirect to users index page with success message
$response->assertRedirect(route('users.index'));
$response->assertSessionHas('success', 'User updated successfully.');
// Assert the user was updated in the database
$this->assertDatabaseHas('users', [
'id' => $userToUpdate->id,
'name' => 'Updated Name',
'email' => 'updated@example.com',
'nik' => '654321'
]);
// Refresh the user model from database
$userToUpdate->refresh();
// Assert the user has the new role and doesn't have the old role
$this->assertTrue($userToUpdate->hasRole('operator'));
$this->assertFalse($userToUpdate->hasRole('admin'));
}
#[Test]
public function should_delete_a_user_when_the_authenticated_user_has_users_delete_permission()
{
// Create the permission for delete users
$permissionGroup = PermissionGroup::create([
'name' => 'usermanagement',
'slug' => 'usermanagement'
]);
// Create delete permission
Permission::create([
'name' => 'usermanagement.delete',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create role with delete permission
$role = Role::create(['name' => 'manager', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.delete');
// Create an admin user with the role that has delete permission
$adminUser = User::factory()->create();
$adminUser->assignRole($role);
// Create a user to be deleted
$userToDelete = User::factory()->create();
// Make the request to delete the user
$response = $this->actingAs($adminUser)
->delete(route('users.destroy', $userToDelete->id));
// Assert the response is correct
$decodedResponse = json_decode($response->getContent(), true);
$this->assertEquals('User deleted successfully.', $decodedResponse['message']);
$this->assertTrue($decodedResponse['success']);
// Assert the user was soft deleted
$this->assertSoftDeleted('users', ['id' => $userToDelete->id]);
}
#[Test]
public function should_restore_a_soft_deleted_user_when_the_authenticated_user_has_users_restore_permission()
{
// Create permission group
$permissionGroup = PermissionGroup::create([
'name' => 'usermanagement',
'slug' => 'usermanagement'
]);
// Create restore permission
Permission::create([
'name' => 'usermanagement.restore',
'guard_name' => 'web',
'permission_group_id' => $permissionGroup->id
]);
// Create role with restore permission
$role = Role::create(['name' => 'restorer', 'guard_name' => 'web']);
$role->givePermissionTo('usermanagement.restore');
// Create an admin user with the role that has restore permission
$adminUser = User::factory()->create();
$adminUser->assignRole($role);
// Create a user to be restored
$userToRestore = User::factory()->create();
$userToRestore->delete(); // Soft delete the user
// Verify the user is soft-deleted
$this->assertSoftDeleted('users', ['id' => $userToRestore->id]);
// Make the request to restore the user
$response = $this->actingAs($adminUser)
->get(route('users.restore', $userToRestore->id));
// Assert the response redirects to users.index with success message
$response->assertRedirect(route('users.index'));
$response->assertSessionHas('success', 'User restored successfully.');
// Assert the user was restored
$this->assertDatabaseHas('users', [
'id' => $userToRestore->id,
'deleted_at' => null
]);
}
#[Test]
public function should_update_users_profile_including_signature_image_when_valid_data_is_submitted()
{
// Create a fake signature file
$file = UploadedFile::fake()->image('new-signature.jpg');
// Create a fake old signature file in storage
Storage::disk('public')->put(
'signatures/' . $this->user->id . '/old-signature.jpg',
'fake content'
);
// Prepare valid profile data with new signature
$profileData = [
'name' => 'Updated Name',
'email' => 'updated@example.com',
'nik' => '654321',
'sign' => $file
];
// Make the request to update the profile
$response = $this->actingAs($this->user)
->put(route('users.update-profile'), $profileData);
// Assert redirect to profile page with success message
$response->assertRedirect(route('users.profile'));
$response->assertSessionHas('success', 'Profile updated successfully.');
// Assert the user was updated in the database
$this->assertDatabaseHas('users', [
'id' => $this->user->id,
'name' => 'Updated Name',
'email' => 'updated@example.com',
'nik' => '654321',
]);
// Refresh the user model from database
$this->user->refresh();
// Assert that the user has a sign value (any non-empty string)
$this->assertNotEmpty($this->user->sign);
// Debug information
$files = Storage::disk('public')->allFiles('signatures/' . $this->user->id);
// Assert the file has been stored in the expected location
// Use a more flexible check that doesn't rely on the exact filename
$signaturePath = 'signatures/' . $this->user->id;
$this->assertTrue(
Storage::disk('public')->exists($signaturePath . '/' . $this->user->sign),
"Signature file not found at expected location: {$signaturePath}/{$this->user->sign}"
);
// Verify old signature was deleted
Storage::disk('public')->assertMissing('signatures/' . $this->user->id . '/old-signature.jpg');
}
}