ИД 1
Откако корисникот ќе се најави оди да си одбере предмети. Студентот клика на "Предмети"
- GET барање кон
/subjectsрута SubjectController@index()акција се активира
Си бира година, како и кариерна патека и смер.
Серверот прикажува листа на сите предмети
// SubjectController.php
public function index(Request $request)
{
$subjects = Subject::with('prerequisites')
->orderBy('code')
->paginate(15);
return view('admin.subjects.index', [
'subjects' => $subjects
]);
}
Откако ќе одбере му дава листа на предмети популирани од база, за неговиот смер и година. Сортирани по зимски и летен семестар.
ИД 2
Серверот филтрира по пребарување
// 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
- Студентот кликне на "Создај мој план/Roadmap"
- GET барање кон
/roadmap/create RoadmapController@create()се активира
- GET барање кон
Тука е даден целиот опис на академскиот роадмап и кои се препорачани предмети според избирачката (опционално) кариера.
Серверот враќа форма за креирање
// RoadmapController.php
public function create()
{
$studyPrograms = StudyProgram::all();
$careerPaths = CareerPath::all();
return view('roadmap.create', [
'studyPrograms' => $studyPrograms,
'careerPaths' => $careerPaths
]);
}
Апликацијата прикажува ЧЕКОР 1: Избор програма и кариера
JavaScriptAJAX барање за предмети
// 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 со предмети
// 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: Избор завршени предмети
function displaySubjectsByYear(subjects) {
// Организирај по години
const subjectsByYear = {};
subjects.forEach(subject => {
if (!subjectsByYear[subject.year]) {
subjectsByYear[subject.year] = [];
}
subjectsByYear[subject.year].push(subject);
});
// Креирај HTML за секоја година
let html = '<div class="subjects-grid">';
Object.keys(subjectsByYear).sort().forEach(year => {
html += `<div class="year-section">
<h3>Година ${year}</h3>
${['winter', 'summer'].map(semester => {
const semesterSubjects = subjectsByYear[year]
.filter(s => s.semester_type === semester);
return `<div class="semester">
<h4>${semester === 'winter' ? 'Зимски' : 'Летен'}</h4>
${semesterSubjects.map(subject => `
<div class="subject-card">
<input type="checkbox" name="completed_subjects[]"
value="${subject.id}"
class="subject-checkbox">
<strong>${subject.code}</strong> - ${subject.name_mk}
<span class="credits">(${subject.credits} ECTS)</span>
<span class="type badge-${subject.type}">
${subject.type === 'mandatory' ? 'Задолжително' : 'Изборно'}
</span>
</div>
`).join('')}
</div>`;
}).join('')}
</div>`;
});
html += '</div>';
document.getElementById('subjectsContainer').innerHTML = html;
document.getElementById('step2').style.display = 'block';
}
Студентот кликне "ГЕНЕРИРАЈ МОЈ ПЛАН"
- JavaScript собира податоци од checkbox-ите
JavaScript ја повикува POST барање
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 барање и генерира план
// 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 функционалности
CRUD за предмети
Уредување на предмет како и додавање на предуслови и кариера.
CRUD за студиски програми
Тука уредувачот може да додаде предмети на дадени студиски програми.
