feat: add filter functionality to various admin pages
Some checks failed
tests / ci (pull_request) Has been cancelled
Some checks failed
tests / ci (pull_request) Has been cancelled
- Implemented filter dialogs in the following pages: - Academic Classes Assignments - Course Registrations - Materials - Announcements - Feedback - Tuition Invoices - Course Classes - Courses - Academic Terms - Academic Advising Logs - Letter Requests - Administrators - Lecturers - Students - Updated the useServerTable hook to support filter parameters. - Enhanced the UI with filter options for better data management and retrieval.
This commit is contained in:
parent
a8f864aa28
commit
a76ee85c24
@ -22,8 +22,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/academic-classes/assignments/index', [
|
return Inertia::render('admin/academic-classes/assignments/index', [
|
||||||
'assignments' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'assignments' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
courseClassId: $request->validated('course_class_id'),
|
||||||
|
),
|
||||||
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['course_class_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,11 +28,16 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/academic-classes/course-registrations/index', [
|
return Inertia::render('admin/academic-classes/course-registrations/index', [
|
||||||
'registrations' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'registrations' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
status: $request->validated('status'),
|
||||||
|
academicTermId: $request->validated('academic_term_id'),
|
||||||
|
),
|
||||||
'students' => $this->studentService->getAllForSelect(),
|
'students' => $this->studentService->getAllForSelect(),
|
||||||
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
||||||
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
||||||
'lecturers' => $this->lecturerService->getAllForSelect(),
|
'lecturers' => $this->lecturerService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['status', 'academic_term_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/academic-classes/materials/index', [
|
return Inertia::render('admin/academic-classes/materials/index', [
|
||||||
'materials' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'materials' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
courseClassId: $request->validated('course_class_id'),
|
||||||
|
),
|
||||||
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
'courseClasses' => $this->courseClassService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['course_class_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/announcements/index', [
|
return Inertia::render('admin/announcements/index', [
|
||||||
'announcements' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'announcements' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
departmentId: $request->validated('department_id'),
|
||||||
|
),
|
||||||
'departments' => $this->departmentService->getAllForSelect(),
|
'departments' => $this->departmentService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['department_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Enums\FeedbackStatus;
|
||||||
use App\Enums\FeedbackType;
|
use App\Enums\FeedbackType;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\Admin\FeedbackRequest;
|
use App\Http\Requests\Admin\FeedbackRequest;
|
||||||
@ -22,8 +23,15 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/feedback/index', [
|
return Inertia::render('admin/feedback/index', [
|
||||||
'feedbacks' => $this->service->paginated($request->user(), ...$request->validatedWithDefaults()),
|
'feedbacks' => $this->service->paginated(
|
||||||
|
$request->user(),
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
type: $request->validated('type'),
|
||||||
|
status: $request->validated('status'),
|
||||||
|
),
|
||||||
'types' => FeedbackType::options(),
|
'types' => FeedbackType::options(),
|
||||||
|
'statuses' => FeedbackStatus::options(),
|
||||||
|
'filters' => $request->only(['type', 'status']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,13 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/finances/tuition-invoices/index', [
|
return Inertia::render('admin/finances/tuition-invoices/index', [
|
||||||
'invoices' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'invoices' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
academicTermId: $request->validated('academic_term_id'),
|
||||||
|
),
|
||||||
'students' => $this->studentService->getAllForSelect(),
|
'students' => $this->studentService->getAllForSelect(),
|
||||||
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['academic_term_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,10 +26,15 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/manage/course-classes/index', [
|
return Inertia::render('admin/manage/course-classes/index', [
|
||||||
'courseClasses' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'courseClasses' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
academicTermId: $request->validated('academic_term_id'),
|
||||||
|
method: $request->validated('method'),
|
||||||
|
),
|
||||||
'courses' => $this->courseService->getAllForSelect(),
|
'courses' => $this->courseService->getAllForSelect(),
|
||||||
'lecturers' => $this->lecturerService->getAllForSelect(),
|
'lecturers' => $this->lecturerService->getAllForSelect(),
|
||||||
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
'academicTerms' => $this->academicTermService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['academic_term_id', 'method']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/manage/courses/index', [
|
return Inertia::render('admin/manage/courses/index', [
|
||||||
'courses' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'courses' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
departmentId: $request->validated('department_id'),
|
||||||
|
),
|
||||||
'departments' => $this->departmentService->getAllForSelect(),
|
'departments' => $this->departmentService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['department_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/master/academic-terms/index', [
|
return Inertia::render('admin/master/academic-terms/index', [
|
||||||
'academicTerms' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'academicTerms' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
semester: $request->validated('semester'),
|
||||||
|
isActive: $request->filled('is_active') ? filter_var($request->validated('is_active'), FILTER_VALIDATE_BOOLEAN) : null,
|
||||||
|
),
|
||||||
|
'filters' => $request->only(['semester', 'is_active']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,9 +24,13 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/services/academic-advising-logs/index', [
|
return Inertia::render('admin/services/academic-advising-logs/index', [
|
||||||
'logs' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'logs' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
lecturerId: $request->validated('lecturer_id'),
|
||||||
|
),
|
||||||
'students' => $this->studentService->getAllForSelect(),
|
'students' => $this->studentService->getAllForSelect(),
|
||||||
'lecturers' => $this->lecturerService->getAllForSelect(),
|
'lecturers' => $this->lecturerService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['lecturer_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,12 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/services/letter-requests/index', [
|
return Inertia::render('admin/services/letter-requests/index', [
|
||||||
'letterRequests' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'letterRequests' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
status: $request->validated('status'),
|
||||||
|
),
|
||||||
'students' => $this->studentService->getAllForSelect(),
|
'students' => $this->studentService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['status']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,11 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/users/administrators/index', [
|
return Inertia::render('admin/users/administrators/index', [
|
||||||
'administrators' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'administrators' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
gender: $request->validated('gender'),
|
||||||
|
),
|
||||||
|
'filters' => $request->only(['gender']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,13 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/users/lecturers/index', [
|
return Inertia::render('admin/users/lecturers/index', [
|
||||||
'lecturers' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'lecturers' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
gender: $request->validated('gender'),
|
||||||
|
departmentId: $request->validated('department_id'),
|
||||||
|
),
|
||||||
|
'departments' => $this->departmentService->getAllForSelect(),
|
||||||
|
'filters' => $request->only(['gender', 'department_id']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Admin\Users;
|
namespace App\Http\Controllers\Admin\Users;
|
||||||
|
|
||||||
|
use App\Enums\StudentStatus;
|
||||||
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\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
@ -24,7 +25,15 @@ public function __construct(
|
|||||||
public function index(PaginatedRequest $request): Response
|
public function index(PaginatedRequest $request): Response
|
||||||
{
|
{
|
||||||
return Inertia::render('admin/users/students/index', [
|
return Inertia::render('admin/users/students/index', [
|
||||||
'students' => $this->service->paginated(...$request->validatedWithDefaults()),
|
'students' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
gender: $request->validated('gender'),
|
||||||
|
departmentId: $request->validated('department_id'),
|
||||||
|
status: $request->validated('status'),
|
||||||
|
),
|
||||||
|
'departments' => $this->departmentService->getAllForSelect(),
|
||||||
|
'statuses' => StudentStatus::options(),
|
||||||
|
'filters' => $request->only(['gender', 'department_id', 'status']),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Http\Requests;
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use App\Enums\ClassMethod;
|
||||||
|
use App\Enums\Gender;
|
||||||
|
use App\Enums\Semester;
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class PaginatedRequest extends FormRequest
|
class PaginatedRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@ -19,6 +23,16 @@ public function rules(): array
|
|||||||
'sort' => ['nullable', 'string'],
|
'sort' => ['nullable', 'string'],
|
||||||
'direction' => ['nullable', 'string', 'in:asc,desc'],
|
'direction' => ['nullable', 'string', 'in:asc,desc'],
|
||||||
'highlight' => ['nullable', 'integer'],
|
'highlight' => ['nullable', 'integer'],
|
||||||
|
'gender' => ['nullable', 'string', Rule::in(Gender::values())],
|
||||||
|
'department_id' => ['nullable', 'integer'],
|
||||||
|
'status' => ['nullable', 'string'],
|
||||||
|
'semester' => ['nullable', 'string', Rule::in(Semester::values())],
|
||||||
|
'is_active' => ['nullable', Rule::in(['true', 'false'])],
|
||||||
|
'academic_term_id' => ['nullable', 'integer'],
|
||||||
|
'method' => ['nullable', 'string', Rule::in(ClassMethod::values())],
|
||||||
|
'course_class_id' => ['nullable', 'integer'],
|
||||||
|
'lecturer_id' => ['nullable', 'integer'],
|
||||||
|
'type' => ['nullable', 'string'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,13 +8,14 @@
|
|||||||
|
|
||||||
class AssignmentService
|
class AssignmentService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $courseClassId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Assignment::query()
|
return Assignment::query()
|
||||||
->select(['id', 'course_class_id', 'title', 'description', 'deadline'])
|
->select(['id', 'course_class_id', 'title', 'description', 'deadline'])
|
||||||
->withCount('submissions')
|
->withCount('submissions')
|
||||||
->with('courseClass.course:id,code,name')
|
->with('courseClass.course:id,code,name')
|
||||||
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
||||||
|
->when($courseClassId, fn ($q) => $q->where('course_class_id', $courseClassId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
class CourseRegistrationService
|
class CourseRegistrationService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $status = null, ?int $academicTermId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return CourseRegistration::query()
|
return CourseRegistration::query()
|
||||||
->select(['id', 'student_id', 'academic_term_id', 'course_class_id', 'status', 'approved_by', 'created_at'])
|
->select(['id', 'student_id', 'academic_term_id', 'course_class_id', 'status', 'approved_by', 'created_at'])
|
||||||
@ -22,6 +22,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
])
|
])
|
||||||
->when($search, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
->when($search, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
||||||
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
||||||
|
->when($status, fn ($q) => $q->where('status', $status))
|
||||||
|
->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,12 +8,13 @@
|
|||||||
|
|
||||||
class MaterialService
|
class MaterialService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $courseClassId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Material::query()
|
return Material::query()
|
||||||
->select(['id', 'course_class_id', 'title', 'description', 'meeting_number'])
|
->select(['id', 'course_class_id', 'title', 'description', 'meeting_number'])
|
||||||
->with('courseClass.course:id,code,name')
|
->with('courseClass.course:id,code,name')
|
||||||
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
||||||
|
->when($courseClassId, fn ($q) => $q->where('course_class_id', $courseClassId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,13 @@
|
|||||||
|
|
||||||
class AnnouncementService
|
class AnnouncementService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $departmentId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Announcement::query()
|
return Announcement::query()
|
||||||
->select(['id', 'title', 'content', 'department_id', 'enrollment_year', 'created_by', 'created_at'])
|
->select(['id', 'title', 'content', 'department_id', 'enrollment_year', 'created_by', 'created_at'])
|
||||||
->with(['department:id,name', 'creator.profile'])
|
->with(['department:id,name', 'creator.profile'])
|
||||||
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('title', 'like', "%{$search}%"))
|
||||||
|
->when($departmentId, fn ($q) => $q->where('department_id', $departmentId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,11 +40,13 @@ public function __construct()
|
|||||||
$this->sanitizer = new HtmlSanitizer($config);
|
$this->sanitizer = new HtmlSanitizer($config);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(User $user, int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(User $user, int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $type = null, ?string $status = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Feedback::query()
|
return Feedback::query()
|
||||||
->where('user_id', $user->id)
|
->where('user_id', $user->id)
|
||||||
->when($search, fn ($q) => $q->where('subject', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('subject', 'like', "%{$search}%"))
|
||||||
|
->when($type, fn ($q) => $q->where('type', $type))
|
||||||
|
->when($status, fn ($q) => $q->where('status', $status))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
class TuitionInvoiceService
|
class TuitionInvoiceService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $academicTermId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return TuitionInvoice::query()
|
return TuitionInvoice::query()
|
||||||
->select(['id', 'student_id', 'academic_term_id', 'amount_due', 'due_date'])
|
->select(['id', 'student_id', 'academic_term_id', 'amount_due', 'due_date'])
|
||||||
@ -16,6 +16,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->with(['student.user.profile', 'student.department', 'academicTerm:id,name,semester,start_date,end_date'])
|
->with(['student.user.profile', 'student.department', 'academicTerm:id,name,semester,start_date,end_date'])
|
||||||
->when($search, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
->when($search, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
||||||
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
||||||
|
->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ public function getAllForSelect(): Collection
|
|||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $academicTermId = null, ?string $method = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return CourseClass::query()
|
return CourseClass::query()
|
||||||
->select(['id', 'course_id', 'lecturer_id', 'academic_term_id', 'class_name', 'method'])
|
->select(['id', 'course_id', 'lecturer_id', 'academic_term_id', 'class_name', 'method'])
|
||||||
@ -23,6 +23,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->with(['course:id,code,name,department_id', 'lecturer.user.profile', 'academicTerm:id,name,semester,start_date,end_date'])
|
->with(['course:id,code,name,department_id', 'lecturer.user.profile', 'academicTerm:id,name,semester,start_date,end_date'])
|
||||||
->when($search, fn ($q) => $q->where('class_name', 'like', "%{$search}%")
|
->when($search, fn ($q) => $q->where('class_name', 'like', "%{$search}%")
|
||||||
->orWhereHas('course', fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")))
|
->orWhereHas('course', fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")))
|
||||||
|
->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
|
||||||
|
->when($method, fn ($q) => $q->where('method', $method))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,12 +13,13 @@ public function getAllForSelect(): Collection
|
|||||||
return Course::select(['id', 'code', 'name', 'department_id'])->get();
|
return Course::select(['id', 'code', 'name', 'department_id'])->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $departmentId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return Course::query()
|
return Course::query()
|
||||||
->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number'])
|
->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number'])
|
||||||
->with('department:id,name')
|
->with('department:id,name')
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))
|
||||||
|
->when($departmentId, fn ($q) => $q->where('department_id', $departmentId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,11 +13,13 @@ public function getAllForSelect(): Collection
|
|||||||
return AcademicTerm::select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active'])->latest()->get();
|
return AcademicTerm::select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active'])->latest()->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $semester = null, ?bool $isActive = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return AcademicTerm::query()
|
return AcademicTerm::query()
|
||||||
->select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active'])
|
->select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active'])
|
||||||
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%"))
|
||||||
|
->when($semester, fn ($q) => $q->where('semester', $semester))
|
||||||
|
->when($isActive !== null, fn ($q) => $q->where('is_active', $isActive))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
class AcademicAdvisingLogService
|
class AcademicAdvisingLogService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?int $lecturerId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return AcademicAdvisingLog::query()
|
return AcademicAdvisingLog::query()
|
||||||
->select(['id', 'student_id', 'lecturer_id', 'topic', 'notes', 'session_date', 'created_at'])
|
->select(['id', 'student_id', 'lecturer_id', 'topic', 'notes', 'session_date', 'created_at'])
|
||||||
@ -19,6 +19,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->when($search, fn ($q) => $q->where('topic', 'like', "%{$search}%")
|
->when($search, fn ($q) => $q->where('topic', 'like', "%{$search}%")
|
||||||
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
||||||
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
||||||
|
->when($lecturerId, fn ($q) => $q->where('lecturer_id', $lecturerId))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
class LetterRequestService
|
class LetterRequestService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $status = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return LetterRequest::query()
|
return LetterRequest::query()
|
||||||
->select(['id', 'student_id', 'letter_type', 'purpose', 'status', 'processed_by', 'submitted_at', 'completed_at'])
|
->select(['id', 'student_id', 'letter_type', 'purpose', 'status', 'processed_by', 'submitted_at', 'completed_at'])
|
||||||
@ -17,6 +17,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->when($search, fn ($q) => $q->where('letter_type', 'like', "%{$search}%")
|
->when($search, fn ($q) => $q->where('letter_type', 'like', "%{$search}%")
|
||||||
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
|
||||||
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
|
||||||
|
->when($status, fn ($q) => $q->where('status', $status))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
class AdministratorService
|
class AdministratorService
|
||||||
{
|
{
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
return User::with(['profile', 'roles'])
|
return User::with(['profile', 'roles'])
|
||||||
->whereHas('roles', fn ($q) => $q->whereIn('name', ['staff-admin', 'staff-keuangan']))
|
->whereHas('roles', fn ($q) => $q->whereIn('name', ['staff-admin', 'staff-keuangan']))
|
||||||
@ -18,6 +18,7 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->orWhere('email', 'like', "%{$search}%")
|
->orWhere('email', 'like', "%{$search}%")
|
||||||
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"));
|
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"));
|
||||||
}))
|
}))
|
||||||
|
->when($gender, fn ($q) => $q->whereHas('profile', fn ($q) => $q->where('gender', $gender)))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public function getAllForSelect(): Collection
|
|||||||
return Lecturer::with('user.profile')->get();
|
return Lecturer::with('user.profile')->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): LengthAwarePaginator
|
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc', ?string $gender = null, ?int $departmentId = null): LengthAwarePaginator
|
||||||
{
|
{
|
||||||
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'))
|
||||||
@ -26,6 +26,8 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))
|
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))
|
||||||
->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($departmentId, fn ($q) => $q->whereHas('lecturer', fn ($q) => $q->where('department_id', $departmentId)))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ public function getAllForSelect(): Collection
|
|||||||
return Student::with(['user.profile', 'department'])->get();
|
return Student::with(['user.profile', 'department'])->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginated(int $perPage = 25, string $search = '', string $sort = 'created_at', string $direction = 'desc'): 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 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'))
|
||||||
@ -26,6 +26,9 @@ public function paginated(int $perPage = 25, string $search = '', string $sort =
|
|||||||
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))
|
->orWhereHas('profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))
|
||||||
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%"));
|
->orWhereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%"));
|
||||||
}))
|
}))
|
||||||
|
->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($status, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('status', $status)))
|
||||||
->orderBy($sort, $direction)
|
->orderBy($sort, $direction)
|
||||||
->paginate($perPage);
|
->paginate($perPage);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -205,7 +205,88 @@ ## 5. Service ✅
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Wayfinder ✅
|
## 6. Filter dropdown di halaman listing ✅
|
||||||
|
|
||||||
|
Tombol filter (ikon corong di samping search box) memakai dialog, bukan
|
||||||
|
popover — komponennya `resources/js/components/filter-dialog.tsx`
|
||||||
|
(`<FilterDialog fields={...} activeFilters={filters} onApply={applyFilters} />`
|
||||||
|
dilewatkan lewat prop `toolbar` milik `<DataTable>`, otomatis nongol di
|
||||||
|
sebelah kanan search box).
|
||||||
|
|
||||||
|
Sudah diterapkan di semua halaman listing yang punya field layak difilter:
|
||||||
|
|
||||||
|
| Halaman | Field filter |
|
||||||
|
| --------------------------- | -------------------------------- |
|
||||||
|
| Administrator/Dosen/Mahasiswa | Jenis Kelamin (+ Jurusan, Status untuk Dosen/Mahasiswa) |
|
||||||
|
| Periode Akademik | Semester, Status Aktif |
|
||||||
|
| Registrasi KRS | Status, Periode Akademik |
|
||||||
|
| Materi, Tugas | Kelas |
|
||||||
|
| Mata Kuliah | Jurusan |
|
||||||
|
| Kelas Mata Kuliah | Periode Akademik, Metode |
|
||||||
|
| Pengumuman | Jurusan |
|
||||||
|
| Tagihan | Periode Akademik |
|
||||||
|
| Surat Permohonan | Status |
|
||||||
|
| Bimbingan Akademik | Dosen |
|
||||||
|
| Kritik dan Saran | Jenis, Status |
|
||||||
|
|
||||||
|
Halaman tanpa field yang layak difilter (mis. Jurusan/Master — tabelnya kecil,
|
||||||
|
tidak butuh filter) sengaja tidak diberi `FilterDialog`.
|
||||||
|
|
||||||
|
**Perilaku UI (jangan diubah tanpa alasan kuat):**
|
||||||
|
|
||||||
|
- **Langsung diterapkan** — begitu satu field di dalam dialog dipilih
|
||||||
|
(`onValueChange`), filter langsung jalan (navigasi Inertia), tidak ada
|
||||||
|
tombol "Terapkan" terpisah yang harus diklik dulu.
|
||||||
|
- **Field filter disusun 2 kolom per baris** (`grid grid-cols-2 gap-4`) kalau
|
||||||
|
field-nya lebih dari satu; kalau cuma 1 field, 1 kolom saja (`grid gap-4`)
|
||||||
|
— jangan sisakan slot kosong di grid.
|
||||||
|
- **Tombol "Hapus Filter" ada DI LUAR dialog**, sejajar di samping tombol
|
||||||
|
ikon filter (bukan di footer dialog), berupa tombol teks (`variant="ghost"`
|
||||||
|
+ label "Hapus Filter"), dan cuma muncul kalau ada filter yang aktif.
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
|
||||||
|
1. Field filter (mis. `gender`, `department_id`, `status`) didaftarkan di
|
||||||
|
`App\Http\Requests\PaginatedRequest::rules()` sebagai `nullable` — request
|
||||||
|
ini dipakai bersama oleh semua halaman listing, jadi field filter yang
|
||||||
|
sifatnya umum (dipakai lebih dari satu fitur) taruh di sini alih-alih
|
||||||
|
bikin FormRequest baru per halaman.
|
||||||
|
2. `Service::paginated()` menerima parameter filter tambahan sebagai
|
||||||
|
parameter bernama opsional di akhir signature (setelah
|
||||||
|
`$perPage/$search/$sort/$direction`), mis.
|
||||||
|
`paginated(..., ?string $gender = null, ?int $departmentId = null)`, lalu
|
||||||
|
diterapkan dengan `->when($gender, fn ($q) => ...)`.
|
||||||
|
3. Controller memanggilnya secara **eksplisit per parameter** — jangan
|
||||||
|
nge-spread seluruh `$request->validated()` mentah-mentah ke
|
||||||
|
`paginated()`, karena tidak semua Service menerima semua field filter
|
||||||
|
(bisa error "Unknown named parameter"):
|
||||||
|
|
||||||
|
```php
|
||||||
|
'students' => $this->service->paginated(
|
||||||
|
...$request->validatedWithDefaults(),
|
||||||
|
gender: $request->validated('gender'),
|
||||||
|
departmentId: $request->validated('department_id'),
|
||||||
|
status: $request->validated('status'),
|
||||||
|
),
|
||||||
|
'filters' => $request->only(['gender', 'department_id', 'status']),
|
||||||
|
```
|
||||||
|
|
||||||
|
**Frontend:**
|
||||||
|
|
||||||
|
1. `Props.filters` menampung nilai filter yang sedang aktif (dari query
|
||||||
|
string, dikirim controller lewat `$request->only([...])`).
|
||||||
|
2. `useServerTable({..., filters})` — prop `filters` diteruskan apa adanya
|
||||||
|
dari `Props.filters` (bukan `useState` terpisah, karena Inertia sudah
|
||||||
|
selalu mengirim prop terbaru setiap navigasi).
|
||||||
|
3. Definisikan `filterFields: FilterField[]` (key, label, options) sesuai
|
||||||
|
data yang tersedia di halaman itu (mis. `departments` dari prop untuk
|
||||||
|
Select jurusan), lalu render
|
||||||
|
`<FilterDialog fields={filterFields} activeFilters={filters} onApply={applyFilters} />`
|
||||||
|
sebagai `toolbar` di `<DataTable>`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Wayfinder ✅
|
||||||
|
|
||||||
Selalu jalankan generate dengan flag form variant, supaya `<Form
|
Selalu jalankan generate dengan flag form variant, supaya `<Form
|
||||||
{...Controller.method.form()}>` tidak error saat runtime:
|
{...Controller.method.form()}>` tidak error saat runtime:
|
||||||
|
|||||||
136
resources/js/components/filter-dialog.tsx
Normal file
136
resources/js/components/filter-dialog.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { Filter, X } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { Label } from '@/components/ui/label';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
|
||||||
|
export type FilterOption = {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FilterField = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
placeholder?: string;
|
||||||
|
options: FilterOption[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type FilterDialogProps = {
|
||||||
|
fields: FilterField[];
|
||||||
|
activeFilters: Record<string, string | undefined>;
|
||||||
|
onApply: (filters: Record<string, string>) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FilterDialog({
|
||||||
|
fields,
|
||||||
|
activeFilters,
|
||||||
|
onApply,
|
||||||
|
}: FilterDialogProps) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const activeCount = fields.filter(
|
||||||
|
(field) => activeFilters[field.key],
|
||||||
|
).length;
|
||||||
|
|
||||||
|
function handleChange(key: string, value: string) {
|
||||||
|
const next: Record<string, string> = {};
|
||||||
|
|
||||||
|
fields.forEach((field) => {
|
||||||
|
const current =
|
||||||
|
field.key === key ? value : activeFilters[field.key];
|
||||||
|
|
||||||
|
if (current && current !== 'all') {
|
||||||
|
next[field.key] = current;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onApply(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClear() {
|
||||||
|
onApply({});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon" className="relative">
|
||||||
|
<Filter className="h-4 w-4" />
|
||||||
|
{activeCount > 0 && (
|
||||||
|
<span className="absolute -top-1.5 -right-1.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] text-primary-foreground">
|
||||||
|
{activeCount}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Filter</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
fields.length > 1
|
||||||
|
? 'grid grid-cols-2 gap-4 py-4'
|
||||||
|
: 'grid gap-4 py-4'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<div className="grid gap-2" key={field.key}>
|
||||||
|
<Label>{field.label}</Label>
|
||||||
|
<Select
|
||||||
|
value={activeFilters[field.key] ?? 'all'}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleChange(field.key, value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
field.placeholder ?? 'Semua'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">
|
||||||
|
Semua
|
||||||
|
</SelectItem>
|
||||||
|
{field.options.map((option) => (
|
||||||
|
<SelectItem
|
||||||
|
key={option.value}
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{activeCount > 0 && (
|
||||||
|
<Button variant="ghost" size="sm" onClick={handleClear}>
|
||||||
|
<X className="h-4 w-4" />
|
||||||
|
Hapus Filter
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
|
||||||
import { router } from '@inertiajs/react';
|
import { router } from '@inertiajs/react';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
import type { PaginationState } from '@/components/data-table';
|
||||||
|
|
||||||
type UseServerTableOptions = {
|
type UseServerTableOptions = {
|
||||||
route: () => string;
|
route: () => string;
|
||||||
@ -80,11 +80,26 @@ export function useServerTable({
|
|||||||
route(),
|
route(),
|
||||||
filterWithParams
|
filterWithParams
|
||||||
? {
|
? {
|
||||||
...newFilters,
|
...newFilters,
|
||||||
page: 1,
|
page: 1,
|
||||||
per_page: pagination.per_page,
|
per_page: pagination.per_page,
|
||||||
search,
|
search,
|
||||||
}
|
}
|
||||||
|
: newFilters,
|
||||||
|
{ preserveState: true, replace: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyFilters(newFilters: Record<string, string | undefined>) {
|
||||||
|
router.get(
|
||||||
|
route(),
|
||||||
|
filterWithParams
|
||||||
|
? {
|
||||||
|
...newFilters,
|
||||||
|
page: 1,
|
||||||
|
per_page: pagination.per_page,
|
||||||
|
search,
|
||||||
|
}
|
||||||
: newFilters,
|
: newFilters,
|
||||||
{ preserveState: true, replace: true },
|
{ preserveState: true, replace: true },
|
||||||
);
|
);
|
||||||
@ -95,10 +110,10 @@ export function useServerTable({
|
|||||||
route(),
|
route(),
|
||||||
filterWithParams
|
filterWithParams
|
||||||
? {
|
? {
|
||||||
page: 1,
|
page: 1,
|
||||||
per_page: pagination.per_page,
|
per_page: pagination.per_page,
|
||||||
search,
|
search,
|
||||||
}
|
}
|
||||||
: {},
|
: {},
|
||||||
{ preserveState: true, replace: true },
|
{ preserveState: true, replace: true },
|
||||||
);
|
);
|
||||||
@ -113,6 +128,7 @@ export function useServerTable({
|
|||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
applyFilter,
|
applyFilter,
|
||||||
|
applyFilters,
|
||||||
clearFilters,
|
clearFilters,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { DataTable } from '@/components/data-table';
|
|||||||
import { DateTimeField } from '@/components/datetime-field';
|
import { DateTimeField } from '@/components/datetime-field';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
import { FileUploadField } from '@/components/file-upload-field';
|
import { FileUploadField } from '@/components/file-upload-field';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -46,6 +48,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
courseClasses: CourseClassOption[];
|
courseClasses: CourseClassOption[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
course_class_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function courseClassLabel(courseClass: CourseClassOption): string {
|
function courseClassLabel(courseClass: CourseClassOption): string {
|
||||||
@ -60,11 +65,23 @@ export default function AssignmentIndex({
|
|||||||
assignments,
|
assignments,
|
||||||
courseClasses,
|
courseClasses,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Assignment | null>(null);
|
const [editing, setEditing] = useState<Assignment | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Assignment | null>(null);
|
const [deleting, setDeleting] = useState<Assignment | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'course_class_id',
|
||||||
|
label: 'Kelas',
|
||||||
|
options: courseClasses.map((courseClass) => ({
|
||||||
|
value: String(courseClass.id),
|
||||||
|
label: courseClassLabel(courseClass),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: assignments.current_page,
|
current_page: assignments.current_page,
|
||||||
last_page: assignments.last_page,
|
last_page: assignments.last_page,
|
||||||
@ -77,9 +94,11 @@ export default function AssignmentIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => assignmentIndex.url(),
|
route: () => assignmentIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -168,6 +187,13 @@ export default function AssignmentIndex({
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useState } from 'react';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -54,6 +56,10 @@ type Props = {
|
|||||||
courseClasses: CourseRegistrationCourseClass[];
|
courseClasses: CourseRegistrationCourseClass[];
|
||||||
lecturers: LecturerOption[];
|
lecturers: LecturerOption[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
status?: string;
|
||||||
|
academic_term_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function studentLabel(student: CourseRegistrationStudent): string {
|
function studentLabel(student: CourseRegistrationStudent): string {
|
||||||
@ -78,11 +84,31 @@ export default function CourseRegistrationIndex({
|
|||||||
courseClasses,
|
courseClasses,
|
||||||
lecturers,
|
lecturers,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<CourseRegistration | null>(null);
|
const [editing, setEditing] = useState<CourseRegistration | null>(null);
|
||||||
const [deleting, setDeleting] = useState<CourseRegistration | null>(null);
|
const [deleting, setDeleting] = useState<CourseRegistration | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
options: RegistrationStatuses.map((status) => ({
|
||||||
|
value: status,
|
||||||
|
label: RegistrationStatusLabels[status],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'academic_term_id',
|
||||||
|
label: 'Periode Akademik',
|
||||||
|
options: academicTerms.map((term) => ({
|
||||||
|
value: String(term.id),
|
||||||
|
label: term.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: registrations.current_page,
|
current_page: registrations.current_page,
|
||||||
last_page: registrations.last_page,
|
last_page: registrations.last_page,
|
||||||
@ -95,9 +121,11 @@ export default function CourseRegistrationIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => courseRegistrationIndex.url(),
|
route: () => courseRegistrationIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -177,6 +205,13 @@ export default function CourseRegistrationIndex({
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="student"
|
searchKey="student"
|
||||||
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import type { PaginationState } from '@/components/data-table';
|
|||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
import { FileUploadField } from '@/components/file-upload-field';
|
import { FileUploadField } from '@/components/file-upload-field';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -45,6 +47,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
courseClasses: CourseClassOption[];
|
courseClasses: CourseClassOption[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
course_class_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function courseClassLabel(courseClass: CourseClassOption): string {
|
function courseClassLabel(courseClass: CourseClassOption): string {
|
||||||
@ -59,11 +64,23 @@ export default function MaterialIndex({
|
|||||||
materials,
|
materials,
|
||||||
courseClasses,
|
courseClasses,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Material | null>(null);
|
const [editing, setEditing] = useState<Material | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Material | null>(null);
|
const [deleting, setDeleting] = useState<Material | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'course_class_id',
|
||||||
|
label: 'Kelas',
|
||||||
|
options: courseClasses.map((courseClass) => ({
|
||||||
|
value: String(courseClass.id),
|
||||||
|
label: courseClassLabel(courseClass),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: materials.current_page,
|
current_page: materials.current_page,
|
||||||
last_page: materials.last_page,
|
last_page: materials.last_page,
|
||||||
@ -76,9 +93,11 @@ export default function MaterialIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => materialIndex.url(),
|
route: () => materialIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -167,6 +186,13 @@ export default function MaterialIndex({
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useRef, useState } from 'react';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -41,17 +43,32 @@ type Props = {
|
|||||||
};
|
};
|
||||||
departments: AnnouncementDepartment[];
|
departments: AnnouncementDepartment[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
department_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AnnouncementIndex({
|
export default function AnnouncementIndex({
|
||||||
announcements,
|
announcements,
|
||||||
departments,
|
departments,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Announcement | null>(null);
|
const [editing, setEditing] = useState<Announcement | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Announcement | null>(null);
|
const [deleting, setDeleting] = useState<Announcement | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'department_id',
|
||||||
|
label: 'Jurusan',
|
||||||
|
options: departments.map((department) => ({
|
||||||
|
value: String(department.id),
|
||||||
|
label: department.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: announcements.current_page,
|
current_page: announcements.current_page,
|
||||||
last_page: announcements.last_page,
|
last_page: announcements.last_page,
|
||||||
@ -64,9 +81,11 @@ export default function AnnouncementIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => announcementIndex.url(),
|
route: () => announcementIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -140,6 +159,13 @@ export default function AnnouncementIndex({
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="title"
|
searchKey="title"
|
||||||
searchPlaceholder="Cari judul pengumuman..."
|
searchPlaceholder="Cari judul pengumuman..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useState } from 'react';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -39,13 +41,36 @@ type Props = {
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
types: FeedbackTypeOption[];
|
types: FeedbackTypeOption[];
|
||||||
|
statuses: FeedbackTypeOption[];
|
||||||
|
filters: {
|
||||||
|
type?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FeedbackIndex({ feedbacks, types }: Props) {
|
export default function FeedbackIndex({
|
||||||
|
feedbacks,
|
||||||
|
types,
|
||||||
|
statuses,
|
||||||
|
filters,
|
||||||
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Feedback | null>(null);
|
const [editing, setEditing] = useState<Feedback | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Feedback | null>(null);
|
const [deleting, setDeleting] = useState<Feedback | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
label: 'Jenis',
|
||||||
|
options: types,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
options: statuses,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: feedbacks.current_page,
|
current_page: feedbacks.current_page,
|
||||||
last_page: feedbacks.last_page,
|
last_page: feedbacks.last_page,
|
||||||
@ -58,9 +83,11 @@ export default function FeedbackIndex({ feedbacks, types }: Props) {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => feedbackIndex.url(),
|
route: () => feedbackIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -133,6 +160,13 @@ export default function FeedbackIndex({ feedbacks, types }: Props) {
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="subject"
|
searchKey="subject"
|
||||||
searchPlaceholder="Cari subjek..."
|
searchPlaceholder="Cari subjek..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import type { PaginationState } from '@/components/data-table';
|
|||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DatePicker } from '@/components/date-picker';
|
import { DatePicker } from '@/components/date-picker';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -45,6 +47,9 @@ type Props = {
|
|||||||
students: TuitionInvoiceStudent[];
|
students: TuitionInvoiceStudent[];
|
||||||
academicTerms: AcademicTermOption[];
|
academicTerms: AcademicTermOption[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
academic_term_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function studentLabel(student: TuitionInvoiceStudent): string {
|
function studentLabel(student: TuitionInvoiceStudent): string {
|
||||||
@ -56,11 +61,23 @@ export default function TuitionInvoiceIndex({
|
|||||||
students,
|
students,
|
||||||
academicTerms,
|
academicTerms,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<TuitionInvoice | null>(null);
|
const [editing, setEditing] = useState<TuitionInvoice | null>(null);
|
||||||
const [deleting, setDeleting] = useState<TuitionInvoice | null>(null);
|
const [deleting, setDeleting] = useState<TuitionInvoice | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'academic_term_id',
|
||||||
|
label: 'Periode Akademik',
|
||||||
|
options: academicTerms.map((term) => ({
|
||||||
|
value: String(term.id),
|
||||||
|
label: term.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: invoices.current_page,
|
current_page: invoices.current_page,
|
||||||
last_page: invoices.last_page,
|
last_page: invoices.last_page,
|
||||||
@ -73,9 +90,11 @@ export default function TuitionInvoiceIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => tuitionInvoiceIndex.url(),
|
route: () => tuitionInvoiceIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -151,6 +170,13 @@ export default function TuitionInvoiceIndex({
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="student"
|
searchKey="student"
|
||||||
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useState } from 'react';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -49,6 +51,10 @@ type Props = {
|
|||||||
lecturers: Lecturer[];
|
lecturers: Lecturer[];
|
||||||
academicTerms: AcademicTerm[];
|
academicTerms: AcademicTerm[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
academic_term_id?: string;
|
||||||
|
method?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CourseClassIndex({
|
export default function CourseClassIndex({
|
||||||
@ -57,11 +63,31 @@ export default function CourseClassIndex({
|
|||||||
lecturers,
|
lecturers,
|
||||||
academicTerms,
|
academicTerms,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<CourseClass | null>(null);
|
const [editing, setEditing] = useState<CourseClass | null>(null);
|
||||||
const [deleting, setDeleting] = useState<CourseClass | null>(null);
|
const [deleting, setDeleting] = useState<CourseClass | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'academic_term_id',
|
||||||
|
label: 'Periode Akademik',
|
||||||
|
options: academicTerms.map((term) => ({
|
||||||
|
value: String(term.id),
|
||||||
|
label: term.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'method',
|
||||||
|
label: 'Metode',
|
||||||
|
options: ClassMethods.map((method) => ({
|
||||||
|
value: method,
|
||||||
|
label: ClassMethodLabels[method],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: courseClasses.current_page,
|
current_page: courseClasses.current_page,
|
||||||
last_page: courseClasses.last_page,
|
last_page: courseClasses.last_page,
|
||||||
@ -74,9 +100,11 @@ export default function CourseClassIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => courseClassIndex.url(),
|
route: () => courseClassIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -169,6 +197,13 @@ export default function CourseClassIndex({
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { useState } from 'react';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -39,17 +41,32 @@ type Props = {
|
|||||||
};
|
};
|
||||||
departments: Department[];
|
departments: Department[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
department_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CourseIndex({
|
export default function CourseIndex({
|
||||||
courses,
|
courses,
|
||||||
departments,
|
departments,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<Course | null>(null);
|
const [editing, setEditing] = useState<Course | null>(null);
|
||||||
const [deleting, setDeleting] = useState<Course | null>(null);
|
const [deleting, setDeleting] = useState<Course | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'department_id',
|
||||||
|
label: 'Jurusan',
|
||||||
|
options: departments.map((department) => ({
|
||||||
|
value: String(department.id),
|
||||||
|
label: department.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: courses.current_page,
|
current_page: courses.current_page,
|
||||||
last_page: courses.last_page,
|
last_page: courses.last_page,
|
||||||
@ -62,9 +79,11 @@ export default function CourseIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => courseIndex.url(),
|
route: () => courseIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -153,6 +172,13 @@ export default function CourseIndex({
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import type { PaginationState } from '@/components/data-table';
|
|||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DatePicker } from '@/components/date-picker';
|
import { DatePicker } from '@/components/date-picker';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -33,9 +35,36 @@ type Props = {
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
semester?: string;
|
||||||
|
is_active?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AcademicTermIndex({ academicTerms, highlight }: Props) {
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'semester',
|
||||||
|
label: 'Semester',
|
||||||
|
options: Object.values(Semester).map((value) => ({
|
||||||
|
value,
|
||||||
|
label: SemesterLabels[value],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'is_active',
|
||||||
|
label: 'Status',
|
||||||
|
options: [
|
||||||
|
{ value: 'true', label: 'Aktif' },
|
||||||
|
{ value: 'false', label: 'Tidak Aktif' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function AcademicTermIndex({
|
||||||
|
academicTerms,
|
||||||
|
highlight,
|
||||||
|
filters,
|
||||||
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<AcademicTerm | null>(null);
|
const [editing, setEditing] = useState<AcademicTerm | null>(null);
|
||||||
const [deleting, setDeleting] = useState<AcademicTerm | null>(null);
|
const [deleting, setDeleting] = useState<AcademicTerm | null>(null);
|
||||||
@ -52,9 +81,11 @@ export default function AcademicTermIndex({ academicTerms, highlight }: Props) {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => academicTermIndex.url(),
|
route: () => academicTermIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -114,10 +145,7 @@ export default function AcademicTermIndex({ academicTerms, highlight }: Props) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CreateForm
|
<CreateForm open={createOpen} onOpenChange={setCreateOpen} />
|
||||||
open={createOpen}
|
|
||||||
onOpenChange={setCreateOpen}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditForm
|
<EditForm
|
||||||
key={editing?.id}
|
key={editing?.id}
|
||||||
@ -141,6 +169,13 @@ export default function AcademicTermIndex({ academicTerms, highlight }: Props) {
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
@ -207,9 +242,18 @@ function CreateForm({
|
|||||||
className="flex flex-row gap-4"
|
className="flex flex-row gap-4"
|
||||||
>
|
>
|
||||||
{Object.values(Semester).map((value) => (
|
{Object.values(Semester).map((value) => (
|
||||||
<div key={value} className="flex items-center gap-2">
|
<div
|
||||||
<RadioGroupItem value={value} id={`create-${value}`} />
|
key={value}
|
||||||
<Label htmlFor={`create-${value}`} className="font-normal">
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<RadioGroupItem
|
||||||
|
value={value}
|
||||||
|
id={`create-${value}`}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`create-${value}`}
|
||||||
|
className="font-normal"
|
||||||
|
>
|
||||||
{SemesterLabels[value]}
|
{SemesterLabels[value]}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
@ -220,9 +264,18 @@ function CreateForm({
|
|||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>
|
||||||
Tanggal Mulai <span className="text-destructive">*</span>
|
Tanggal Mulai{' '}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<input type="hidden" name="start_date" value={startDate ? startDate.toISOString().split('T')[0] : ''} />
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="start_date"
|
||||||
|
value={
|
||||||
|
startDate
|
||||||
|
? startDate.toISOString().split('T')[0]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={setStartDate}
|
onChange={setStartDate}
|
||||||
@ -231,8 +284,19 @@ function CreateForm({
|
|||||||
<InputError message={errors.start_date} />
|
<InputError message={errors.start_date} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Tanggal Selesai <span className="text-destructive">*</span></Label>
|
<Label>
|
||||||
<input type="hidden" name="end_date" value={endDate ? endDate.toISOString().split('T')[0] : ''} />
|
Tanggal Selesai{' '}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="end_date"
|
||||||
|
value={
|
||||||
|
endDate
|
||||||
|
? endDate.toISOString().split('T')[0]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onChange={setEndDate}
|
onChange={setEndDate}
|
||||||
@ -286,8 +350,7 @@ function EditForm({
|
|||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label htmlFor="edit-name">
|
<Label htmlFor="edit-name">
|
||||||
Nama{' '}
|
Nama <span className="text-destructive">*</span>
|
||||||
<span className="text-destructive">*</span>
|
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="edit-name"
|
id="edit-name"
|
||||||
@ -299,7 +362,8 @@ function EditForm({
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>
|
<Label>
|
||||||
Semester <span className="text-destructive">*</span>
|
Semester{' '}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
name="semester"
|
name="semester"
|
||||||
@ -307,9 +371,18 @@ function EditForm({
|
|||||||
className="flex flex-row gap-4"
|
className="flex flex-row gap-4"
|
||||||
>
|
>
|
||||||
{Object.values(Semester).map((value) => (
|
{Object.values(Semester).map((value) => (
|
||||||
<div key={value} className="flex items-center gap-2">
|
<div
|
||||||
<RadioGroupItem value={value} id={`edit-${value}`} />
|
key={value}
|
||||||
<Label htmlFor={`edit-${value}`} className="font-normal">
|
className="flex items-center gap-2"
|
||||||
|
>
|
||||||
|
<RadioGroupItem
|
||||||
|
value={value}
|
||||||
|
id={`edit-${value}`}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor={`edit-${value}`}
|
||||||
|
className="font-normal"
|
||||||
|
>
|
||||||
{SemesterLabels[value]}
|
{SemesterLabels[value]}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
@ -319,8 +392,21 @@ function EditForm({
|
|||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Tanggal Mulai <span className="text-destructive">*</span></Label>
|
<Label>
|
||||||
<input type="hidden" name="start_date" value={startDate ? startDate.toISOString().split('T')[0] : ''} />
|
Tanggal Mulai{' '}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="start_date"
|
||||||
|
value={
|
||||||
|
startDate
|
||||||
|
? startDate
|
||||||
|
.toISOString()
|
||||||
|
.split('T')[0]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={startDate}
|
value={startDate}
|
||||||
onChange={setStartDate}
|
onChange={setStartDate}
|
||||||
@ -329,8 +415,21 @@ function EditForm({
|
|||||||
<InputError message={errors.start_date} />
|
<InputError message={errors.start_date} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Tanggal Selesai <span className="text-destructive">*</span></Label>
|
<Label>
|
||||||
<input type="hidden" name="end_date" value={endDate ? endDate.toISOString().split('T')[0] : ''} />
|
Tanggal Selesai{' '}
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="end_date"
|
||||||
|
value={
|
||||||
|
endDate
|
||||||
|
? endDate
|
||||||
|
.toISOString()
|
||||||
|
.split('T')[0]
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
value={endDate}
|
value={endDate}
|
||||||
onChange={setEndDate}
|
onChange={setEndDate}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import type { PaginationState } from '@/components/data-table';
|
|||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DateTimeField } from '@/components/datetime-field';
|
import { DateTimeField } from '@/components/datetime-field';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -44,6 +46,9 @@ type Props = {
|
|||||||
students: AcademicAdvisingLogStudent[];
|
students: AcademicAdvisingLogStudent[];
|
||||||
lecturers: AcademicAdvisingLogLecturer[];
|
lecturers: AcademicAdvisingLogLecturer[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
lecturer_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function studentLabel(student: AcademicAdvisingLogStudent): string {
|
function studentLabel(student: AcademicAdvisingLogStudent): string {
|
||||||
@ -59,11 +64,23 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
students,
|
students,
|
||||||
lecturers,
|
lecturers,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<AcademicAdvisingLog | null>(null);
|
const [editing, setEditing] = useState<AcademicAdvisingLog | null>(null);
|
||||||
const [deleting, setDeleting] = useState<AcademicAdvisingLog | null>(null);
|
const [deleting, setDeleting] = useState<AcademicAdvisingLog | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'lecturer_id',
|
||||||
|
label: 'Dosen',
|
||||||
|
options: lecturers.map((lecturer) => ({
|
||||||
|
value: String(lecturer.id),
|
||||||
|
label: lecturerLabel(lecturer),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: logs.current_page,
|
current_page: logs.current_page,
|
||||||
last_page: logs.last_page,
|
last_page: logs.last_page,
|
||||||
@ -76,9 +93,11 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => academicAdvisingLogIndex.url(),
|
route: () => academicAdvisingLogIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -154,6 +173,13 @@ export default function AcademicAdvisingLogIndex({
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="student"
|
searchKey="student"
|
||||||
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
searchPlaceholder="Cari nama atau NIM mahasiswa..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import type { PaginationState } from '@/components/data-table';
|
|||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
import { FileUploadField } from '@/components/file-upload-field';
|
import { FileUploadField } from '@/components/file-upload-field';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
@ -43,6 +45,9 @@ type Props = {
|
|||||||
};
|
};
|
||||||
students: LetterRequestStudent[];
|
students: LetterRequestStudent[];
|
||||||
highlight?: number;
|
highlight?: number;
|
||||||
|
filters: {
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function studentLabel(student: LetterRequestStudent): string {
|
function studentLabel(student: LetterRequestStudent): string {
|
||||||
@ -53,11 +58,23 @@ export default function LetterRequestIndex({
|
|||||||
letterRequests,
|
letterRequests,
|
||||||
students,
|
students,
|
||||||
highlight,
|
highlight,
|
||||||
|
filters,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [editing, setEditing] = useState<LetterRequest | null>(null);
|
const [editing, setEditing] = useState<LetterRequest | null>(null);
|
||||||
const [deleting, setDeleting] = useState<LetterRequest | null>(null);
|
const [deleting, setDeleting] = useState<LetterRequest | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
options: LetterStatuses.map((status) => ({
|
||||||
|
value: status,
|
||||||
|
label: LetterStatusLabels[status],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: letterRequests.current_page,
|
current_page: letterRequests.current_page,
|
||||||
last_page: letterRequests.last_page,
|
last_page: letterRequests.last_page,
|
||||||
@ -70,9 +87,11 @@ export default function LetterRequestIndex({
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => letterRequestIndex.url(),
|
route: () => letterRequestIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -146,6 +165,13 @@ export default function LetterRequestIndex({
|
|||||||
searchValue={search}
|
searchValue={search}
|
||||||
searchKey="student"
|
searchKey="student"
|
||||||
searchPlaceholder="Cari nama, NIM, atau jenis surat..."
|
searchPlaceholder="Cari nama, NIM, atau jenis surat..."
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { ConfirmDialog } from '@/components/confirm-dialog';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
@ -13,7 +15,7 @@ import {
|
|||||||
create,
|
create,
|
||||||
destroy,
|
destroy,
|
||||||
edit,
|
edit,
|
||||||
reset_password
|
reset_password,
|
||||||
} from '@/routes/admin/users/administrators';
|
} from '@/routes/admin/users/administrators';
|
||||||
import type { Administrator } from './columns';
|
import type { Administrator } from './columns';
|
||||||
import { createAdministratorColumns } from './columns';
|
import { createAdministratorColumns } from './columns';
|
||||||
@ -26,9 +28,23 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
filters: {
|
||||||
|
gender?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AdministratorIndex({ administrators }: Props) {
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'gender',
|
||||||
|
label: 'Jenis Kelamin',
|
||||||
|
options: [
|
||||||
|
{ value: 'male', label: 'Laki-laki' },
|
||||||
|
{ value: 'female', label: 'Perempuan' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function AdministratorIndex({ administrators, filters }: Props) {
|
||||||
const [deleting, setDeleting] = useState<Administrator | null>(null);
|
const [deleting, setDeleting] = useState<Administrator | null>(null);
|
||||||
const [resetting, setResetting] = useState<Administrator | null>(null);
|
const [resetting, setResetting] = useState<Administrator | null>(null);
|
||||||
|
|
||||||
@ -44,9 +60,11 @@ export default function AdministratorIndex({ administrators }: Props) {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => administratorsIndex.url(),
|
route: () => administratorsIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -64,9 +82,13 @@ export default function AdministratorIndex({ administrators }: Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.patch(reset_password.url(resetting.id), {}, {
|
router.patch(
|
||||||
onSuccess: () => setResetting(null),
|
reset_password.url(resetting.id),
|
||||||
});
|
{},
|
||||||
|
{
|
||||||
|
onSuccess: () => setResetting(null),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = createAdministratorColumns({
|
const columns = createAdministratorColumns({
|
||||||
@ -105,6 +127,13 @@ export default function AdministratorIndex({ administrators }: Props) {
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { ConfirmDialog } from '@/components/confirm-dialog';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
@ -18,6 +20,8 @@ import {
|
|||||||
import type { Lecturer } from './columns';
|
import type { Lecturer } from './columns';
|
||||||
import { createLecturerColumns } from './columns';
|
import { createLecturerColumns } from './columns';
|
||||||
|
|
||||||
|
type Department = { id: number; name: string };
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
lecturers: {
|
lecturers: {
|
||||||
data: Lecturer[];
|
data: Lecturer[];
|
||||||
@ -26,12 +30,40 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
departments: Department[];
|
||||||
|
filters: {
|
||||||
|
gender?: string;
|
||||||
|
department_id?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LecturerIndex({ lecturers }: Props) {
|
export default function LecturerIndex({
|
||||||
|
lecturers,
|
||||||
|
departments,
|
||||||
|
filters,
|
||||||
|
}: Props) {
|
||||||
const [deleting, setDeleting] = useState<Lecturer | null>(null);
|
const [deleting, setDeleting] = useState<Lecturer | null>(null);
|
||||||
const [resetting, setResetting] = useState<Lecturer | null>(null);
|
const [resetting, setResetting] = useState<Lecturer | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'gender',
|
||||||
|
label: 'Jenis Kelamin',
|
||||||
|
options: [
|
||||||
|
{ value: 'male', label: 'Laki-laki' },
|
||||||
|
{ value: 'female', label: 'Perempuan' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'department_id',
|
||||||
|
label: 'Jurusan',
|
||||||
|
options: departments.map((department) => ({
|
||||||
|
value: String(department.id),
|
||||||
|
label: department.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: lecturers.current_page,
|
current_page: lecturers.current_page,
|
||||||
last_page: lecturers.last_page,
|
last_page: lecturers.last_page,
|
||||||
@ -44,9 +76,11 @@ export default function LecturerIndex({ lecturers }: Props) {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => lecturersIndex.url(),
|
route: () => lecturersIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -64,9 +98,13 @@ export default function LecturerIndex({ lecturers }: Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.patch(reset_password.url(resetting.id), {}, {
|
router.patch(
|
||||||
onSuccess: () => setResetting(null),
|
reset_password.url(resetting.id),
|
||||||
});
|
{},
|
||||||
|
{
|
||||||
|
onSuccess: () => setResetting(null),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = createLecturerColumns({
|
const columns = createLecturerColumns({
|
||||||
@ -105,6 +143,13 @@ export default function LecturerIndex({ lecturers }: Props) {
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import { ConfirmDialog } from '@/components/confirm-dialog';
|
|||||||
import type { PaginationState } from '@/components/data-table';
|
import type { PaginationState } from '@/components/data-table';
|
||||||
import { DataTable } from '@/components/data-table';
|
import { DataTable } from '@/components/data-table';
|
||||||
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
|
||||||
|
import type { FilterField } from '@/components/filter-dialog';
|
||||||
|
import { FilterDialog } from '@/components/filter-dialog';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useServerTable } from '@/hooks/use-server-table';
|
import { useServerTable } from '@/hooks/use-server-table';
|
||||||
@ -15,8 +17,11 @@ import {
|
|||||||
reset_password,
|
reset_password,
|
||||||
index as studentsIndex,
|
index as studentsIndex,
|
||||||
} from '@/routes/admin/users/students';
|
} from '@/routes/admin/users/students';
|
||||||
import { createStudentColumns } from './columns';
|
import { createStudentColumns } from './columns';
|
||||||
import type {Student} from './columns';
|
import type { Student } from './columns';
|
||||||
|
|
||||||
|
type Department = { id: number; name: string };
|
||||||
|
type StatusOption = { value: string; label: string };
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
students: {
|
students: {
|
||||||
@ -26,12 +31,48 @@ type Props = {
|
|||||||
per_page: number;
|
per_page: number;
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
departments: Department[];
|
||||||
|
statuses: StatusOption[];
|
||||||
|
filters: {
|
||||||
|
gender?: string;
|
||||||
|
department_id?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function StudentIndex({ students }: Props) {
|
export default function StudentIndex({
|
||||||
|
students,
|
||||||
|
departments,
|
||||||
|
statuses,
|
||||||
|
filters,
|
||||||
|
}: Props) {
|
||||||
const [deleting, setDeleting] = useState<Student | null>(null);
|
const [deleting, setDeleting] = useState<Student | null>(null);
|
||||||
const [resetting, setResetting] = useState<Student | null>(null);
|
const [resetting, setResetting] = useState<Student | null>(null);
|
||||||
|
|
||||||
|
const filterFields: FilterField[] = [
|
||||||
|
{
|
||||||
|
key: 'gender',
|
||||||
|
label: 'Jenis Kelamin',
|
||||||
|
options: [
|
||||||
|
{ value: 'male', label: 'Laki-laki' },
|
||||||
|
{ value: 'female', label: 'Perempuan' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'department_id',
|
||||||
|
label: 'Jurusan',
|
||||||
|
options: departments.map((department) => ({
|
||||||
|
value: String(department.id),
|
||||||
|
label: department.name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'status',
|
||||||
|
label: 'Status',
|
||||||
|
options: statuses,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const pagination: PaginationState = {
|
const pagination: PaginationState = {
|
||||||
current_page: students.current_page,
|
current_page: students.current_page,
|
||||||
last_page: students.last_page,
|
last_page: students.last_page,
|
||||||
@ -44,9 +85,11 @@ export default function StudentIndex({ students }: Props) {
|
|||||||
handlePageChange,
|
handlePageChange,
|
||||||
handlePerPageChange,
|
handlePerPageChange,
|
||||||
handleSearchChange,
|
handleSearchChange,
|
||||||
|
applyFilters,
|
||||||
} = useServerTable({
|
} = useServerTable({
|
||||||
route: () => studentsIndex.url(),
|
route: () => studentsIndex.url(),
|
||||||
pagination,
|
pagination,
|
||||||
|
filters,
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
@ -64,9 +107,13 @@ export default function StudentIndex({ students }: Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.patch(reset_password.url(resetting.id), {}, {
|
router.patch(
|
||||||
onSuccess: () => setResetting(null),
|
reset_password.url(resetting.id),
|
||||||
});
|
{},
|
||||||
|
{
|
||||||
|
onSuccess: () => setResetting(null),
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = createStudentColumns({
|
const columns = createStudentColumns({
|
||||||
@ -105,6 +152,13 @@ export default function StudentIndex({ students }: Props) {
|
|||||||
onPerPageChange={handlePerPageChange}
|
onPerPageChange={handlePerPageChange}
|
||||||
onSearchChange={handleSearchChange}
|
onSearchChange={handleSearchChange}
|
||||||
searchValue={search}
|
searchValue={search}
|
||||||
|
toolbar={
|
||||||
|
<FilterDialog
|
||||||
|
fields={filterFields}
|
||||||
|
activeFilters={filters}
|
||||||
|
onApply={applyFilters}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteConfirmDialog
|
<DeleteConfirmDialog
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user