feat: add export functionality for lecturers and students, including styled heading rows in exports
Some checks failed
tests / ci (pull_request) Has been cancelled

This commit is contained in:
Yoga Pangestu 2026-08-30 10:56:23 +07:00
parent c321c99ac1
commit c77f6ae603
12 changed files with 672 additions and 21 deletions

View File

@ -0,0 +1,49 @@
<?php
namespace App\Exports\Concerns;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
trait StyledHeadingRow
{
abstract public function title(): string;
public function styles(Worksheet $sheet): array
{
return [
1 => [
'font' => ['bold' => true, 'color' => ['rgb' => '733E0A']],
'fill' => [
'fillType' => Fill::FILL_SOLID,
'startColor' => ['rgb' => 'FDC700'],
],
'alignment' => [
'horizontal' => Alignment::HORIZONTAL_LEFT,
'vertical' => Alignment::VERTICAL_CENTER,
],
],
];
}
public function registerEvents(): array
{
return [
AfterSheet::class => function (AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$lastColumn = $sheet->getHighestColumn();
$sheet->insertNewRowBefore(1, 1);
$sheet->mergeCells("A1:{$lastColumn}1");
$sheet->setCellValue('A1', $this->title().' '.config('app.name'));
$sheet->getRowDimension(1)->setRowHeight(24);
$sheet->getStyle('A1')->getFont()->setBold(true)->setSize(14);
$sheet->getStyle('A1')->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER);
$sheet->getStyle('A1')->getAlignment()->setVertical(Alignment::VERTICAL_CENTER);
},
];
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Exports;
use App\Exports\Concerns\StyledHeadingRow;
use App\Models\User;
use App\Services\Admin\Users\LecturerService;
use Illuminate\Support\Enumerable;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
class LecturersExport implements FromCollection, ShouldAutoSize, WithEvents, WithHeadings, WithMapping, WithStyles
{
use StyledHeadingRow;
public function __construct(
private readonly string $search = '',
private readonly ?string $gender = null,
private readonly ?int $departmentId = null,
) {}
public function title(): string
{
return 'Data Dosen';
}
public function collection(): Enumerable
{
return app(LecturerService::class)->forExport($this->search, $this->gender, $this->departmentId);
}
public function headings(): array
{
return [
'NIDN',
'Nama Lengkap',
'Username',
'Email',
'Nomor Telepon',
'Jurusan',
'Jenis Kelamin',
'Tempat Lahir',
'Tanggal Lahir',
'Alamat',
];
}
public function map($user): array
{
/** @var User $user */
return [
$user->lecturer?->lecturer_number ?? '-',
$user->profile?->full_name ?? '-',
$user->username,
$user->email,
$user->profile?->phone_number ?? '-',
$user->lecturer?->department?->name ?? '-',
$user->profile?->gender?->label() ?? '-',
$user->profile?->birth_place ?? '-',
$user->profile?->birth_date?->format('d M Y') ?? '-',
$user->profile?->address ?? '-',
];
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Exports;
use App\Exports\Concerns\StyledHeadingRow;
use App\Models\User;
use App\Services\Admin\Users\StudentService;
use Illuminate\Support\Enumerable;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithMapping;
use Maatwebsite\Excel\Concerns\WithStyles;
class StudentsExport implements FromCollection, ShouldAutoSize, WithEvents, WithHeadings, WithMapping, WithStyles
{
use StyledHeadingRow;
public function __construct(
private readonly string $search = '',
private readonly ?string $gender = null,
private readonly ?int $departmentId = null,
private readonly ?string $status = null,
) {}
public function title(): string
{
return 'Data Mahasiswa';
}
public function collection(): Enumerable
{
return app(StudentService::class)->forExport($this->search, $this->gender, $this->departmentId, $this->status);
}
public function headings(): array
{
return [
'NIM',
'Nama Lengkap',
'Username',
'Email',
'Nomor Telepon',
'Jurusan',
'Angkatan',
'Status',
'Jenis Kelamin',
'Tempat Lahir',
'Tanggal Lahir',
'Alamat',
];
}
public function map($user): array
{
/** @var User $user */
return [
$user->student?->student_number ?? '-',
$user->profile?->full_name ?? '-',
$user->username,
$user->email,
$user->profile?->phone_number ?? '-',
$user->student?->department?->name ?? '-',
$user->student?->enrollment_year ?? '-',
$user->student?->status?->label() ?? '-',
$user->profile?->gender?->label() ?? '-',
$user->profile?->birth_place ?? '-',
$user->profile?->birth_date?->format('d M Y') ?? '-',
$user->profile?->address ?? '-',
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin\Users; namespace App\Http\Controllers\Admin\Users;
use App\Exports\LecturersExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Users\LecturerRequest; use App\Http\Requests\Admin\Users\LecturerRequest;
use App\Http\Requests\PaginatedRequest; use App\Http\Requests\PaginatedRequest;
@ -11,6 +12,8 @@
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class LecturerController extends Controller class LecturerController extends Controller
{ {
@ -80,4 +83,13 @@ public function resetPassword(User $user): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back(); return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back();
} }
public function export(PaginatedRequest $request): BinaryFileResponse
{
return Excel::download(new LecturersExport(
search: $request->validated('search') ?? '',
gender: $request->validated('gender'),
departmentId: $request->validated('department_id'),
), 'dosen.xlsx');
}
} }

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers\Admin\Users; namespace App\Http\Controllers\Admin\Users;
use App\Enums\StudentStatus; use App\Enums\StudentStatus;
use App\Exports\StudentsExport;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Users\StudentRequest; use App\Http\Requests\Admin\Users\StudentRequest;
use App\Http\Requests\Admin\Users\StudentStatusRequest; use App\Http\Requests\Admin\Users\StudentStatusRequest;
@ -14,6 +15,8 @@
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Inertia\Inertia; use Inertia\Inertia;
use Inertia\Response; use Inertia\Response;
use Maatwebsite\Excel\Facades\Excel;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class StudentController extends Controller class StudentController extends Controller
{ {
@ -95,4 +98,14 @@ public function updateStatus(StudentStatusRequest $request, User $user): Redirec
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status mahasiswa berhasil diperbarui.'])->back(); return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status mahasiswa berhasil diperbarui.'])->back();
} }
public function export(PaginatedRequest $request): BinaryFileResponse
{
return Excel::download(new StudentsExport(
search: $request->validated('search') ?? '',
gender: $request->validated('gender'),
departmentId: $request->validated('department_id'),
status: $request->validated('status'),
), 'mahasiswa.xlsx');
}
} }

View File

@ -5,6 +5,7 @@
use App\Models\Lecturer; use App\Models\Lecturer;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
@ -17,6 +18,20 @@ public function getAllForSelect(): Collection
} }
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null, ?int $departmentId = null): LengthAwarePaginator public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null, ?int $departmentId = null): LengthAwarePaginator
{
return $this->filteredQuery($search, $gender, $departmentId)
->orderBy($sort, $direction)
->paginate($perPage);
}
public function forExport(string $search = '', ?string $gender = null, ?int $departmentId = null): Collection
{
return $this->filteredQuery($search, $gender, $departmentId)
->orderBy('created_at', 'desc')
->get();
}
private function filteredQuery(string $search, ?string $gender, ?int $departmentId): Builder
{ {
return User::with(['profile', 'lecturer.department']) return User::with(['profile', 'lecturer.department'])
->whereHas('roles', fn ($q) => $q->where('name', 'dosen')) ->whereHas('roles', fn ($q) => $q->where('name', 'dosen'))
@ -27,9 +42,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
->orWhereHas('lecturer', fn ($q) => $q->where('lecturer_number', 'like', "%{$search}%")); ->orWhereHas('lecturer', fn ($q) => $q->where('lecturer_number', 'like', "%{$search}%"));
})) }))
->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender))) ->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender)))
->when($departmentId, fn ($q) => $q->whereHas('lecturer', fn ($q) => $q->where('department_id', $departmentId))) ->when($departmentId, fn ($q) => $q->whereHas('lecturer', fn ($q) => $q->where('department_id', $departmentId)));
->orderBy($sort, $direction)
->paginate($perPage);
} }
public function create(array $data): User public function create(array $data): User

View File

@ -5,6 +5,7 @@
use App\Models\Student; use App\Models\Student;
use App\Models\User; use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
@ -17,6 +18,20 @@ public function getAllForSelect(): Collection
} }
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null, ?int $departmentId = null, ?string $status = null): LengthAwarePaginator public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null, ?int $departmentId = null, ?string $status = null): LengthAwarePaginator
{
return $this->filteredQuery($search, $gender, $departmentId, $status)
->orderBy($sort, $direction)
->paginate($perPage);
}
public function forExport(string $search = '', ?string $gender = null, ?int $departmentId = null, ?string $status = null): Collection
{
return $this->filteredQuery($search, $gender, $departmentId, $status)
->orderBy('created_at', 'desc')
->get();
}
private function filteredQuery(string $search, ?string $gender, ?int $departmentId, ?string $status): Builder
{ {
return User::with(['profile', 'student.department']) return User::with(['profile', 'student.department'])
->whereHas('roles', fn ($q) => $q->where('name', 'mahasiswa')) ->whereHas('roles', fn ($q) => $q->where('name', 'mahasiswa'))
@ -28,9 +43,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
})) }))
->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender))) ->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender)))
->when($departmentId, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('department_id', $departmentId))) ->when($departmentId, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('department_id', $departmentId)))
->when($status, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('status', $status))) ->when($status, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('status', $status)));
->orderBy($sort, $direction)
->paginate($perPage);
} }
public function create(array $data): User public function create(array $data): User

View File

@ -16,6 +16,7 @@
"laravel/framework": "^13.17", "laravel/framework": "^13.17",
"laravel/tinker": "^3.0", "laravel/tinker": "^3.0",
"laravel/wayfinder": "^0.1.14", "laravel/wayfinder": "^0.1.14",
"maatwebsite/excel": "^4.0",
"spatie/laravel-medialibrary": "^11.23", "spatie/laravel-medialibrary": "^11.23",
"spatie/laravel-permission": "^8.3", "spatie/laravel-permission": "^8.3",
"symfony/html-sanitizer": "^8.1" "symfony/html-sanitizer": "^8.1"

383
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "3fabe2a835a47ecc56faf22b029cda13", "content-hash": "379c97a9e3b22c91d1427f44e711a975",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -189,6 +189,82 @@
], ],
"time": "2024-02-09T16:56:22+00:00" "time": "2024-02-09T16:56:22+00:00"
}, },
{
"name": "composer/pcre",
"version": "3.4.0",
"source": {
"type": "git",
"url": "https://github.com/composer/pcre.git",
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"phpstan/phpstan": "<2.2.2"
},
"require-dev": {
"phpstan/phpstan": "^2",
"phpstan/phpstan-deprecation-rules": "^2",
"phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^9"
},
"type": "library",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
"psr-4": {
"Composer\\Pcre\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "PCRE wrapping library that offers type-safe preg_* replacements.",
"keywords": [
"PCRE",
"preg",
"regex",
"regular expression"
],
"support": {
"issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.4.0"
},
"funding": [
{
"url": "https://packagist.com",
"type": "custom"
},
{
"url": "https://github.com/composer",
"type": "github"
}
],
"time": "2026-06-07T11:47:49+00:00"
},
{ {
"name": "composer/semver", "name": "composer/semver",
"version": "3.4.4", "version": "3.4.4",
@ -2581,6 +2657,95 @@
], ],
"time": "2026-03-08T20:05:35+00:00" "time": "2026-03-08T20:05:35+00:00"
}, },
{
"name": "maatwebsite/excel",
"version": "4.0.2",
"source": {
"type": "git",
"url": "https://github.com/SpartnerNL/Laravel-Excel.git",
"reference": "3f3e73956f87e3d229b244cec8cbc3bbda4fc8dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/3f3e73956f87e3d229b244cec8cbc3bbda4fc8dd",
"reference": "3f3e73956f87e3d229b244cec8cbc3bbda4fc8dd",
"shasum": ""
},
"require": {
"composer/semver": "^3.4",
"illuminate/support": "^12.0 || ^13.0",
"php": "^8.3",
"phpoffice/phpspreadsheet": "^5.8",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"brianium/paratest": "^7.20",
"driftingly/rector-laravel": "^2.5",
"ext-sqlite3": "*",
"larastan/larastan": "^3.10",
"laravel/pint": "^1.29",
"laravel/scout": "^10.25 || ^11.2",
"orchestra/testbench": "^10.11 || ^11.1",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan-mockery": "^2.0",
"phpunit/phpunit": "^12.5 || ~13.1.14",
"predis/predis": "^2.3 || ^3.0",
"rector/rector": "^2.4.2"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Excel": "Maatwebsite\\Excel\\Facades\\Excel"
},
"providers": [
"Maatwebsite\\Excel\\ExcelServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Maatwebsite\\Excel\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Patrick Brouwers",
"email": "patrick@spartner.nl"
}
],
"description": "Supercharged Excel exports and imports in Laravel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel",
"php",
"phpspreadsheet"
],
"support": {
"issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
"source": "https://github.com/SpartnerNL/Laravel-Excel/tree/4.0.2"
},
"funding": [
{
"url": "https://laravel-excel.com/commercial-support",
"type": "custom"
},
{
"url": "https://github.com/patrickbrouwers",
"type": "github"
}
],
"time": "2026-08-24T13:06:48+00:00"
},
{ {
"name": "maennchen/zipstream-php", "name": "maennchen/zipstream-php",
"version": "3.2.2", "version": "3.2.2",
@ -2659,6 +2824,113 @@
], ],
"time": "2026-04-11T18:38:28+00:00" "time": "2026-04-11T18:38:28+00:00"
}, },
{
"name": "markbaker/complex",
"version": "3.0.2",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPComplex.git",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Complex\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@lange.demon.co.uk"
}
],
"description": "PHP Class for working with complex numbers",
"homepage": "https://github.com/MarkBaker/PHPComplex",
"keywords": [
"complex",
"mathematics"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
},
"time": "2022-12-06T16:21:08+00:00"
},
{
"name": "markbaker/matrix",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/MarkBaker/PHPMatrix.git",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
"phpcompatibility/php-compatibility": "^9.3",
"phpdocumentor/phpdocumentor": "2.*",
"phploc/phploc": "^4.0",
"phpmd/phpmd": "2.*",
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
"sebastian/phpcpd": "^4.0",
"squizlabs/php_codesniffer": "^3.7"
},
"type": "library",
"autoload": {
"psr-4": {
"Matrix\\": "classes/src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mark Baker",
"email": "mark@demon-angel.eu"
}
],
"description": "PHP Class for working with matrices",
"homepage": "https://github.com/MarkBaker/PHPMatrix",
"keywords": [
"mathematics",
"matrix",
"vector"
],
"support": {
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
},
"time": "2022-12-02T22:17:43+00:00"
},
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"version": "3.10.0", "version": "3.10.0",
@ -3414,6 +3686,115 @@
}, },
"time": "2026-01-06T21:53:42+00:00" "time": "2026-01-06T21:53:42+00:00"
}, },
{
"name": "phpoffice/phpspreadsheet",
"version": "5.9.0",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
"reference": "05e99ebf61238a70227b4d9cc02d0030d34f6339"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/05e99ebf61238a70227b4d9cc02d0030d34f6339",
"reference": "05e99ebf61238a70227b4d9cc02d0030d34f6339",
"shasum": ""
},
"require": {
"composer/pcre": "^1||^2||^3",
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-filter": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-simplexml": "*",
"ext-xml": "*",
"ext-xmlreader": "*",
"ext-xmlwriter": "*",
"ext-zip": "*",
"ext-zlib": "*",
"maennchen/zipstream-php": "^2.1 || ^3.0",
"markbaker/complex": "^3.0",
"markbaker/matrix": "^3.0",
"php": "^8.2",
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0 || ^3.0",
"ext-intl": "*",
"friendsofphp/php-cs-fixer": "^3.2",
"mitoteam/jpgraph": "^10.5",
"mpdf/mpdf": "^8.1.1",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^1.1 || ^2.0",
"phpstan/phpstan-deprecation-rules": "^1.0 || ^2.0",
"phpstan/phpstan-phpunit": "^1.0 || ^2.0",
"phpunit/phpunit": "^10.5 || ^11.0",
"squizlabs/php_codesniffer": "^3.7",
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
"ext-intl": "PHP Internationalization Functions, required for NumberFormat Wizard and StringHelper::setLocale()",
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
},
"type": "library",
"autoload": {
"psr-4": {
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "https://blog.maartenballiauw.be"
},
{
"name": "Mark Baker",
"homepage": "https://markbakeruk.net"
},
{
"name": "Franck Lefevre",
"homepage": "https://rootslabs.net"
},
{
"name": "Erik Tilt"
},
{
"name": "Adrien Crivelli"
},
{
"name": "Owen Leibman"
}
],
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
"keywords": [
"OpenXML",
"excel",
"gnumeric",
"ods",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/5.9.0"
},
"time": "2026-07-12T19:17:39+00:00"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.9.5", "version": "1.9.5",

View File

@ -1,5 +1,5 @@
import { Head, router } from '@inertiajs/react'; import { Head, router } from '@inertiajs/react';
import { Plus, RotateCcw } from 'lucide-react'; import { FileSpreadsheet, Plus, RotateCcw } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog'; import { ConfirmDialog } from '@/components/confirm-dialog';
import type { PaginationState } from '@/components/data-table'; import type { PaginationState } from '@/components/data-table';
@ -16,6 +16,7 @@ import {
edit, edit,
destroy, destroy,
reset_password, reset_password,
exportMethod as exportLecturers,
} from '@/routes/admin/users/lecturers'; } from '@/routes/admin/users/lecturers';
import type { Lecturer } from './columns'; import type { Lecturer } from './columns';
import { createLecturerColumns } from './columns'; import { createLecturerColumns } from './columns';
@ -123,12 +124,24 @@ export default function LecturerIndex({
<PageHeader <PageHeader
title="Dosen" title="Dosen"
actions={ actions={
<Button asChild> <div className="flex items-center gap-2">
<a href={create.url()}> <Button variant="outline" asChild>
<Plus className="h-4 w-4" /> <a
Tambah href={exportLecturers.url({
</a> query: { search, ...filters },
</Button> })}
>
<FileSpreadsheet className="h-4 w-4" />
Export
</a>
</Button>
<Button asChild>
<a href={create.url()}>
<Plus className="h-4 w-4" />
Tambah
</a>
</Button>
</div>
} }
/> />

View File

@ -1,5 +1,5 @@
import { Head, router } from '@inertiajs/react'; import { Head, router } from '@inertiajs/react';
import { Info, Plus, RotateCcw } from 'lucide-react'; import { FileSpreadsheet, Info, Plus, RotateCcw } from 'lucide-react';
import { useState } from 'react'; import { useState } from 'react';
import { ConfirmDialog } from '@/components/confirm-dialog'; import { ConfirmDialog } from '@/components/confirm-dialog';
import type { PaginationState } from '@/components/data-table'; import type { PaginationState } from '@/components/data-table';
@ -17,6 +17,7 @@ import {
edit, edit,
reset_password, reset_password,
update_status, update_status,
exportMethod as exportStudents,
index as studentsIndex, index as studentsIndex,
} from '@/routes/admin/users/students'; } from '@/routes/admin/users/students';
import { createStudentColumns } from './columns'; import { createStudentColumns } from './columns';
@ -144,12 +145,24 @@ export default function StudentIndex({
<PageHeader <PageHeader
title="Mahasiswa" title="Mahasiswa"
actions={ actions={
<Button asChild> <div className="flex items-center gap-2">
<a href={create.url()}> <Button variant="outline" asChild>
<Plus className="h-4 w-4" /> <a
Tambah href={exportStudents.url({
</a> query: { search, ...filters },
</Button> })}
>
<FileSpreadsheet className="h-4 w-4" />
Export
</a>
</Button>
<Button asChild>
<a href={create.url()}>
<Plus className="h-4 w-4" />
Tambah
</a>
</Button>
</div>
} }
/> />

View File

@ -119,10 +119,12 @@
Route::prefix('admin/users')->name('admin.users.')->group(function () { Route::prefix('admin/users')->name('admin.users.')->group(function () {
Route::resource('lecturers', LecturerController::class)->except(['show'])->parameters(['lecturers' => 'user']); Route::resource('lecturers', LecturerController::class)->except(['show'])->parameters(['lecturers' => 'user']);
Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password'); Route::patch('lecturers/{user}/reset-password', [LecturerController::class, 'resetPassword'])->name('lecturers.reset_password');
Route::get('lecturers/export', [LecturerController::class, 'export'])->name('lecturers.export');
Route::resource('students', StudentController::class)->except(['show'])->parameters(['students' => 'user']); Route::resource('students', StudentController::class)->except(['show'])->parameters(['students' => 'user']);
Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password'); Route::patch('students/{user}/reset-password', [StudentController::class, 'resetPassword'])->name('students.reset_password');
Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status'); Route::patch('students/{user}/status', [StudentController::class, 'updateStatus'])->name('students.update_status');
Route::get('students/export', [StudentController::class, 'export'])->name('students.export');
Route::resource('administrators', AdministratorController::class)->except(['show'])->parameters(['administrators' => 'user']); Route::resource('administrators', AdministratorController::class)->except(['show'])->parameters(['administrators' => 'user']);
Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password'); Route::patch('administrators/{user}/reset-password', [AdministratorController::class, 'resetPassword'])->name('administrators.reset_password');