Compare commits

..

No commits in common. "4183ec1e36969602ca3c9f88dbde721b375fd1e8" and "783a0f6dda088f7ecf44e2168ee2149beedc3a75" have entirely different histories.

5 changed files with 49 additions and 159 deletions

View File

@ -2,7 +2,6 @@
namespace App\Http\Controllers\Admin\Finances;
use App\Enums\StudentStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Finances\TuitionInvoiceRequest;
use App\Http\Requests\PaginatedRequest;
@ -29,7 +28,7 @@ public function index(PaginatedRequest $request): Response
...$request->validatedWithDefaults(),
academicTermId: $request->validated('academic_term_id'),
),
'students' => $this->studentService->getAllForSelect(status: StudentStatus::Active->value),
'students' => $this->studentService->getAllForSelect(),
'academicTerms' => $this->academicTermService->getAllForSelect(),
'filters' => $request->only(['academic_term_id']),
]);
@ -37,7 +36,7 @@ public function index(PaginatedRequest $request): Response
public function store(TuitionInvoiceRequest $request): RedirectResponse
{
$this->service->createMany($request->validated('student_ids'), $request->validated());
$this->service->create($request->validated());
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tagihan berhasil ditambahkan.']);

View File

@ -14,19 +14,11 @@ public function authorize(): bool
public function rules(): array
{
$rules = [
return [
'student_id' => ['required', 'integer', Rule::exists('students', 'id')],
'academic_term_id' => ['required', 'integer', Rule::exists('academic_terms', 'id')],
'amount_due' => ['required', 'numeric', 'min:0'],
'due_date' => ['required', 'date'],
'due_date' => ['nullable', 'date'],
];
if ($this->isMethod('post')) {
$rules['student_ids'] = ['required', 'array', 'min:1'];
$rules['student_ids.*'] = ['integer', Rule::exists('students', 'id')];
} else {
$rules['student_id'] = ['required', 'integer', Rule::exists('students', 'id')];
}
return $rules;
}
}

View File

@ -4,7 +4,6 @@
use App\Models\TuitionInvoice;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
class TuitionInvoiceService
{
@ -22,18 +21,14 @@ public function paginated(int $perPage = 25, string $search = '', ?int $academic
->paginate($perPage);
}
public function createMany(array $studentIds, array $data): void
public function create(array $data): TuitionInvoice
{
DB::transaction(function () use ($studentIds, $data) {
foreach ($studentIds as $studentId) {
TuitionInvoice::create([
'student_id' => $studentId,
'academic_term_id' => $data['academic_term_id'],
'amount_due' => $data['amount_due'],
'due_date' => $data['due_date'],
]);
}
});
return TuitionInvoice::create([
'student_id' => $data['student_id'],
'academic_term_id' => $data['academic_term_id'],
'amount_due' => $data['amount_due'],
'due_date' => $data['due_date'] ?? null,
]);
}
public function update(TuitionInvoice $invoice, array $data): TuitionInvoice

View File

@ -12,7 +12,7 @@
class StudentService
{
public function getAllForSelect(?string $status = null): Collection
public function getAllForSelect(): Collection
{
return Student::select(['id', 'user_id', 'student_number', 'department_id'])
->with([
@ -20,7 +20,6 @@ public function getAllForSelect(?string $status = null): Collection
'user.profile:id,user_id,full_name',
'department:id,name',
])
->when($status, fn ($q) => $q->where('status', $status))
->get();
}

View File

@ -13,18 +13,6 @@ import InputError from '@/components/input-error';
import { PageHeader } from '@/components/page-header';
import { RupiahInput } from '@/components/rupiah-input';
import { Button } from '@/components/ui/button';
import {
Combobox,
ComboboxChip,
ComboboxChips,
ComboboxChipsInput,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxItem,
ComboboxList,
useComboboxAnchor,
} from '@/components/ui/combobox';
import { Label } from '@/components/ui/label';
import {
Select,
@ -221,15 +209,6 @@ function CreateForm({
academicTerms: AcademicTermOption[];
}) {
const [dueDate, setDueDate] = useState<Date | undefined>();
const [selectedStudents, setSelectedStudents] = useState<
TuitionInvoiceStudent[]
>([]);
const studentAnchor = useComboboxAnchor();
function reset() {
setDueDate(undefined);
setSelectedStudents([]);
}
return (
<FormDialog
@ -240,84 +219,33 @@ function CreateForm({
resetOnSuccess
onSuccess={() => {
onOpenChange(false);
reset();
setDueDate(undefined);
}}
>
{({ errors }) => (
<div className="grid gap-4">
<div className="grid gap-2">
<div className="flex items-center justify-between">
<Label>
Mahasiswa{' '}
<span className="text-destructive">*</span>
</Label>
<button
type="button"
className="text-xs text-primary underline underline-offset-4 hover:text-primary/80"
onClick={() =>
setSelectedStudents(
selectedStudents.length ===
students.length
? []
: students,
)
}
>
{selectedStudents.length === students.length
? 'Batalkan Semua'
: 'Pilih Semua'}
</button>
</div>
{selectedStudents.map((student) => (
<input
key={student.id}
type="hidden"
name="student_ids[]"
value={student.id}
/>
))}
<Combobox
items={students}
multiple
value={selectedStudents}
onValueChange={setSelectedStudents}
itemToStringLabel={studentLabel}
isItemEqualToValue={(a, b) => a.id === b.id}
>
<ComboboxChips ref={studentAnchor}>
{selectedStudents.map((student) => (
<ComboboxChip
<Label>
Mahasiswa{' '}
<span className="text-destructive">*</span>
</Label>
<input type="hidden" name="student_id" />
<Select name="student_id">
<SelectTrigger className="w-full">
<SelectValue placeholder="Pilih mahasiswa" />
</SelectTrigger>
<SelectContent>
{students.map((student) => (
<SelectItem
key={student.id}
aria-label={studentLabel(student)}
value={String(student.id)}
>
{studentLabel(student)}
</ComboboxChip>
</SelectItem>
))}
<ComboboxChipsInput
placeholder={
selectedStudents.length === 0
? 'Pilih mahasiswa (aktif)'
: ''
}
/>
</ComboboxChips>
<ComboboxContent anchor={studentAnchor}>
<ComboboxEmpty>
Mahasiswa tidak ditemukan.
</ComboboxEmpty>
<ComboboxList>
{students.map((student) => (
<ComboboxItem
key={student.id}
value={student}
>
{studentLabel(student)}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>
<InputError message={errors.student_ids} />
</SelectContent>
</Select>
<InputError message={errors.student_id} />
</div>
<div className="grid gap-2">
<Label>
@ -356,10 +284,7 @@ function CreateForm({
<InputError message={errors.amount_due} />
</div>
<div className="grid gap-2">
<Label>
Jatuh Tempo{' '}
<span className="text-destructive">*</span>
</Label>
<Label>Jatuh Tempo</Label>
<input
type="hidden"
name="due_date"
@ -394,9 +319,6 @@ function EditForm({
const [dueDate, setDueDate] = useState<Date | undefined>(
editing?.due_date ? new Date(editing.due_date) : undefined,
);
const [student, setStudent] = useState<TuitionInvoiceStudent | null>(
students.find((s) => s.id === editing?.student_id) ?? null,
);
return (
<FormDialog
@ -415,38 +337,24 @@ function EditForm({
Mahasiswa{' '}
<span className="text-destructive">*</span>
</Label>
<input
type="hidden"
<Select
name="student_id"
value={student?.id ?? ''}
/>
<Combobox
items={students}
value={student}
onValueChange={setStudent}
itemToStringLabel={studentLabel}
isItemEqualToValue={(a, b) => a.id === b.id}
defaultValue={String(editing.student_id)}
>
<ComboboxInput
placeholder="Pilih mahasiswa (aktif)"
className="w-full"
/>
<ComboboxContent>
<ComboboxEmpty>
Mahasiswa tidak ditemukan.
</ComboboxEmpty>
<ComboboxList>
{students.map((option) => (
<ComboboxItem
key={option.id}
value={option}
>
{studentLabel(option)}
</ComboboxItem>
))}
</ComboboxList>
</ComboboxContent>
</Combobox>
<SelectTrigger className="w-full">
<SelectValue placeholder="Pilih mahasiswa" />
</SelectTrigger>
<SelectContent>
{students.map((student) => (
<SelectItem
key={student.id}
value={String(student.id)}
>
{studentLabel(student)}
</SelectItem>
))}
</SelectContent>
</Select>
<InputError message={errors.student_id} />
</div>
<div className="grid gap-2">
@ -488,10 +396,7 @@ function EditForm({
<InputError message={errors.amount_due} />
</div>
<div className="grid gap-2">
<Label>
Jatuh Tempo{' '}
<span className="text-destructive">*</span>
</Label>
<Label>Jatuh Tempo</Label>
<input
type="hidden"
name="due_date"