feat: implement bulk creation of course classes and enhance validation for lecturer assignment
This commit is contained in:
parent
584056e379
commit
03c3fc61af
@ -41,9 +41,14 @@ public function index(PaginatedRequest $request): Response
|
||||
|
||||
public function store(CourseClassRequest $request): RedirectResponse
|
||||
{
|
||||
$this->service->create($request->validated());
|
||||
$created = $this->service->createMany($request->validated());
|
||||
|
||||
Inertia::flash('toast', ['type' => 'success', 'message' => 'Kelas mata kuliah berhasil ditambahkan.']);
|
||||
Inertia::flash('toast', [
|
||||
'type' => 'success',
|
||||
'message' => $created->count() > 1
|
||||
? "{$created->count()} kelas mata kuliah berhasil ditambahkan."
|
||||
: 'Kelas mata kuliah berhasil ditambahkan.',
|
||||
]);
|
||||
|
||||
return to_route('admin.manage.course-classes.index');
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
use App\Enums\ClassMethod;
|
||||
use App\Models\Course;
|
||||
use App\Models\Lecturer;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
@ -16,10 +17,51 @@ public function authorize(): bool
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$academicTermId = $this->input('academic_term_id');
|
||||
|
||||
if ($this->isMethod('post')) {
|
||||
return [
|
||||
'course_ids' => ['required', 'array', 'min:1'],
|
||||
'course_ids.*' => [
|
||||
'integer',
|
||||
Rule::exists('courses', 'id'),
|
||||
Rule::unique('course_classes', 'course_id')
|
||||
->where('academic_term_id', $academicTermId)
|
||||
->whereNull('deleted_at'),
|
||||
],
|
||||
'lecturer_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
function ($attribute, $value, $fail) {
|
||||
$courseIds = (array) $this->input('course_ids', []);
|
||||
$departmentIds = Course::whereIn('id', $courseIds)->pluck('department_id')->unique();
|
||||
$lecturer = Lecturer::find($value);
|
||||
$coveredDepartmentIds = $lecturer
|
||||
? $lecturer->departments()->pluck('departments.id')
|
||||
: collect();
|
||||
|
||||
if ($departmentIds->diff($coveredDepartmentIds)->isNotEmpty()) {
|
||||
$fail('Dosen tidak terdaftar di jurusan seluruh mata kuliah yang dipilih.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'academic_term_id' => ['required', 'integer', Rule::exists('academic_terms', 'id')],
|
||||
'method' => ['nullable', 'string', Rule::in(ClassMethod::values())],
|
||||
];
|
||||
}
|
||||
|
||||
$departmentId = Course::find($this->input('course_id'))?->department_id;
|
||||
|
||||
return [
|
||||
'course_id' => ['required', 'integer', Rule::exists('courses', 'id')],
|
||||
'course_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
Rule::exists('courses', 'id'),
|
||||
Rule::unique('course_classes', 'course_id')
|
||||
->where('academic_term_id', $academicTermId)
|
||||
->whereNull('deleted_at')
|
||||
->ignore($this->route('course_class')?->id),
|
||||
],
|
||||
'lecturer_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
|
||||
@ -28,9 +28,18 @@ public function paginated(int $perPage = 25, string $search = '', ?int $academic
|
||||
->paginate($perPage);
|
||||
}
|
||||
|
||||
public function create(array $data): CourseClass
|
||||
/**
|
||||
* @param array{course_ids: array<int, int>, lecturer_id: int, academic_term_id: int, method?: string|null} $data
|
||||
* @return Collection<int, CourseClass>
|
||||
*/
|
||||
public function createMany(array $data): Collection
|
||||
{
|
||||
return CourseClass::create($data);
|
||||
return new Collection(array_map(fn (int $courseId) => CourseClass::create([
|
||||
'course_id' => $courseId,
|
||||
'lecturer_id' => $data['lecturer_id'],
|
||||
'academic_term_id' => $data['academic_term_id'],
|
||||
'method' => $data['method'] ?? null,
|
||||
]), $data['course_ids']));
|
||||
}
|
||||
|
||||
public function update(CourseClass $courseClass, array $data): CourseClass
|
||||
|
||||
@ -12,6 +12,9 @@ import { PageHeader } from '@/components/page-header';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxChip,
|
||||
ComboboxChips,
|
||||
ComboboxChipsInput,
|
||||
ComboboxCollection,
|
||||
ComboboxContent,
|
||||
ComboboxEmpty,
|
||||
@ -20,6 +23,7 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxLabel,
|
||||
ComboboxList,
|
||||
useComboboxAnchor,
|
||||
} from '@/components/ui/combobox';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import {
|
||||
@ -299,27 +303,37 @@ function CreateForm({
|
||||
courseClassAssignments: CourseClassAssignment[];
|
||||
}) {
|
||||
const [academicTermId, setAcademicTermId] = useState('');
|
||||
const [course, setCourse] = useState<Course | null>(null);
|
||||
const [selectedCourses, setSelectedCourses] = useState<Course[]>([]);
|
||||
const [lecturer, setLecturer] = useState<Lecturer | null>(null);
|
||||
const courseAnchor = useComboboxAnchor();
|
||||
|
||||
const takenCourseIds = new Set(
|
||||
courseClassAssignments
|
||||
.filter((a) => String(a.academic_term_id) === academicTermId)
|
||||
.map((a) => a.course_id),
|
||||
);
|
||||
const availableCourses = courses.filter((c) => !takenCourseIds.has(c.id));
|
||||
const selectedCourseIds = new Set(selectedCourses.map((c) => c.id));
|
||||
const availableCourses = courses.filter(
|
||||
(c) => !takenCourseIds.has(c.id) && !selectedCourseIds.has(c.id),
|
||||
);
|
||||
const courseGroups = groupCoursesByDepartmentAndSemester(availableCourses);
|
||||
const availableLecturers = course
|
||||
? lecturers.filter((l) =>
|
||||
l.departments.some(
|
||||
(department) => department.id === course.department_id,
|
||||
),
|
||||
)
|
||||
: lecturers;
|
||||
const selectedDepartmentIds = new Set(
|
||||
selectedCourses.map((c) => c.department_id),
|
||||
);
|
||||
const availableLecturers =
|
||||
selectedCourses.length > 0
|
||||
? lecturers.filter((l) =>
|
||||
[...selectedDepartmentIds].every((departmentId) =>
|
||||
l.departments.some(
|
||||
(department) => department.id === departmentId,
|
||||
),
|
||||
),
|
||||
)
|
||||
: lecturers;
|
||||
|
||||
function reset() {
|
||||
setAcademicTermId('');
|
||||
setCourse(null);
|
||||
setSelectedCourses([]);
|
||||
setLecturer(null);
|
||||
}
|
||||
|
||||
@ -351,7 +365,7 @@ function CreateForm({
|
||||
value={academicTermId}
|
||||
onValueChange={(value) => {
|
||||
setAcademicTermId(value);
|
||||
setCourse(null);
|
||||
setSelectedCourses([]);
|
||||
setLecturer(null);
|
||||
}}
|
||||
>
|
||||
@ -376,26 +390,43 @@ function CreateForm({
|
||||
Mata Kuliah{' '}
|
||||
<span className="text-destructive">*</span>
|
||||
</Label>
|
||||
<input
|
||||
type="hidden"
|
||||
name="course_id"
|
||||
value={course?.id ?? ''}
|
||||
/>
|
||||
{selectedCourses.map((c) => (
|
||||
<input
|
||||
key={c.id}
|
||||
type="hidden"
|
||||
name="course_ids[]"
|
||||
value={c.id}
|
||||
/>
|
||||
))}
|
||||
<Combobox
|
||||
items={courseGroups}
|
||||
value={course}
|
||||
multiple
|
||||
value={selectedCourses}
|
||||
onValueChange={(value) => {
|
||||
setCourse(value);
|
||||
setSelectedCourses(value);
|
||||
setLecturer(null);
|
||||
}}
|
||||
itemToStringLabel={(c) => `${c.code} - ${c.name}`}
|
||||
isItemEqualToValue={(a, b) => a.id === b.id}
|
||||
>
|
||||
<ComboboxInput
|
||||
placeholder="Pilih mata kuliah"
|
||||
className="w-full"
|
||||
/>
|
||||
<ComboboxContent>
|
||||
<ComboboxChips ref={courseAnchor}>
|
||||
{selectedCourses.map((c) => (
|
||||
<ComboboxChip
|
||||
key={c.id}
|
||||
aria-label={`${c.code} - ${c.name}`}
|
||||
>
|
||||
{c.code}
|
||||
</ComboboxChip>
|
||||
))}
|
||||
<ComboboxChipsInput
|
||||
placeholder={
|
||||
selectedCourses.length === 0
|
||||
? 'Pilih mata kuliah (bisa lebih dari satu)'
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</ComboboxChips>
|
||||
<ComboboxContent anchor={courseAnchor}>
|
||||
<ComboboxEmpty>
|
||||
Mata kuliah tidak ditemukan.
|
||||
</ComboboxEmpty>
|
||||
@ -423,7 +454,7 @@ function CreateForm({
|
||||
</ComboboxList>
|
||||
</ComboboxContent>
|
||||
</Combobox>
|
||||
<InputError message={errors.course_id} />
|
||||
<InputError message={errors.course_ids} />
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>
|
||||
@ -443,12 +474,12 @@ function CreateForm({
|
||||
`${lect.user?.profile?.full_name ?? 'N/A'} - ${lect.lecturer_number}`
|
||||
}
|
||||
isItemEqualToValue={(a, b) => a.id === b.id}
|
||||
disabled={!course}
|
||||
disabled={selectedCourses.length === 0}
|
||||
>
|
||||
<ComboboxInput
|
||||
disabled={!course}
|
||||
disabled={selectedCourses.length === 0}
|
||||
placeholder={
|
||||
course
|
||||
selectedCourses.length > 0
|
||||
? 'Pilih dosen pengampu'
|
||||
: 'Pilih mata kuliah terlebih dahulu'
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user