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.
This commit is contained in:
Daeng Deni Mardaeni
2025-05-17 15:07:09 +07:00
parent e9fa45a808
commit a6c79c72b5
4 changed files with 58 additions and 35 deletions

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

@@ -286,7 +286,11 @@
if ($request->has('search') && !empty($request->get('search'))) {
$search = $request->get('search');
$query->where(function ($q) use ($search) {
$q->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%']);
$q->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereHas('position', function($query) use ($search) {
$query->whereRaw('LOWER(name) LIKE ?', ['%' . strtolower($search) . '%'])
->orWhereRaw('CAST(level AS TEXT) LIKE ?', ['%' . $search . '%']);
});
});
}
@@ -294,11 +298,26 @@
if ($request->has('sortOrder') && !empty($request->get('sortOrder'))) {
$order = $request->get('sortOrder');
$column = $request->get('sortField');
$query->orderBy($column, $order);
// Handle sorting for position-related columns
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 {
$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')) {
@@ -309,11 +328,11 @@
$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();
$roles = $query->with('position')->get();
// Calculate the page count
$pageCount = ceil($totalRecords/$request->get('size'));

View File

@@ -38,11 +38,11 @@
Position
</label>
<div class="flex flex-wrap items-baseline w-full">
<select class="select @error('position_id') border-danger @enderror" name="position_id">
<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 }} (Level: {{ $position->level }})
{{ $position->name }} | Tingkat Jabatan: {{ $position->level }}
</option>
@endforeach
</select>

View File

@@ -19,29 +19,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>
@@ -60,6 +38,14 @@
<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>
@@ -138,6 +124,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) => {
@@ -162,4 +162,3 @@
});
</script>
@endpush