==UseCaseRealisations == ИД 1 == Откако корисникот ќе се најави оди да си одбере предмети. **Студентот клика на "Предмети"** - GET барање кон `/subjects` рута - `SubjectController@index()` акција се активира Си бира година, како и кариерна патека и смер. **Серверот прикажува листа на сите предмети** {{{#!php // SubjectController.php public function index(Request $request) { $subjects = Subject::with('prerequisites') ->orderBy('code') ->paginate(15); return view('admin.subjects.index', [ 'subjects' => $subjects ]); } }}} Откако ќе одбере му дава листа на предмети популирани од база, за неговиот смер и година. Сортирани по зимски и летен семестар. == ИД 2 == **Серверот филтрира по пребарување** {{{#!php // SubjectController.php public function index(Request $request) { $query = Subject::query(); // Филтрирање по пребарување if ($request->filled('search')) { $search = $request->input('search'); $query->where('code', 'LIKE', "%{$search}%") ->orWhere('name', 'LIKE', "%{$search}%") ->orWhere('name_mk', 'LIKE', "%{$search}%"); } // Филтрирање по година if ($request->filled('year')) { $query->where('year', $request->input('year')); } // Филтрирање по семестар if ($request->filled('semester')) { $query->where('semester_type', $request->input('semester')); } // Филтрирање по тип if ($request->filled('type')) { $query->where('subject_type', $request->input('type')); } $subjects = $query->with('prerequisites') ->orderBy('code') ->paginate(15); return view('admin.subjects.index', ['subjects' => $subjects]); } }}} Откако тој ќе си ги пополни си оди директно на Генерирај роадмап копчето. == ИД 3 == 1. **Студентот кликне на "Создај мој план/Roadmap"** - GET барање кон `/roadmap/create` - `RoadmapController@create()` се активира Тука е даден целиот опис на академскиот роадмап и кои се препорачани предмети според избирачката (опционално) кариера. **Серверот враќа форма за креирање** {{{#!php // RoadmapController.php public function create() { $studyPrograms = StudyProgram::all(); $careerPaths = CareerPath::all(); return view('roadmap.create', [ 'studyPrograms' => $studyPrograms, 'careerPaths' => $careerPaths ]); } }}} **Апликацијата прикажува ЧЕКОР 1: Избор програма и кариера** {{{#!html
@csrf
}}} **JavaScriptAJAX барање за предмети** {{{#!js // views/roadmap/create.blade.php document.getElementById('programSelect').addEventListener('change', function() { const programId = this.value; if (!programId) return; // AJAX fetch за да добиeме предмети од оваа програма fetch(`/api/study-program/${programId}/subjects`, { method: 'GET', headers: { 'Accept': 'application/json' } }) .then(response => response.json()) .then(data => { displaySubjectsByYear(data.subjects); }) .catch(error => console.error('Error:', error)); }); }}} **Серверот враќа JSON со предмети** {{{#!php // RoadmapController.php public function getSubjectsByProgram($programId) { $program = StudyProgram::findOrFail($programId); // Вчитај сите предмети од програмата со pivot info $subjects = $program->subjects() ->with('prerequisites') ->get() ->map(function($subject) { return [ 'id' => $subject->id, 'code' => $subject->code, 'name_mk' => $subject->name_mk, 'name_en' => $subject->name_en, 'year' => $subject->pivot->year, 'semester_type' => $subject->pivot->semester_type, 'type' => $subject->pivot->type, 'credits' => $subject->credits, 'prerequisites' => $subject->prerequisites->pluck('id') ]; }); return response()->json(['subjects' => $subjects]); } }}} \\ **JavaScript приказует ЧЕКОР 2: Избор завршени предмети** {{{#!js function displaySubjectsByYear(subjects) { // Организирај по години const subjectsByYear = {}; subjects.forEach(subject => { if (!subjectsByYear[subject.year]) { subjectsByYear[subject.year] = []; } subjectsByYear[subject.year].push(subject); }); // Креирај HTML за секоја година let html = '
'; Object.keys(subjectsByYear).sort().forEach(year => { html += `

Година ${year}

${['winter', 'summer'].map(semester => { const semesterSubjects = subjectsByYear[year] .filter(s => s.semester_type === semester); return `

${semester === 'winter' ? 'Зимски' : 'Летен'}

${semesterSubjects.map(subject => `
${subject.code} - ${subject.name_mk} (${subject.credits} ECTS) ${subject.type === 'mandatory' ? 'Задолжително' : 'Изборно'}
`).join('')}
`; }).join('')}
`; }); html += '
'; document.getElementById('subjectsContainer').innerHTML = html; document.getElementById('step2').style.display = 'block'; } }}} **Студентот кликне "ГЕНЕРИРАЈ МОЈ ПЛАН"** - JavaScript собира податоци од checkbox-ите \\ **JavaScript ја повикува POST барање** {{{#!js document.getElementById('roadmapForm').addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(this); // Конвертирај во JSON const data = { study_program_id: formData.get('study_program_id'), career_path_id: formData.get('career_path_id'), completed_subjects: Array.from(document.querySelectorAll( 'input[name="completed_subjects[]"]:checked' )).map(cb => cb.value), in_progress_subjects: Array.from(document.querySelectorAll( 'input[name="in_progress_subjects[]"]:checked' )).map(cb => cb.value) }; fetch('/roadmap', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify(data) }) .then(response => response.text()) .then(html => { document.body.innerHTML = html; }) .catch(error => console.error('Error:', error)); }); }}} **Серверот прима POST барање и генерира план** {{{#!php // RoadmapController.php public function store(Request $request) { $validated = $request->validate([ 'study_program_id' => 'required|exists:study_programs,id', 'career_path_id' => 'nullable|exists:career_paths,id', 'completed_subjects' => 'array', 'in_progress_subjects' => 'array' ]); $user = Auth::user(); $studyProgram = StudyProgram::find($validated['study_program_id']); // Избриши стари записи UserProgress::where('user_id', $user->id) ->where('study_program_id', $studyProgram->id) ->delete(); // Создај нови за завршени foreach ($validated['completed_subjects'] as $subjectId) { UserProgress::create([ 'user_id' => $user->id, 'subject_id' => $subjectId, 'study_program_id' => $studyProgram->id, 'career_path_id' => $validated['career_path_id'] ?? null, 'status' => 'completed', 'completed_at' => now() ]); } // Создај нови за во-прогрес foreach ($validated['in_progress_subjects'] as $subjectId) { UserProgress::create([ 'user_id' => $user->id, 'subject_id' => $subjectId, 'study_program_id' => $studyProgram->id, 'career_path_id' => $validated['career_path_id'] ?? null, 'status' => 'in_progress' ]); } // Генерирај три-слојна препорака $roadmap = $this->generateRoadmap($user, $studyProgram, $validated); $semesterRoadmap = $this->generateSemesterRoadmap($roadmap); return view('roadmap.show', [ 'roadmap' => $roadmap, 'semesterRoadmap' => $semesterRoadmap, 'studyProgram' => $studyProgram, 'careerPath' => $validated['career_path_id'] ? CareerPath::find($validated['career_path_id']) : null ]); } // ФИЛТРИРАЊЕ: Три слоја private function generateRoadmap($user, $studyProgram, $validated) { $roadmap = []; $completedIds = collect($validated['completed_subjects'])->map(fn($id) => (int)$id); $inProgressIds = collect($validated['in_progress_subjects'])->map(fn($id) => (int)$id); $allSubjects = $studyProgram->subjects()->with('prerequisites')->get(); foreach ($allSubjects as $subject) { // СЛОЈ 1: Проверка на година if ($subject->year > $studyProgram->duration_years) { continue; // Прескочи Year 5+ во 4-годишна програма } // СЛОЈ 2: Веќе завршено? if ($completedIds->contains($subject->id) || $inProgressIds->contains($subject->id)) { continue; // Не препорачувај што вече го взел } // СЛОЈ 3: Каријерна патека? if ($validated['career_path_id']) { $careerPath = CareerPath::find($validated['career_path_id']); $isElective = $subject->pivot->type === 'elective'; $isInPath = $careerPath->subjects->contains($subject->id); if ($isElective && !$isInPath) { continue; // Елективи не во патеката не се препорачуваат } } // Проверка на предуслови $prerequisites = $subject->prerequisites()->get(); $ready = true; $blockedBy = []; foreach ($prerequisites as $prereq) { if (!$completedIds->contains($prereq->id)) { $ready = false; $blockedBy[] = $prereq; } } // Додај во roadmap $roadmap[] = [ 'subject' => $subject, 'ready' => $ready, 'blocked_by' => $blockedBy, 'year' => $subject->pivot->year, 'semester' => $subject->pivot->semester_type ]; } // Сортирај: Ready first, потоа по година, потоа по ред usort($roadmap, function($a, $b) { if ($a['ready'] !== $b['ready']) { return $a['ready'] ? -1 : 1; // Ready прво } if ($a['year'] !== $b['year']) { return $a['year'] - $b['year']; // После по година } return 0; }); return $roadmap; } private function generateSemesterRoadmap($roadmap) { $semesterRoadmap = []; foreach ($roadmap as $item) { $year = $item['year']; $semester = $item['semester']; if (!isset($semesterRoadmap[$year])) { $semesterRoadmap[$year] = [ 'winter' => [], 'summer' => [] ]; } $semesterRoadmap[$year][$semester][] = $item; } return $semesterRoadmap; } }}} \\ Студентот може да ги види препораките** - Зелени: Готови за запишување оваа година - Црвени: Блокирани - мора прво да завршат [Предуслови] Апликацијата прикажува РЕЗУЛТАТ** - Резиме на напредок (X завршени, Y во-прогрес) - ECTS прогресна лента - Семестар-по-семестар план: - Година 1 Зимски: F23L3W001, F23L3W002... - Година 1 Летен: F23L3S003... - Препорачани следни чекори (зелено: готови, црвено: блокирани) \\ ### Исклучоци: - **Нелогиран корисник**: Редирекција кон `/login` - **Невалидна програма**: 404 грешка - **Нема избрани предмети**: Покажи порака да избере барем еден - **Конфликт на години**: Предмет е во повеќе години (обично задолжително) == ИД 4 == Одржување на информации за предмети (Админ) Овдека остануваат повеќето crud функционалности [[Image(admin1.png)]] CRUD за предмети [[Image(admin2.png)]] Уредување на предмет како и додавање на предуслови и кариера. [[Image(admin3.png)]] CRUD за студиски програми [[Image(admin4.png)]] Тука уредувачот може да додаде предмети на дадени студиски програми.