feat: implement assignment delivery status workflow with lecturer notification lock and UI updates
This commit is contained in:
parent
3ee0e49723
commit
2d36e961ba
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\Learning\Assignments\Actions;
|
||||
|
||||
use App\Filament\Support\SystemNotification;
|
||||
use App\Models\Assignment;
|
||||
use Filament\Actions\Action;
|
||||
|
||||
class MarkAsSentAction extends Action
|
||||
{
|
||||
public static function getDefaultName(): ?string
|
||||
{
|
||||
return 'markAsSentAction';
|
||||
}
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->label(function (array $arguments, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
|
||||
return $assignment?->is_sent_to_lecturer ? 'Buka Kunci' : 'Tandai Terkirim';
|
||||
})
|
||||
->icon(function (array $arguments, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
|
||||
return $assignment?->is_sent_to_lecturer ? 'heroicon-o-lock-open' : 'heroicon-o-check-circle';
|
||||
})
|
||||
->color(function (array $arguments, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
|
||||
return $assignment?->is_sent_to_lecturer ? 'warning' : 'success';
|
||||
})
|
||||
->link()
|
||||
->visible(fn () => auth()->user()->hasRole(['Kosma', 'Developer']))
|
||||
->action(function (array $arguments, $livewire, ?Assignment $record) {
|
||||
$assignment = $record ?? Assignment::find($arguments['record'] ?? null);
|
||||
if (! $assignment) {
|
||||
return;
|
||||
}
|
||||
|
||||
$assignment->update([
|
||||
'is_sent_to_lecturer' => ! $assignment->is_sent_to_lecturer,
|
||||
]);
|
||||
|
||||
SystemNotification::send($assignment->is_sent_to_lecturer ? 'assignment_sent' : 'assignment_unlocked')
|
||||
->send();
|
||||
|
||||
if (isset($livewire->assignments)) {
|
||||
unset($livewire->assignments);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\CreateAssignmentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\DeleteAssignmentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\EditAssignmentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\MarkAsSentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\PinAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\ShareAssignmentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\AssignmentResource;
|
||||
@ -156,6 +157,11 @@ public function shareAssignmentAction(): Action
|
||||
return ShareAssignmentAction::make();
|
||||
}
|
||||
|
||||
public function markAsSentAction(): Action
|
||||
{
|
||||
return MarkAsSentAction::make();
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function assignments(): Collection
|
||||
{
|
||||
@ -234,15 +240,20 @@ public function assignments(): Collection
|
||||
|
||||
private function getAssignmentPriority($assignment): int
|
||||
{
|
||||
$isSent = $assignment->is_sent_to_lecturer;
|
||||
if ($isSent) {
|
||||
return 5; // Closed
|
||||
}
|
||||
|
||||
$isSubmitted = $assignment->assignmentSubmissions?->isNotEmpty();
|
||||
$isOverdue = now()->isAfter($assignment->due_date);
|
||||
|
||||
if (! $isOverdue) {
|
||||
return $isSubmitted ? 2 : 1;
|
||||
return $isSubmitted ? 3 : 1; // 1: Active, 3: Completed Active
|
||||
}
|
||||
|
||||
// Overdue
|
||||
return $isSubmitted ? 3 : 4;
|
||||
// Overdue but not sent
|
||||
return $isSubmitted ? 4 : 2; // 2: Actionable Overdue, 4: Completed Overdue
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
@ -269,8 +280,10 @@ public function assignmentCards()
|
||||
$isLeader = $userGroup && $userGroup->leader_id === $studentProfile->id;
|
||||
}
|
||||
|
||||
$isSentToLecturer = $assignment->is_sent_to_lecturer;
|
||||
$isOverdue = now()->isAfter($assignment->due_date);
|
||||
$canSubmit = ! $isOverdue;
|
||||
|
||||
$canSubmit = ! $isSentToLecturer;
|
||||
|
||||
$canSubmitByRole = ! $isGroup || $isLeader;
|
||||
$canSubmitActual = $canSubmit && $canSubmitByRole;
|
||||
@ -279,30 +292,35 @@ public function assignmentCards()
|
||||
$isNew = $assignment->created_at?->diffInDays(now()) <= 3;
|
||||
$isPinned = in_array($assignment->id, $pinnedIds);
|
||||
|
||||
$statusLabel = SystemNotification::getByKey('labels.assignment_status.not_submitted');
|
||||
$statusColor = 'warning';
|
||||
$statusIcon = 'heroicon-o-arrow-up-tray';
|
||||
|
||||
if ($isSubmitted) {
|
||||
$statusLabel = SystemNotification::getByKey('labels.assignment_status.submitted');
|
||||
$statusColor = 'success';
|
||||
$statusIcon = 'heroicon-o-check-circle';
|
||||
$submissionLabel = SystemNotification::getByKey('labels.assignment_status.submitted');
|
||||
$submissionColor = 'success';
|
||||
$submissionIcon = 'heroicon-o-check-circle';
|
||||
} elseif ($isOverdue) {
|
||||
$statusLabel = SystemNotification::getByKey('labels.assignment_status.overdue');
|
||||
$statusColor = 'danger';
|
||||
$statusIcon = 'heroicon-o-clock';
|
||||
$submissionLabel = SystemNotification::getByKey('labels.assignment_status.overdue');
|
||||
$submissionColor = 'danger';
|
||||
$submissionIcon = 'heroicon-o-clock';
|
||||
} elseif ($isGroup && ! $isLeader && ! $isSubmitted) {
|
||||
$statusLabel = SystemNotification::getByKey('labels.assignment_status.waiting_leader');
|
||||
$statusColor = 'gray';
|
||||
$statusIcon = 'heroicon-o-user-group';
|
||||
$submissionLabel = SystemNotification::getByKey('labels.assignment_status.waiting_leader');
|
||||
$submissionColor = 'gray';
|
||||
$submissionIcon = 'heroicon-o-user-group';
|
||||
} else {
|
||||
$submissionLabel = SystemNotification::getByKey('labels.assignment_status.not_submitted');
|
||||
$submissionColor = 'warning';
|
||||
$submissionIcon = 'heroicon-o-arrow-up-tray';
|
||||
}
|
||||
|
||||
$deliveryLabel = $isSentToLecturer ? 'Telah Dikirim' : 'Belum Dikirim';
|
||||
$deliveryColor = $isSentToLecturer ? 'success' : 'amber';
|
||||
$deliveryIcon = $isSentToLecturer ? 'heroicon-o-check-badge' : 'heroicon-o-clock';
|
||||
|
||||
return (object) [
|
||||
'id' => $assignment->id,
|
||||
'title' => $assignment->title,
|
||||
'course_name' => $assignment->course?->name ?? '-',
|
||||
'due_date_formatted' => $assignment->due_date?->translatedFormat('l, d F Y H:i'),
|
||||
'submitted_at_formatted' => ($isSubmitted && $submission->submitted_at) ? $submission->submitted_at?->translatedFormat('l, d F Y H:i') : null,
|
||||
'is_sent_to_lecturer' => $isSentToLecturer,
|
||||
'is_submitted' => $isSubmitted,
|
||||
'is_group' => $isGroup,
|
||||
'is_leader' => $isLeader,
|
||||
@ -311,8 +329,28 @@ public function assignmentCards()
|
||||
'is_urgent' => $isUrgent,
|
||||
'is_new' => $isNew,
|
||||
'is_pinned' => $isPinned,
|
||||
'status_label' => $statusLabel,
|
||||
'status_icon' => $statusIcon,
|
||||
'submission_status' => (object) [
|
||||
'label' => $submissionLabel,
|
||||
'color' => $submissionColor,
|
||||
'icon' => $submissionIcon,
|
||||
'classes' => Arr::toCssClasses([
|
||||
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-semibold', // font-semibold for clarity
|
||||
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $submissionColor === 'success',
|
||||
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' => $submissionColor === 'danger',
|
||||
'bg-warning-100 text-warning-700 dark:bg-warning-900/30 dark:text-warning-400' => $submissionColor === 'warning',
|
||||
'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400' => $submissionColor === 'gray',
|
||||
]),
|
||||
],
|
||||
'delivery_status' => (object) [
|
||||
'label' => $deliveryLabel,
|
||||
'color' => $deliveryColor,
|
||||
'icon' => $deliveryIcon,
|
||||
'classes' => Arr::toCssClasses([
|
||||
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-bold ring-1 ring-inset', // font-bold and ring for "delivery" status to make it pop
|
||||
'bg-success-50 text-success-700 ring-success-600/20 dark:bg-success-900/10 dark:text-success-400 dark:ring-success-500/20' => $deliveryColor === 'success',
|
||||
'bg-amber-50 text-amber-700 ring-amber-600/20 dark:bg-amber-900/10 dark:text-amber-400 dark:ring-amber-500/20' => $deliveryColor === 'amber',
|
||||
]),
|
||||
],
|
||||
'url' => AssignmentResource::getUrl('submit', ['record' => $assignment->id]),
|
||||
// Pre-calculated classes
|
||||
'card_classes' => Arr::toCssClasses([
|
||||
@ -327,14 +365,6 @@ public function assignmentCards()
|
||||
'bg-danger-50 dark:bg-danger-900/20 text-danger-600 dark:text-danger-400' => ! $isSubmitted && $isOverdue,
|
||||
'bg-warning-50 dark:bg-warning-900/20 text-warning-600 dark:text-warning-400' => ! $isSubmitted && ! $isOverdue,
|
||||
]),
|
||||
'status_badge_classes' => Arr::toCssClasses([
|
||||
'inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium',
|
||||
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400' => $statusColor === 'amber',
|
||||
'bg-success-100 text-success-700 dark:bg-success-900/30 dark:text-success-400' => $statusColor === 'success',
|
||||
'bg-danger-100 text-danger-700 dark:bg-danger-900/30 dark:text-danger-400' => $statusColor === 'danger',
|
||||
'bg-warning-100 text-warning-700 dark:bg-warning-900/30 dark:text-warning-400' => $statusColor === 'warning',
|
||||
'bg-gray-100 text-gray-500 dark:bg-gray-800 dark:text-gray-400' => $statusColor === 'gray',
|
||||
]),
|
||||
'indicator_icon_classes' => Arr::toCssClasses([
|
||||
'h-5 w-5 opacity-20',
|
||||
'text-primary-500' => $isLeader,
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
|
||||
use App\Enums\AssignmentType;
|
||||
use App\Filament\Actions\BackAction;
|
||||
use App\Filament\Resources\Learning\Assignments\Actions\MarkAsSentAction;
|
||||
use App\Filament\Resources\Learning\Assignments\AssignmentResource;
|
||||
use App\Filament\Support\SystemNotification;
|
||||
use App\Models\Assignment;
|
||||
use App\Models\AssignmentSubmission;
|
||||
use Filament\Actions\Action;
|
||||
@ -53,6 +55,14 @@ public function statCards(): array
|
||||
'icon' => 'heroicon-o-chart-bar',
|
||||
'color_classes' => 'bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-400',
|
||||
],
|
||||
[
|
||||
'label' => 'Status Dosen',
|
||||
'value' => $this->record?->is_sent_to_lecturer ? 'SUDAH TERKIRIM' : 'BELUM TERKIRIM',
|
||||
'icon' => $this->record?->is_sent_to_lecturer ? 'heroicon-o-check-badge' : 'heroicon-o-clock',
|
||||
'color_classes' => $this->record?->is_sent_to_lecturer
|
||||
? 'bg-success-500 text-white shadow-lg shadow-success-200 dark:shadow-none'
|
||||
: 'bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@ -164,9 +174,18 @@ public function assignmentSummary(): array
|
||||
'due_date' => $this->record?->due_date?->translatedFormat('l, d F Y H:i'),
|
||||
'type' => $this->record?->type->value,
|
||||
'is_overdue' => $this->isOverdue,
|
||||
'is_sent_to_lecturer' => $this->record?->is_sent_to_lecturer,
|
||||
];
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function sectionIcon(): string
|
||||
{
|
||||
return SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful
|
||||
? 'heroicon-o-document-magnifying-glass'
|
||||
: 'heroicon-o-document-text';
|
||||
}
|
||||
|
||||
public function previewSubmissionAction(): Action
|
||||
{
|
||||
return Action::make('previewSubmission')
|
||||
@ -186,6 +205,8 @@ public function previewSubmissionAction(): Action
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
MarkAsSentAction::make()
|
||||
->record($this->record),
|
||||
|
||||
BackAction::make()
|
||||
->url(AssignmentResource::getUrl()),
|
||||
|
||||
@ -56,6 +56,9 @@ public function form(Schema $schema): Schema
|
||||
#[Computed]
|
||||
public function statusCards(): array
|
||||
{
|
||||
$isSent = $this->record->is_sent_to_lecturer;
|
||||
$existing = $this->existingSubmission;
|
||||
|
||||
return [
|
||||
[
|
||||
'label' => 'Batas Waktu',
|
||||
@ -71,14 +74,28 @@ public function statusCards(): array
|
||||
],
|
||||
[
|
||||
'label' => 'Status Pengumpulan',
|
||||
'value' => $this->isOverdue ? 'Ditutup' : 'Terbuka',
|
||||
'icon' => $this->isOverdue ? 'heroicon-o-lock-closed' : 'heroicon-o-check-circle',
|
||||
'is_danger' => $this->isOverdue,
|
||||
'is_success' => ! $this->isOverdue,
|
||||
'value' => $existing ? 'Sudah Dikumpulkan' : ($isSent ? 'Terlambat & Terkunci' : ($this->isOverdue ? 'Belum (Masih Terbuka)' : 'Belum Dikumpulkan')),
|
||||
'icon' => $existing ? 'heroicon-o-check-circle' : ($isSent ? 'heroicon-o-lock-closed' : 'heroicon-o-arrow-up-tray'),
|
||||
'is_success' => (bool) $existing,
|
||||
'is_danger' => ! $existing && $isSent,
|
||||
'is_warning' => ! $existing && ! $isSent && $this->isOverdue,
|
||||
'icon_classes' => Arr::toCssClasses([
|
||||
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
|
||||
'bg-danger-50 dark:bg-danger-900/20 text-danger-600' => $this->isOverdue,
|
||||
'bg-success-50 dark:bg-success-900/20 text-success-600' => ! $this->isOverdue,
|
||||
'bg-success-50 dark:bg-success-900/20 text-success-600' => $existing,
|
||||
'bg-danger-50 dark:bg-danger-900/20 text-danger-600' => ! $existing && $isSent,
|
||||
'bg-warning-50 dark:bg-warning-900/20 text-warning-600' => ! $existing && ! $isSent && $this->isOverdue,
|
||||
]),
|
||||
],
|
||||
[
|
||||
'label' => 'Status ke Dosen',
|
||||
'value' => $isSent ? 'Telah Dikirim' : 'Belum Dikirim',
|
||||
'icon' => $isSent ? 'heroicon-o-paper-airplane' : 'heroicon-o-clock',
|
||||
'is_success' => $isSent,
|
||||
'badge' => $isSent ? '(Kunci Aktif)' : '(Masih Terbuka)',
|
||||
'icon_classes' => Arr::toCssClasses([
|
||||
'flex h-9 w-9 shrink-0 items-center justify-center rounded-lg',
|
||||
'bg-success-50 dark:bg-success-900/20 text-success-600' => $isSent,
|
||||
'bg-gray-100 dark:bg-gray-800 text-gray-500' => ! $isSent,
|
||||
]),
|
||||
],
|
||||
];
|
||||
@ -243,6 +260,13 @@ public function submit(): void
|
||||
return;
|
||||
}
|
||||
|
||||
if ($assignment->is_sent_to_lecturer) {
|
||||
SystemNotification::send('submission_locked', type: 'danger')
|
||||
->send();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->canSubmit) {
|
||||
SystemNotification::send('submission_not_leader', type: 'danger')
|
||||
->send();
|
||||
@ -313,7 +337,7 @@ protected function getHeaderActions(): array
|
||||
|
||||
return [
|
||||
BackAction::make()
|
||||
->url(url()->previous() ?? AssignmentResource::getUrl('index')),
|
||||
->url(url()->previous() !== AssignmentResource::getUrl('submit', ['record' => $this->record->id]) ? url()->previous() : AssignmentResource::getUrl('index')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources\Learning\Attendances\Pages;
|
||||
|
||||
use App\Enums\NotifStyle;
|
||||
use App\Filament\Resources\Learning\Attendances\AttendanceResource;
|
||||
use App\Filament\Support\SystemNotification;
|
||||
use App\Models\Attendance;
|
||||
@ -71,6 +72,22 @@ public function emptyDescription(): string
|
||||
return SystemNotification::getByKey('labels.empty_attendance.description');
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function sectionIcon(): string
|
||||
{
|
||||
return SystemNotification::getNotifStyle() === NotifStyle::Cheerful
|
||||
? 'heroicon-o-hand-raised'
|
||||
: 'heroicon-o-clipboard-document-check';
|
||||
}
|
||||
|
||||
#[Computed]
|
||||
public function historyIcon(): string
|
||||
{
|
||||
return SystemNotification::getNotifStyle() === NotifStyle::Cheerful
|
||||
? 'heroicon-o-clock'
|
||||
: 'heroicon-o-archive-box';
|
||||
}
|
||||
|
||||
public function getSchedules()
|
||||
{
|
||||
/** @var User|null $user */
|
||||
|
||||
@ -30,6 +30,7 @@ protected function casts(): array
|
||||
return [
|
||||
'due_date' => 'datetime',
|
||||
'type' => AssignmentType::class,
|
||||
'is_sent_to_lecturer' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
if (!Schema::hasColumn('assignments', 'is_sent_to_lecturer')) {
|
||||
Schema::table('assignments', function (Blueprint $table) {
|
||||
$table->boolean('is_sent_to_lecturer')->default(false)->after('due_date');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('assignments', function (Blueprint $table) {
|
||||
$table->dropColumn('is_sent_to_lecturer');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -145,6 +145,18 @@
|
||||
'title' => 'Mantap! Sudah Dibaca ✅✨',
|
||||
'body' => 'Pembaruan ":title" sudah ditandai. Makin update makin jago! 🚀🔥',
|
||||
],
|
||||
'assignment_sent' => [
|
||||
'title' => 'Tugas Berhasil Dikirim! 🚀✨',
|
||||
'body' => 'Tugas kolektif sudah berhasil ditandai terkirim ke dosen. Status pengumpulan resmi ditutup! 🔒',
|
||||
],
|
||||
'assignment_unlocked' => [
|
||||
'title' => 'Kunci Tugas Dibuka! 🔓✨',
|
||||
'body' => 'Mahasiswa sekarang diperbolehkan kembali untuk mengumpulkan atau memperbarui tugas mereka. 📝',
|
||||
],
|
||||
'submission_locked' => [
|
||||
'title' => 'Tugas Terkunci! 🔒',
|
||||
'body' => 'Mohon maaf, tugas ini sudah ditandai terkirim ke dosen oleh Kosma dan tidak dapat diubah lagi. 🚫',
|
||||
],
|
||||
|
||||
// UI Labels & Descriptions
|
||||
'labels' => [
|
||||
@ -437,6 +449,18 @@
|
||||
'title' => 'Pembaruan Berhasil Ditandai',
|
||||
'body' => 'Catatan rilis ":title" telah berhasil ditandai sebagai sudah dibaca.',
|
||||
],
|
||||
'assignment_sent' => [
|
||||
'title' => 'Konfirmasi Pengiriman Berhasil',
|
||||
'body' => 'Seluruh berkas tugas kolektif telah berhasil ditandai sebagai terkirim ke dosen pengampu.',
|
||||
],
|
||||
'assignment_unlocked' => [
|
||||
'title' => 'Kunci Akses Dibuka',
|
||||
'body' => 'Otoritas pengumpulan tugas telah diaktifkan kembali untuk seluruh mahasiswa terkait.',
|
||||
],
|
||||
'submission_locked' => [
|
||||
'title' => 'Akses Pengumpulan Ditutup',
|
||||
'body' => 'Tugas ini telah dalam status final (terkirim ke dosen). Perubahan data tidak lagi dimungkinkan.',
|
||||
],
|
||||
|
||||
// UI Labels & Descriptions
|
||||
'labels' => [
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
class="flex flex-1 items-start gap-4 p-5 text-left min-w-0 hover:bg-primary-500/5 transition-colors cursor-pointer">
|
||||
|
||||
<div class="{{ $card->icon_wrapper_classes }} h-12 w-12 mt-1 shrink-0">
|
||||
<x-filament::icon :icon="$card->status_icon" class="h-6 w-6" />
|
||||
<x-filament::icon :icon="$card->submission_status->icon" class="h-6 w-6" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
@ -51,11 +51,16 @@ class="inline-flex items-center gap-1.5 rounded-full bg-blue-100 text-blue-700 d
|
||||
</span>
|
||||
@endif
|
||||
|
||||
<span class="{{ $card->status_badge_classes }} px-2.5 py-1 whitespace-nowrap">
|
||||
@if ($card->status_icon)
|
||||
<x-filament::icon :icon="$card->status_icon" class="h-4 w-4" />
|
||||
@endif
|
||||
{{ $card->status_label }}
|
||||
{{-- Status Pengumpulan (Submission Status) --}}
|
||||
<span class="{{ $card->submission_status->classes }} whitespace-nowrap">
|
||||
<x-filament::icon :icon="$card->submission_status->icon" class="h-3.5 w-3.5" />
|
||||
{{ $card->submission_status->label }}
|
||||
</span>
|
||||
|
||||
{{-- Status Pengiriman ke Dosen (Delivery Status) --}}
|
||||
<span class="{{ $card->delivery_status->classes }} whitespace-nowrap">
|
||||
<x-filament::icon :icon="$card->delivery_status->icon" class="h-3.5 w-3.5" />
|
||||
{{ $card->delivery_status->label }}
|
||||
</span>
|
||||
|
||||
@if ($card->is_urgent)
|
||||
@ -91,6 +96,7 @@ class="flex flex-wrap items-center gap-3 px-5 py-4 border-t border-gray-100 dark
|
||||
@endcan
|
||||
|
||||
@canAny(['Update:Assignment', 'Delete:Assignment'])
|
||||
{{ ($this->markAsSentAction)(['record' => $card->id]) }}
|
||||
{{ ($this->editAssignmentAction)(['record' => $card->id]) }}
|
||||
{{ ($this->deleteAssignmentAction)(['record' => $card->id]) }}
|
||||
@endcanAny
|
||||
|
||||
@ -26,11 +26,18 @@
|
||||
style="width: {{ $this->percentage }}%"></div>
|
||||
</div>
|
||||
|
||||
<x-filament::section
|
||||
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-document-magnifying-glass' : 'heroicon-o-document-text' }}"
|
||||
<x-filament::section
|
||||
icon="{{ $this->sectionIcon }}"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ $this->assignmentSummary['title'] }}</x-slot>
|
||||
<x-slot name="heading">
|
||||
<div class="flex items-center gap-3">
|
||||
{{ $this->assignmentSummary['title'] }}
|
||||
@if ($this->assignmentSummary['is_sent_to_lecturer'])
|
||||
<x-filament::badge color="success" icon="heroicon-m-check-badge" size="sm">Sudah Dikirim ke Dosen</x-filament::badge>
|
||||
@endif
|
||||
</div>
|
||||
</x-slot>
|
||||
<x-slot name="description">
|
||||
{{ $this->assignmentSummary['course'] }}
|
||||
·
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<x-slot name="heading">{{ $this->record->title }}</x-slot>
|
||||
<x-slot name="description">{{ $this->record->course?->name ?? '-' }}</x-slot>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-4">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 mb-4">
|
||||
@foreach ($this->statusCards as $card)
|
||||
<div class="flex items-center gap-3 rounded-lg border border-gray-200 dark:border-gray-700 p-3">
|
||||
<div class="{{ $card['icon_classes'] }}">
|
||||
@ -44,29 +44,28 @@ class="block border-0"></iframe>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
|
||||
@if ($this->isOverdue)
|
||||
{{-- Kondisi Form: Muncul jika BELUM dikirim ke dosen --}}
|
||||
@if ($this->record->is_sent_to_lecturer)
|
||||
<x-filament::section :icon="$this->overdueIcon" icon-color="danger">
|
||||
<x-slot name="heading">{{ $this->overdueHeading }}</x-slot>
|
||||
<x-slot name="description">{{ $this->overdueDescription }}</x-slot>
|
||||
<x-slot name="heading">Penerimaan Tugas Ditutup</x-slot>
|
||||
<x-slot name="description">Tugas ini sudah dikirim ke dosen oleh Kosma dan tidak dapat diubah lagi.</x-slot>
|
||||
|
||||
<div class="flex items-center gap-3 mb-5">
|
||||
<div
|
||||
class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-danger-50 dark:bg-danger-900/20 text-danger-600">
|
||||
<x-filament::icon icon="heroicon-o-lock-closed" class="h-5 w-5" />
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<x-filament::badge :color="$this->submissionStatus->badge_color" size="lg">
|
||||
{{ $this->submissionStatus->badge_label }}
|
||||
</x-filament::badge>
|
||||
<div class="flex flex-wrap items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Tugas sudah dikirim ke dosen pada status final.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($url = $this->getSubmissionFileUrl())
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File yang Dikumpulkan</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-2">File yang Terakhir Dikumpulkan</p>
|
||||
<div
|
||||
class="rounded-lg overflow-hidden border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
|
||||
<iframe src="{{ $url }}" width="100%" height="500"
|
||||
<iframe src="{{ $url }}" width="100%" height="400"
|
||||
class="block border-0"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
@ -96,6 +95,16 @@ class="block border-0"></iframe>
|
||||
@endif
|
||||
</x-filament::section>
|
||||
@else
|
||||
@if ($this->isOverdue)
|
||||
<div class="p-4 rounded-xl border border-warning-200 bg-warning-50 dark:bg-warning-900/10 dark:border-warning-900/20 flex gap-3">
|
||||
<x-filament::icon icon="heroicon-m-exclamation-triangle" class="h-5 w-5 text-warning-600 shrink-0" />
|
||||
<div>
|
||||
<p class="text-sm font-bold text-warning-800 dark:text-warning-400">Tenggat Waktu Sudah Terlewat</p>
|
||||
<p class="text-xs text-warning-700 dark:text-warning-500 mt-1">Anda masih diperbolehkan mengumpulkan tugas karena belum dikirim ke dosen oleh Kosma.</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<x-filament::section :icon="$this->submissionIcon" icon-color="primary">
|
||||
<x-slot name="heading">{{ $this->submissionHeading }}</x-slot>
|
||||
<x-slot name="description">{{ $this->submissionDescription }}</x-slot>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<x-filament-panels::page>
|
||||
<x-filament::section
|
||||
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-hand-raised' : 'heroicon-o-clipboard-document-check' }}"
|
||||
<x-filament::section
|
||||
icon="{{ $this->sectionIcon }}"
|
||||
icon-color="primary"
|
||||
>
|
||||
<x-slot name="heading">{{ $this->heading }}</x-slot>
|
||||
@ -102,7 +102,7 @@ class="text-[10px] font-bold text-gray-400 uppercase tracking-widest leading-non
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<x-filament::empty-state icon="heroicon-o-finger-print"
|
||||
<x-filament::empty-state icon="heroicon-o-finger-print"
|
||||
heading="{{ $this->emptyHeading }}"
|
||||
description="{{ $this->emptyDescription }}"
|
||||
iconColor="gray">
|
||||
@ -111,8 +111,8 @@ class="text-[10px] font-bold text-gray-400 uppercase tracking-widest leading-non
|
||||
|
||||
</x-filament::section>
|
||||
|
||||
<x-filament::section
|
||||
icon="{{ \App\Filament\Support\SystemNotification::getNotifStyle() === \App\Enums\NotifStyle::Cheerful ? 'heroicon-o-clock' : 'heroicon-o-archive-box' }}"
|
||||
<x-filament::section
|
||||
icon="{{ $this->historyIcon }}"
|
||||
icon-color="gray"
|
||||
>
|
||||
<x-slot name="heading">{{ $this->historyHeading }}</x-slot>
|
||||
|
||||
@ -366,3 +366,71 @@
|
||||
->assertSee($assignment->title);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Assignment Delivery Status', function () {
|
||||
beforeEach(function () {
|
||||
$this->kosmaUser = User::factory()->create();
|
||||
$this->kosmaUser->assignRole(RoleEnum::Kosma);
|
||||
$this->kosmaUser->givePermissionTo(['ViewAny:Assignment', 'View:Assignment', 'View:CourseSchedule']);
|
||||
|
||||
$this->studentUser = User::factory()->create();
|
||||
$this->studentUser->assignRole(RoleEnum::Student);
|
||||
$this->studentUser->givePermissionTo(['ViewAny:Assignment', 'View:CourseSchedule']);
|
||||
$this->student = Student::factory()->create(['user_id' => $this->studentUser->id]);
|
||||
|
||||
$this->currentSemester = app(GeneralSettings::class)->current_semester;
|
||||
});
|
||||
|
||||
it('can toggle assignment delivery status (Mark as Sent)', function () {
|
||||
$assignment = Assignment::factory()->create([
|
||||
'is_sent_to_lecturer' => false,
|
||||
]);
|
||||
|
||||
$this->actingAs($this->kosmaUser);
|
||||
|
||||
Livewire::test(ListAssignments::class)
|
||||
->callAction('markAsSentAction', [], ['record' => $assignment->id]);
|
||||
|
||||
expect($assignment->refresh()->is_sent_to_lecturer)->toBeTrue();
|
||||
|
||||
Livewire::test(ListAssignments::class)
|
||||
->callAction('markAsSentAction', [], ['record' => $assignment->id]);
|
||||
|
||||
expect($assignment->refresh()->is_sent_to_lecturer)->toBeFalse();
|
||||
});
|
||||
|
||||
it('allows students to submit overdue assignments if not yet sent to lecturer', function () {
|
||||
$assignment = Assignment::factory()->create([
|
||||
'due_date' => now()->subDays(2)->toDateTimeString(), // Far enough in the past
|
||||
'is_sent_to_lecturer' => false,
|
||||
'type' => AssignmentType::Individual,
|
||||
]);
|
||||
$assignment->students()->attach($this->student->id);
|
||||
$assignment->course->update(['semester' => $this->currentSemester]);
|
||||
|
||||
$this->actingAs($this->studentUser);
|
||||
|
||||
Livewire::test(SubmitAssignmentPage::class, ['record' => $assignment])
|
||||
->assertSuccessful()
|
||||
->assertSet('isOverdue', true)
|
||||
->assertSet('canSubmit', true)
|
||||
->assertSee('Tenggat Waktu Sudah Terlewat');
|
||||
});
|
||||
|
||||
it('prevents students from submitting if assignment is marked as sent to lecturer', function () {
|
||||
$assignment = Assignment::factory()->create([
|
||||
'due_date' => now()->addDay(),
|
||||
'is_sent_to_lecturer' => true,
|
||||
]);
|
||||
$assignment->students()->attach($this->student->id);
|
||||
$assignment->course->update(['semester' => $this->currentSemester]);
|
||||
|
||||
$this->actingAs($this->studentUser);
|
||||
|
||||
Livewire::test(SubmitAssignmentPage::class, ['record' => $assignment])
|
||||
->assertSuccessful()
|
||||
->assertSee('Penerimaan Tugas Ditutup')
|
||||
->assertDontSeeHtml('type="file"');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user