diff --git a/app/Http/Controllers/CourseTrackController.php b/app/Http/Controllers/CourseTrackController.php
index ba379e0c..075f606e 100644
--- a/app/Http/Controllers/CourseTrackController.php
+++ b/app/Http/Controllers/CourseTrackController.php
@@ -5,11 +5,28 @@
use App\Models\Course;
use App\Models\CourseTrack;
use App\Models\Task;
+use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class CourseTrackController extends Controller
{
+
+ public function index(Course $course) : View
+ {
+ $tracks = $course->tracks()->get();
+
+ $tracks->map(function(CourseTrack $track) {
+ $track->depth = $track->getDepthAttribute();
+ });
+
+ return view('courses.tracks.index', [
+ 'course' => $course,
+ 'tracks' => $tracks,
+ ]);
+ }
+
public function show(Course $course, CourseTrack $track) : View
{
$tasks = $course->tasks()->where('track_id', $track->id)->where('is_visible', true)->get()->map(fn(Task $task) => [
@@ -32,4 +49,143 @@ public function show(Course $course, CourseTrack $track) : View
],
]);
}
+
+ public function create(Request $request, Course $course) : View
+ {
+ Log::info("Create course track");
+
+ $parent = $course->tracks()->where('id', $request->query('parent'))->first();
+
+ return view('courses.tracks.create', [
+ 'course' => $course,
+ 'parent' => $parent,
+ ]);
+ }
+
+ public function store(Request $request, Course $course) : RedirectResponse
+ {
+ $validated = $request->validate([
+ 'name' => 'required|string|max:255|min:1',
+ 'description' => 'nullable|string',
+ 'parent' => 'nullable|exists:course_tracks,id',
+ ]);
+
+ /** @var CourseTrack $track */
+
+ $track = $course->tracks()->make([
+ 'name' => $validated['name'],
+ 'description' => $validated['description'],
+ ]);
+
+ if (array_key_exists('parent', $validated) && $validated['parent'] != null)
+ {
+ $parent = $course->tracks()->where('id', $validated['parent'])->first();
+ if ($parent == null)
+ {
+ flash()->addError("Parent track does not exist!");
+
+ return redirect()->route('courses.manage.tracks.index', $course);
+ }
+
+ $track->parent()->associate($validated['parent']);
+ }
+
+ $track->save();
+
+ flash()->addSuccess("Track created successfully");
+
+ return redirect()->route('courses.manage.tracks.index', [$course, $track]);
+ }
+
+ public function destroy(Course $course, CourseTrack $track) : RedirectResponse
+ {
+ if ($track->children()->count() > 0)
+ {
+ flash()->addError("Track has sub tracks, remove them first!");
+
+ return redirect()->route('courses.manage.tracks.index', [$course]);
+ }
+
+ Log::info("Deleting course track $track->id");
+
+ $track->tasks()->each(function(Task $task) {
+ $task->track()->dissociate();
+ $task->save();
+ });
+
+ $track->delete();
+
+ flash()->addSuccess("Track deleted successfully");
+
+ return redirect()->route('courses.manage.tracks.index', $course);
+ }
+
+ public function assign(Course $course, CourseTrack $track) : View
+ {
+ $tasks = $course->tasks()->where('track_id', null)->get();
+
+ return view('courses.tracks.assign-tasks', [
+ 'course' => $course,
+ 'track' => $track,
+ 'tasks' => $tasks,
+ ]);
+ }
+
+ public function doAssign(Request $request, Course $course, CourseTrack $track) : RedirectResponse
+ {
+ if ($course->id != $track->course_id)
+ {
+ abort(403);
+ }
+
+ $validated = $request->validate([
+ 'tasks' => 'required|array',
+ 'tasks.*' => 'exists:tasks,id',
+ ]);
+
+ $selectedTasks = $course->tasks()->whereIn('id', $validated['tasks'])->getQuery();
+ if ($selectedTasks->count() != count($validated['tasks']))
+ {
+ flash()->addError("Some tasks do not exist");
+
+ return redirect()->route('courses.manage.tracks.assign', [$course, $track]);
+ }
+
+ if ($selectedTasks->clone()->where('track_id', '!=', null)->count() > 0)
+ {
+ flash()->addError("Some tasks are already assigned to a track");
+
+ return redirect()->route('courses.manage.tracks.assign', [$course, $track]);
+ }
+
+
+ $selectedTasks->get()->each(function (Task $task) use ($track) {
+ $task->track()->associate($track);
+ $task->save();
+ });
+
+ flash()->addSuccess("Tasks assigned to track successfully");
+
+ return redirect()->route('courses.manage.tracks.index', $course);
+ }
+
+ public function unassign(Course $course, CourseTrack $track, Task $task) : RedirectResponse
+ {
+ if ($course->id != $track->course_id)
+ {
+ abort(403);
+ }
+
+ if ($task->track_id != $track->id)
+ {
+ abort(403);
+ }
+
+ $task->track()->dissociate();
+ $task->save();
+
+ flash()->addSuccess("Task unassigned from track successfully");
+
+ return redirect()->route('courses.manage.tracks.assign', [$course, $track]);
+ }
}
diff --git a/app/Models/CourseTrack.php b/app/Models/CourseTrack.php
index 62fb26fa..c2f918b7 100644
--- a/app/Models/CourseTrack.php
+++ b/app/Models/CourseTrack.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use Database\Factories\CourseTrackFactory;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -10,8 +11,12 @@
use Illuminate\Support\Facades\DB;
/**
+ * @property-read int $id
+ * @property int $course_id
* @property-read CourseTrack|null $parent
* @property-read CourseTrack[]|Collection $immediateChildren
+ * @method static CourseTrackFactory factory()
+ * @mixin \Eloquent
*/
class CourseTrack extends Model
{
@@ -140,4 +145,9 @@ public function rootChildrenNotInPath(bool $withChildren = true): Collection
return $remaining->whereNotIn('id', $this->children()->pluck('id'));
}
+
+ public function getDepthAttribute(): int
+ {
+ return $this->path()->count();
+ }
}
diff --git a/app/Models/Task.php b/app/Models/Task.php
index b6c320d0..bd9e024d 100644
--- a/app/Models/Task.php
+++ b/app/Models/Task.php
@@ -57,6 +57,7 @@
* @property string $name
* @static Task findOrFail($id, $columns = []) {
* @property SubTaskCollection $sub_tasks
+ * @property-read int $track_id
* @property-read CourseTrack|null $track
* @property-read SurveyTask|null $pivot
* @property-read bool $hasEnded
diff --git a/database/factories/CourseTrackFactory.php b/database/factories/CourseTrackFactory.php
index 3d50344e..1fc7c76f 100644
--- a/database/factories/CourseTrackFactory.php
+++ b/database/factories/CourseTrackFactory.php
@@ -17,7 +17,8 @@ class CourseTrackFactory extends Factory
public function definition()
{
return [
- //
+ 'name' => $this->faker->sentence(3),
];
}
+
}
diff --git a/resources/css/app.css b/resources/css/app.css
index 61bd5048..b32c5958 100644
--- a/resources/css/app.css
+++ b/resources/css/app.css
@@ -106,3 +106,7 @@
@apply bg-red-700 hover:bg-red-800 text-white py-2 px-2 rounded transition-colors text-center
}
}
+
+.max-h-fit {
+ max-height: fit-content;
+}
diff --git a/resources/views/courses/manage/partials/sidebar.blade.php b/resources/views/courses/manage/partials/sidebar.blade.php
index db4a0db8..4ab6b274 100644
--- a/resources/views/courses/manage/partials/sidebar.blade.php
+++ b/resources/views/courses/manage/partials/sidebar.blade.php
@@ -7,8 +7,11 @@
No tasks are currently assigned to this track.
+ @else +Create tracks to organize your course activities.
+No tracks have been created for this course.
+ @else +