Merge pull request 'feat: add active status management for academic terms, users, and students with new UpdateStatusRequest' (#52) from feat/add-status-management-academic-terms into dev

Reviewed-on: #52
This commit is contained in:
pangestu 2026-08-30 12:18:37 +08:00
commit b76d82c2f0
13 changed files with 72 additions and 27 deletions

View File

@ -4,6 +4,7 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Master\AcademicTermRequest;
use App\Http\Requests\Admin\UpdateStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\AcademicTerm;
use App\Services\Admin\Master\AcademicTermService;
@ -53,4 +54,11 @@ public function destroy(AcademicTerm $academicTerm): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Periode akademik berhasil dihapus.'])->back();
}
public function updateStatus(UpdateStatusRequest $request, AcademicTerm $academicTerm): RedirectResponse
{
$this->service->updateActiveStatus($academicTerm, $request->boolean('is_active'));
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status periode akademik berhasil diperbarui.'])->back();
}
}

View File

@ -3,8 +3,8 @@
namespace App\Http\Controllers\Admin\Users;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UpdateStatusRequest;
use App\Http\Requests\Admin\Users\AdministratorRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Users\AdministratorService;
@ -79,7 +79,7 @@ public function resetPassword(User $user): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
public function updateUserStatus(UpdateStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));

View File

@ -4,8 +4,8 @@
use App\Exports\LecturersExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UpdateStatusRequest;
use App\Http\Requests\Admin\Users\LecturerRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Master\DepartmentService;
@ -85,7 +85,7 @@ public function resetPassword(User $user): RedirectResponse
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Kata sandi berhasil direset.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
public function updateUserStatus(UpdateStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));

View File

@ -5,9 +5,9 @@
use App\Enums\StudentStatus;
use App\Exports\StudentsExport;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\UpdateStatusRequest;
use App\Http\Requests\Admin\Users\Student\StudentRequest;
use App\Http\Requests\Admin\Users\Student\StudentStatusRequest;
use App\Http\Requests\Admin\Users\UpdateUserStatusRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\User;
use App\Services\Admin\Master\DepartmentService;
@ -100,7 +100,7 @@ public function updateStatus(StudentStatusRequest $request, User $user): Redirec
return Inertia::flash('toast', ['type' => 'success', 'message' => 'Status mahasiswa berhasil diperbarui.'])->back();
}
public function updateUserStatus(UpdateUserStatusRequest $request, User $user): RedirectResponse
public function updateUserStatus(UpdateStatusRequest $request, User $user): RedirectResponse
{
$this->service->updateUserStatus($user, $request->boolean('is_active'));

View File

@ -1,10 +1,10 @@
<?php
namespace App\Http\Requests\Admin\Users;
namespace App\Http\Requests\Admin;
use Illuminate\Foundation\Http\FormRequest;
class UpdateUserStatusRequest extends FormRequest
class UpdateStatusRequest extends FormRequest
{
public function authorize(): bool
{

View File

@ -5,6 +5,7 @@
use App\Models\AcademicTerm;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\DB;
class AcademicTermService
{
@ -45,4 +46,15 @@ public function delete(AcademicTerm $academicTerm): bool
{
return $academicTerm->delete();
}
public function updateActiveStatus(AcademicTerm $academicTerm, bool $isActive): void
{
DB::transaction(function () use ($academicTerm, $isActive) {
if ($isActive) {
AcademicTerm::where('id', '!=', $academicTerm->id)->update(['is_active' => false]);
}
$academicTerm->update(['is_active' => $isActive]);
});
}
}

View File

@ -1,11 +1,14 @@
import { Switch } from '@/components/ui/switch';
type UserActiveSwitchProps = {
type ActiveStatusSwitchProps = {
isActive: boolean;
onChange: (isActive: boolean) => void;
};
export function UserActiveSwitch({ isActive, onChange }: UserActiveSwitchProps) {
export function ActiveStatusSwitch({
isActive,
onChange,
}: ActiveStatusSwitchProps) {
return (
<div className="flex items-center gap-2">
<Switch checked={isActive} onCheckedChange={onChange} />

View File

@ -1,21 +1,23 @@
import { RowActions } from '@/components/row-actions';
import { Badge } from '@/components/ui/badge';
import type { AcademicTerm } from '@/types/academic-term';
import { SemesterLabels, type Semester } from '@/types/academic-term';
import type { ColumnDef } from '@tanstack/react-table';
import { Pencil, Trash2 } from 'lucide-react';
import { ActiveStatusSwitch } from '@/components/active-status-switch';
import { RowActions } from '@/components/row-actions';
import { SemesterLabels } from '@/types/academic-term';
import type { AcademicTerm } from '@/types/academic-term';
import type { Semester } from '@/types/academic-term';
export type { AcademicTerm } from '@/types/academic-term';
type CreateColumnsParams = {
handleEdit: (academicTerm: AcademicTerm) => void;
handleDeleteClick: (academicTerm: AcademicTerm) => void;
handleStatusChange: (academicTerm: AcademicTerm, isActive: boolean) => void;
};
export function createAcademicTermColumns(
params: CreateColumnsParams,
): ColumnDef<AcademicTerm>[] {
const { handleEdit, handleDeleteClick } = params;
const { handleEdit, handleDeleteClick, handleStatusChange } = params;
return [
{
@ -32,6 +34,7 @@ export function createAcademicTermColumns(
header: () => <span>Semester</span>,
cell: ({ row }) => {
const semester = row.getValue('semester') as Semester;
return <span>{SemesterLabels[semester]}</span>;
},
},
@ -39,14 +42,18 @@ export function createAcademicTermColumns(
accessorKey: 'formatted_start_date',
header: () => <span>Tanggal Mulai</span>,
cell: ({ row }) => (
<span>{row.getValue('formatted_start_date') as string | null}</span>
<span>
{row.getValue('formatted_start_date') as string | null}
</span>
),
},
{
accessorKey: 'formatted_end_date',
header: () => <span>Tanggal Selesai</span>,
cell: ({ row }) => (
<span>{row.getValue('formatted_end_date') as string | null}</span>
<span>
{row.getValue('formatted_end_date') as string | null}
</span>
),
},
{
@ -57,12 +64,16 @@ export function createAcademicTermColumns(
headerClassName: 'w-[100px] text-center',
},
cell: ({ row }) => {
const isActive = row.getValue('is_active') as boolean;
const academicTerm = row.original;
return (
<div className="flex justify-center">
<Badge variant={isActive ? 'default' : 'secondary'}>
{isActive ? 'Aktif' : 'Nonaktif'}
</Badge>
<ActiveStatusSwitch
isActive={academicTerm.is_active}
onChange={(isActive) =>
handleStatusChange(academicTerm, isActive)
}
/>
</div>
);
},

View File

@ -21,6 +21,7 @@ import {
destroy,
store,
update,
update_status,
} from '@/routes/admin/master/academic-terms';
import type { AcademicTerm } from '@/types/academic-term';
import { Semester, SemesterLabels } from '@/types/academic-term';
@ -98,9 +99,18 @@ export default function AcademicTermIndex({
});
}
function handleStatusChange(academicTerm: AcademicTerm, isActive: boolean) {
router.patch(
update_status.url(academicTerm.id),
{ is_active: isActive },
{ preserveScroll: true },
);
}
const columns = createAcademicTermColumns({
handleEdit: (academicTerm) => setEditing(academicTerm),
handleDeleteClick: (academicTerm) => setDeleting(academicTerm),
handleStatusChange,
});
return (

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/react-table';
import { Key, Pencil, Trash2 } from 'lucide-react';
import { ActiveStatusSwitch } from '@/components/active-status-switch';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Administrator = {
id: number;
@ -69,7 +69,7 @@ export function createAdministratorColumns(
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
<ActiveStatusSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)

View File

@ -1,8 +1,8 @@
import type { ColumnDef } from '@tanstack/react-table';
import { Key, Pencil, Trash2 } from 'lucide-react';
import { ActiveStatusSwitch } from '@/components/active-status-switch';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Lecturer = {
id: number;
@ -79,7 +79,7 @@ export function createLecturerColumns(
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
<ActiveStatusSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)

View File

@ -1,9 +1,9 @@
import type { ColumnDef } from '@tanstack/react-table';
import { Key, Pencil, Trash2 } from 'lucide-react';
import { ActiveStatusSwitch } from '@/components/active-status-switch';
import { GenderBadge } from '@/components/gender-badge';
import { RowActions } from '@/components/row-actions';
import { StudentStatusBadge } from '@/components/student-status-badge';
import { UserActiveSwitch } from '@/components/user-active-switch';
export type Student = {
id: number;
@ -106,7 +106,7 @@ export function createStudentColumns(
accessorKey: 'is_active',
header: () => <span>Status Akun</span>,
cell: ({ row }) => (
<UserActiveSwitch
<ActiveStatusSwitch
isActive={row.original.is_active}
onChange={(isActive) =>
handleUserStatusChange(row.original, isActive)

View File

@ -27,6 +27,7 @@
Route::middleware(['auth', 'verified'])->group(function () {
Route::prefix('admin/master')->name('admin.master.')->group(function () {
Route::resource('academic-terms', AcademicTermController::class)->except(['create', 'edit', 'show']);
Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status');
Route::resource('departments', DepartmentController::class)->except(['create', 'edit', 'show']);
});