feat: add open_semesters field to AcademicTerm model and request, update related services and migration

This commit is contained in:
Yoga Pangestu 2026-09-01 14:27:02 +07:00
parent 93ddee8fd6
commit 10c3e42228
6 changed files with 65 additions and 6 deletions

View File

@ -79,6 +79,8 @@ function ($attribute, $value, $fail) {
},
],
'is_active' => ['boolean'],
'open_semesters' => ['nullable', 'array'],
'open_semesters.*' => ['integer', 'between:1,8'],
];
}
}

View File

@ -28,6 +28,7 @@ protected function casts(): array
'start_date' => 'date',
'end_date' => 'date',
'is_active' => 'boolean',
'open_semesters' => 'array',
];
}

View File

@ -11,7 +11,7 @@ class AcademicTermService
{
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', 'open_semesters'])->latest()->get();
}
/**
@ -27,17 +27,20 @@ public function getActive(): ?AcademicTerm
public function paginated(int $perPage = 25, string $search = '', ?string $semester = null, ?bool $isActive = null): LengthAwarePaginator
{
return AcademicTerm::query()
->select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active'])
->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))
->select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])
->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))
->latest()
->paginate($perPage);
}
public function create(array $data): AcademicTerm
{
return AcademicTerm::create($data);
return AcademicTerm::create([
...$data,
'open_semesters' => $this->normalizeOpenSemesters($data['open_semesters'] ?? []),
]);
}
public function update(AcademicTerm $academicTerm, array $data): AcademicTerm
@ -47,6 +50,7 @@ public function update(AcademicTerm $academicTerm, array $data): AcademicTerm
$academicTerm->start_date = $data['start_date'];
$academicTerm->end_date = $data['end_date'];
$academicTerm->is_active = $data['is_active'];
$academicTerm->open_semesters = $this->normalizeOpenSemesters($data['open_semesters'] ?? []);
$academicTerm->update();
return $academicTerm;
@ -67,4 +71,13 @@ public function updateActiveStatus(AcademicTerm $academicTerm, bool $isActive):
$academicTerm->update(['is_active' => $isActive]);
});
}
/**
* @param array<int, mixed> $openSemesters
* @return array<int, int>
*/
private function normalizeOpenSemesters(array $openSemesters): array
{
return array_values(array_unique(array_map('intval', $openSemesters)));
}
}

View File

@ -16,6 +16,7 @@ public function up(): void
$table->date('start_date');
$table->date('end_date');
$table->boolean('is_active')->default(false);
$table->json('open_semesters')->nullable();
$table->timestamps();
});
}

View File

@ -329,6 +329,7 @@ function CreateForm({
<Checkbox id="is_active" name="is_active" value="1" />
<Label htmlFor="is_active">Aktif</Label>
</div>
<OpenSemestersField idPrefix="create" />
</div>
)}
</FormDialog>
@ -467,9 +468,49 @@ function EditForm({
/>
<Label htmlFor="edit-is_active">Aktif</Label>
</div>
<OpenSemestersField
idPrefix="edit"
defaultValue={editing.open_semesters ?? []}
/>
</div>
)
}
</FormDialog>
);
}
function OpenSemestersField({
idPrefix,
defaultValue = [],
}: {
idPrefix: string;
defaultValue?: number[];
}) {
return (
<div className="grid gap-2">
<Label>Semester Dibuka untuk KRS</Label>
<p className="text-xs text-muted-foreground">
Kosongkan untuk menutup semua semester. Centang semester
tertentu untuk membuka pendaftaran KRS pada semester itu.
</p>
<div className="grid grid-cols-4 gap-2">
{[1, 2, 3, 4, 5, 6, 7, 8].map((semester) => (
<div key={semester} className="flex items-center gap-2">
<Checkbox
id={`${idPrefix}-open-semester-${semester}`}
name="open_semesters[]"
value={semester}
defaultChecked={defaultValue.includes(semester)}
/>
<Label
htmlFor={`${idPrefix}-open-semester-${semester}`}
className="font-normal"
>
Semester {semester}
</Label>
</div>
))}
</div>
</div>
);
}

View File

@ -17,6 +17,7 @@ export type AcademicTerm = {
end_date: string;
formatted_end_date: string;
is_active: boolean;
open_semesters: number[] | null;
created_at: string;
updated_at: string;
};