feat: rename 'name' to 'academic_year' in AcademicTerm and related components for consistency

This commit is contained in:
Yoga Pangestu 2026-09-02 13:20:18 +07:00
parent 6c55fb5f36
commit ded7217dbc
17 changed files with 67 additions and 45 deletions

View File

@ -16,13 +16,13 @@ public function authorize(): bool
public function rules(): array public function rules(): array
{ {
return [ return [
'name' => [ 'academic_year' => [
'required', 'required',
'string', 'string',
'max:20', 'max:20',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
if (! preg_match('/^(\d{4})\/(\d{4})$/', $value, $matches)) { if (! preg_match('/^(\d{4})\/(\d{4})$/', $value, $matches)) {
$fail('Format name harus YYYY/YYYY, contoh: 2025/2026.'); $fail('Format tahun ajaran harus YYYY/YYYY, contoh: 2025/2026.');
return; return;
} }
@ -40,16 +40,16 @@ function ($attribute, $value, $fail) {
'string', 'string',
Rule::in(Semester::values()), Rule::in(Semester::values()),
Rule::unique('academic_terms')->where(function ($query) { Rule::unique('academic_terms')->where(function ($query) {
return $query->where('name', $this->input('name')); return $query->where('academic_year', $this->input('academic_year'));
})->ignore($this->route('academic_term')), })->ignore($this->route('academic_term')),
], ],
'start_date' => [ 'start_date' => [
'required', 'required',
'date', 'date',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
$name = $this->input('name'); $academicYear = $this->input('academic_year');
$semester = $this->input('semester'); $semester = $this->input('semester');
if ($name && preg_match('/^(\d{4})\/(\d{4})$/', $name, $matches)) { if ($academicYear && preg_match('/^(\d{4})\/(\d{4})$/', $academicYear, $matches)) {
$startYear = (int) $matches[1]; $startYear = (int) $matches[1];
$endYear = (int) $matches[2]; $endYear = (int) $matches[2];
$dateYear = (int) date('Y', strtotime($value)); $dateYear = (int) date('Y', strtotime($value));
@ -65,9 +65,9 @@ function ($attribute, $value, $fail) {
'date', 'date',
'after_or_equal:start_date', 'after_or_equal:start_date',
function ($attribute, $value, $fail) { function ($attribute, $value, $fail) {
$name = $this->input('name'); $academicYear = $this->input('academic_year');
$semester = $this->input('semester'); $semester = $this->input('semester');
if ($name && preg_match('/^(\d{4})\/(\d{4})$/', $name, $matches)) { if ($academicYear && preg_match('/^(\d{4})\/(\d{4})$/', $academicYear, $matches)) {
$startYear = (int) $matches[1]; $startYear = (int) $matches[1];
$endYear = (int) $matches[2]; $endYear = (int) $matches[2];
$dateYear = (int) date('Y', strtotime($value)); $dateYear = (int) date('Y', strtotime($value));

View File

@ -33,7 +33,7 @@ public function paginated(
->select(['id', 'student_id', 'academic_term_id', 'amount_due', 'due_date']) ->select(['id', 'student_id', 'academic_term_id', 'amount_due', 'due_date'])
->withSum('payments as paid_total', 'amount_paid') ->withSum('payments as paid_total', 'amount_paid')
->withCount('payments') ->withCount('payments')
->with(['student.user.profile', 'student.department', 'academicTerm:id,name,semester,start_date,end_date']) ->with(['student.user.profile', 'student.department', 'academicTerm:id,academic_year,semester,start_date,end_date'])
->when($status, fn ($q) => match ($status) { ->when($status, fn ($q) => match ($status) {
'paid' => $q->havingRaw('COALESCE(paid_total, 0) >= amount_due'), 'paid' => $q->havingRaw('COALESCE(paid_total, 0) >= amount_due'),
'partial' => $q->havingRaw('COALESCE(paid_total, 0) > 0 AND COALESCE(paid_total, 0) < amount_due'), 'partial' => $q->havingRaw('COALESCE(paid_total, 0) > 0 AND COALESCE(paid_total, 0) < amount_due'),

View File

@ -20,7 +20,7 @@ public function paginated(int $perPage = 25, string $search = '', ?int $academic
return CourseClass::query() return CourseClass::query()
->select(['id', 'course_id', 'lecturer_id', 'academic_term_id', 'method']) ->select(['id', 'course_id', 'lecturer_id', 'academic_term_id', 'method'])
->withCount('enrollments') ->withCount('enrollments')
->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,academic_year,semester,start_date,end_date'])
->when($search, fn ($q) => $q->whereHas('course', fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))) ->when($search, fn ($q) => $q->whereHas('course', fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")))
->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId)) ->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
->when($method, fn ($q) => $q->where('method', $method)) ->when($method, fn ($q) => $q->where('method', $method))

View File

@ -113,7 +113,7 @@ public function withDetails(CourseRegistrationSubmission $submission): CourseReg
'student.user.profile', 'student.user.profile',
'student.department.currentLeader.lecturer.user.profile', 'student.department.currentLeader.lecturer.user.profile',
'student.academicAdvisor.user.profile', 'student.academicAdvisor.user.profile',
'academicTerm:id,name,semester,start_date,end_date', 'academicTerm:id,academic_year,semester,start_date,end_date',
'reviewer.profile', 'reviewer.profile',
'courseRegistrations.courseClass.course', 'courseRegistrations.courseClass.course',
'logs' => fn ($q) => $q->oldest(), 'logs' => fn ($q) => $q->oldest(),

View File

@ -11,7 +11,7 @@ class AcademicTermService
{ {
public function getAllForSelect(): Collection public function getAllForSelect(): Collection
{ {
return AcademicTerm::select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])->latest()->get(); return AcademicTerm::select(['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])->latest()->get();
} }
/** /**
@ -27,8 +27,8 @@ public function getActive(): ?AcademicTerm
public function paginated(int $perPage = 25, string $search = '', ?string $semester = null, ?bool $isActive = null): LengthAwarePaginator public function paginated(int $perPage = 25, string $search = '', ?string $semester = null, ?bool $isActive = null): LengthAwarePaginator
{ {
return AcademicTerm::query() return AcademicTerm::query()
->select(['id', 'name', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters']) ->select(['id', 'academic_year', 'semester', 'start_date', 'end_date', 'is_active', 'open_semesters'])
->when($search, fn($q) => $q->where('name', 'like', "%{$search}%")) ->when($search, fn($q) => $q->where('academic_year', 'like', "%{$search}%"))
->when($semester, fn($q) => $q->where('semester', $semester)) ->when($semester, fn($q) => $q->where('semester', $semester))
->when($isActive !== null, fn($q) => $q->where('is_active', $isActive)) ->when($isActive !== null, fn($q) => $q->where('is_active', $isActive))
->latest() ->latest()
@ -45,7 +45,7 @@ public function create(array $data): AcademicTerm
public function update(AcademicTerm $academicTerm, array $data): AcademicTerm public function update(AcademicTerm $academicTerm, array $data): AcademicTerm
{ {
$academicTerm->name = $data['name']; $academicTerm->academic_year = $data['academic_year'];
$academicTerm->semester = $data['semester']; $academicTerm->semester = $data['semester'];
$academicTerm->start_date = $data['start_date']; $academicTerm->start_date = $data['start_date'];
$academicTerm->end_date = $data['end_date']; $academicTerm->end_date = $data['end_date'];

View File

@ -11,7 +11,7 @@ public function up(): void
{ {
Schema::create('academic_terms', function (Blueprint $table) { Schema::create('academic_terms', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('name', 20); $table->string('academic_year', 20);
$table->enum('semester', Semester::values()); $table->enum('semester', Semester::values());
$table->date('start_date'); $table->date('start_date');
$table->date('end_date'); $table->date('end_date');

View File

@ -12,7 +12,7 @@ public function run(): void
{ {
AcademicTerm::insert([ AcademicTerm::insert([
[ [
'name' => '2026/2027', 'academic_year' => '2026/2027',
'semester' => Semester::odd->value, 'semester' => Semester::odd->value,
'start_date' => '2026-08-01', 'start_date' => '2026-08-01',
'end_date' => '2027-01-15', 'end_date' => '2027-01-15',
@ -21,7 +21,7 @@ public function run(): void
'updated_at' => now(), 'updated_at' => now(),
], ],
[ [
'name' => '2026/2027', 'academic_year' => '2026/2027',
'semester' => Semester::even->value, 'semester' => Semester::even->value,
'start_date' => '2027-02-01', 'start_date' => '2027-02-01',
'end_date' => '2027-07-15', 'end_date' => '2027-07-15',

View File

@ -51,7 +51,7 @@ export function createTuitionInvoiceColumns(
}, },
}, },
{ {
accessorKey: 'academic_term.name', accessorKey: 'academic_term.academic_year',
header: () => <span>Periode</span>, header: () => <span>Periode</span>,
cell: ({ row }) => { cell: ({ row }) => {
const term = row.original.academic_term; const term = row.original.academic_term;

View File

@ -69,7 +69,11 @@ import {
} from '@/types/tuition-payment'; } from '@/types/tuition-payment';
import { createTuitionInvoiceColumns } from './columns'; import { createTuitionInvoiceColumns } from './columns';
type AcademicTermOption = { id: number; name: string; semester: string }; type AcademicTermOption = {
id: number;
academic_year: string;
semester: string;
};
type InvoiceSummary = { type InvoiceSummary = {
unpaid_students: number; unpaid_students: number;

View File

@ -45,7 +45,7 @@ export function createCourseClassColumns(
row.original.lecturer?.user?.profile?.full_name ?? '-', row.original.lecturer?.user?.profile?.full_name ?? '-',
}, },
{ {
accessorKey: 'academic_term.name', accessorKey: 'academic_term.academic_year',
header: () => <span>Periode</span>, header: () => <span>Periode</span>,
cell: ({ row }) => { cell: ({ row }) => {
const term = row.original.academic_term; const term = row.original.academic_term;

View File

@ -46,7 +46,11 @@ type Lecturer = {
departments: { id: number; name: string }[]; departments: { id: number; name: string }[];
user: { profile: { full_name: string } | null } | null; user: { profile: { full_name: string } | null } | null;
}; };
type AcademicTerm = { id: number; name: string; semester: string }; type AcademicTerm = {
id: number;
academic_year: string;
semester: string;
};
type Props = { type Props = {
courseClasses: { courseClasses: {

View File

@ -32,11 +32,11 @@ export function createAcademicTermColumns(
const columns: ColumnDef<AcademicTerm>[] = [ const columns: ColumnDef<AcademicTerm>[] = [
{ {
accessorKey: 'name', accessorKey: 'academic_year',
header: () => <span>Nama</span>, header: () => <span>Tahun Ajaran</span>,
cell: ({ row }) => ( cell: ({ row }) => (
<span className="font-medium"> <span className="font-medium">
{row.getValue('name') as string} {row.getValue('academic_year') as string}
</span> </span>
), ),
}, },

View File

@ -182,7 +182,7 @@ export default function AcademicTermIndex({
<DataTable <DataTable
columns={columns} columns={columns}
data={academicTerms.data} data={academicTerms.data}
searchKey="name" searchKey="academic_year"
pagination={pagination} pagination={pagination}
onPageChange={handlePageChange} onPageChange={handlePageChange}
onPerPageChange={handlePerPageChange} onPerPageChange={handlePerPageChange}
@ -206,7 +206,7 @@ export default function AcademicTermIndex({
}} }}
title="Hapus Periode Akademik" title="Hapus Periode Akademik"
description={(academicTerm) => description={(academicTerm) =>
`Apakah Anda yakin ingin menghapus periode akademik "${academicTerm.name}"? Tindakan ini tidak dapat dibatalkan.` `Apakah Anda yakin ingin menghapus periode akademik "${academicTerm.academic_year}"? Tindakan ini tidak dapat dibatalkan.`
} }
onConfirm={handleDelete} onConfirm={handleDelete}
/> />
@ -241,15 +241,16 @@ function CreateForm({
{({ errors }) => ( {({ errors }) => (
<div className="grid gap-4"> <div className="grid gap-4">
<div className="grid gap-2"> <div className="grid gap-2">
<Label htmlFor="name"> <Label htmlFor="academic_year">
Nama <span className="text-destructive">*</span> Tahun Ajaran{' '}
<span className="text-destructive">*</span>
</Label> </Label>
<Input <Input
id="name" id="academic_year"
name="name" name="academic_year"
placeholder="Masukkan nama periode akademik" placeholder="Masukkan tahun ajaran, contoh: 2025/2026"
/> />
<InputError message={errors.name} /> <InputError message={errors.academic_year} />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label> <Label>
@ -369,16 +370,17 @@ function EditForm({
editing && ( editing && (
<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-academic_year">
Nama <span className="text-destructive">*</span> Tahun Ajaran{' '}
<span className="text-destructive">*</span>
</Label> </Label>
<Input <Input
id="edit-name" id="edit-academic_year"
name="name" name="academic_year"
placeholder="Masukkan nama periode akademik" placeholder="Masukkan tahun ajaran, contoh: 2025/2026"
defaultValue={editing.name} defaultValue={editing.academic_year}
/> />
<InputError message={errors.name} /> <InputError message={errors.academic_year} />
</div> </div>
<div className="grid gap-2"> <div className="grid gap-2">
<Label> <Label>

View File

@ -10,7 +10,7 @@ export const SemesterLabels: Record<Semester, string> = {
export type AcademicTerm = { export type AcademicTerm = {
id: number; id: number;
name: string; academic_year: string;
semester: Semester; semester: Semester;
start_date: string; start_date: string;
formatted_start_date: string; formatted_start_date: string;
@ -23,10 +23,10 @@ export type AcademicTerm = {
}; };
export function formatAcademicTermLabel(term: { export function formatAcademicTermLabel(term: {
name: string; academic_year: string;
semester: string; semester: string;
}): string { }): string {
const label = SemesterLabels[term.semester as Semester]; const label = SemesterLabels[term.semester as Semester];
return label ? `${term.name} (${label})` : term.name; return label ? `${term.academic_year} (${label})` : term.academic_year;
} }

View File

@ -25,7 +25,11 @@ export type CourseClass = {
user: { profile: { full_name: string } | null } | null; user: { profile: { full_name: string } | null } | null;
} | null; } | null;
academic_term_id: number; academic_term_id: number;
academic_term: { id: number; name: string; semester: string } | null; academic_term: {
id: number;
academic_year: string;
semester: string;
} | null;
method: ClassMethod | null; method: ClassMethod | null;
enrollments_count: number; enrollments_count: number;
created_at: string; created_at: string;

View File

@ -82,7 +82,11 @@ export type CourseRegistrationSubmission = {
advisor_signature_url: string | null; advisor_signature_url: string | null;
has_registrations: boolean; has_registrations: boolean;
student: CourseRegistrationSubmissionStudent | null; student: CourseRegistrationSubmissionStudent | null;
academic_term: { id: number; name: string; semester: string } | null; academic_term: {
id: number;
academic_year: string;
semester: string;
} | null;
course_registrations: CourseRegistrationSubmissionEntry[]; course_registrations: CourseRegistrationSubmissionEntry[];
logs: CourseRegistrationSubmissionLog[]; logs: CourseRegistrationSubmissionLog[];
}; };

View File

@ -11,7 +11,11 @@ export type TuitionInvoice = {
student_id: number; student_id: number;
student: TuitionInvoiceStudent | null; student: TuitionInvoiceStudent | null;
academic_term_id: number; academic_term_id: number;
academic_term: { id: number; name: string; semester: string } | null; academic_term: {
id: number;
academic_year: string;
semester: string;
} | null;
amount_due: string; amount_due: string;
due_date: string | null; due_date: string | null;
paid_total: string | null; paid_total: string | null;