Changes between Version 2 and Version 3 of UseCaseRealisations


Ignore:
Timestamp:
12/29/25 14:23:48 (2 weeks ago)
Author:
214004
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • UseCaseRealisations

    v2 v3  
    44
    55Откако корисникот ќе се најави оди да си одбере предмети.
    6 [[Image(1.png)]]
     6**Студентот клика на "Предмети"**
     7   - GET барање кон `/subjects` рута
     8   - `SubjectController@index()` акција се активира
    79
    810Си бира година, како и кариерна патека и смер.
    911
    10 [[Image(2.png)]]
     12**Серверот прикажува листа на сите предмети**
     13   {{{#!php
     14   // SubjectController.php
     15   public function index(Request $request)
     16   {
     17       $subjects = Subject::with('prerequisites')
     18           ->orderBy('code')
     19           ->paginate(15);
     20       
     21       return view('admin.subjects.index', [
     22           'subjects' => $subjects
     23       ]);
     24   }
     25   }}}
    1126
    1227Откако ќе одбере му дава листа на предмети популирани од база, за неговиот смер и година. Сортирани по зимски и летен семестар.
     
    1530== ИД 2 ==
    1631
    17 [[Image(3.png)]]
     32**Серверот филтрира по пребарување**
     33   {{{#!php
     34   // SubjectController.php
     35   public function index(Request $request)
     36   {
     37       $query = Subject::query();
     38       
     39       // Филтрирање по пребарување
     40       if ($request->filled('search')) {
     41           $search = $request->input('search');
     42           $query->where('code', 'LIKE', "%{$search}%")
     43                 ->orWhere('name', 'LIKE', "%{$search}%")
     44                 ->orWhere('name_mk', 'LIKE', "%{$search}%");
     45       }
     46       
     47       // Филтрирање по година
     48       if ($request->filled('year')) {
     49           $query->where('year', $request->input('year'));
     50       }
     51       
     52       // Филтрирање по семестар
     53       if ($request->filled('semester')) {
     54           $query->where('semester_type', $request->input('semester'));
     55       }
     56       
     57       // Филтрирање по тип
     58       if ($request->filled('type')) {
     59           $query->where('subject_type', $request->input('type'));
     60       }
     61       
     62       $subjects = $query->with('prerequisites')
     63                        ->orderBy('code')
     64                        ->paginate(15);
     65       
     66       return view('admin.subjects.index', ['subjects' => $subjects]);
     67   }
     68   }}}
    1869Откако тој ќе си ги пополни си оди директно на Генерирај роадмап копчето.
    1970
     
    2273
    2374
    24 [[Image(4.png)]]
    25 
     75
     761. **Студентот кликне на "Создај мој план/Roadmap"**
     77   - GET барање кон `/roadmap/create`
     78   - `RoadmapController@create()` се активира
    2679Тука е даден целиот опис на академскиот роадмап и кои се препорачани предмети според избирачката (опционално) кариера.
    2780
    28 
    29 [[Image(5.png)]]
    30 Продолжени сите останати семестри и приказ на кои предмети се препорачани како и предуслов. Во зависност ако студентот ги има завршено ќе биде достапно, во спротивно нема предуслов.
    31 
    32 
    33 [[Image(6.png)]]
    34 Невозможни предмети бидејќи студентот не исполнува даден предуслов за предмет.
     81**Серверот враќа форма за креирање**
     82   {{{#!php
     83   // RoadmapController.php
     84   public function create()
     85   {
     86       $studyPrograms = StudyProgram::all();
     87       $careerPaths = CareerPath::all();
     88       
     89       return view('roadmap.create', [
     90           'studyPrograms' => $studyPrograms,
     91           'careerPaths' => $careerPaths
     92       ]);
     93   }
     94   }}}
     95**Апликацијата прикажува ЧЕКОР 1: Избор програма и кариера**
     96   {{{#!html
     97   <!-- resources/views/roadmap/create.blade.php -->
     98   <form method="POST" action="/roadmap" id="roadmapForm">
     99       @csrf
     100       
     101       <div class="step-1">
     102           <label>Избери студирана програма:</label>
     103           <select name="study_program_id" id="programSelect" required>
     104               <option value="">-- Избери програма --</option>
     105               @foreach($studyPrograms as $program)
     106                   <option value="{{ $program->id }}">
     107                       {{ $program->name_mk }} ({{ $program->duration_years }} години)
     108                   </option>
     109               @endforeach
     110           </select>
     111           
     112           <label>Избери каријерна патека (опционално):</label>
     113           <select name="career_path_id" id="careerPathSelect">
     114               <option value="">-- Без патека --</option>
     115               @foreach($careerPaths as $path)
     116                   <option value="{{ $path->id }}">{{ $path->name }}</option>
     117               @endforeach
     118           </select>
     119       </div>
     120   </form>
     121   }}}
     122
     123**JavaScriptAJAX барање за предмети**
     124   {{{#!js
     125   // views/roadmap/create.blade.php
     126   document.getElementById('programSelect').addEventListener('change', function() {
     127       const programId = this.value;
     128       
     129       if (!programId) return;
     130       
     131       // AJAX fetch за да добиeме предмети од оваа програма
     132       fetch(`/api/study-program/${programId}/subjects`, {
     133           method: 'GET',
     134           headers: {
     135               'Accept': 'application/json'
     136           }
     137       })
     138       .then(response => response.json())
     139       .then(data => {
     140           displaySubjectsByYear(data.subjects);
     141       })
     142       .catch(error => console.error('Error:', error));
     143   });
     144   }}}
     145
     146
     147**Серверот враќа JSON со предмети**
     148   {{{#!php
     149   // RoadmapController.php
     150   public function getSubjectsByProgram($programId)
     151   {
     152       $program = StudyProgram::findOrFail($programId);
     153       
     154       // Вчитај сите предмети од програмата со pivot info
     155       $subjects = $program->subjects()
     156           ->with('prerequisites')
     157           ->get()
     158           ->map(function($subject) {
     159               return [
     160                   'id' => $subject->id,
     161                   'code' => $subject->code,
     162                   'name_mk' => $subject->name_mk,
     163                   'name_en' => $subject->name_en,
     164                   'year' => $subject->pivot->year,
     165                   'semester_type' => $subject->pivot->semester_type,
     166                   'type' => $subject->pivot->type,
     167                   'credits' => $subject->credits,
     168                   'prerequisites' => $subject->prerequisites->pluck('id')
     169               ];
     170           });
     171       
     172       return response()->json(['subjects' => $subjects]);
     173   }
     174   }}}
     175\\
     176
     177
     178**JavaScript приказует ЧЕКОР 2: Избор завршени предмети**
     179   {{{#!js
     180   function displaySubjectsByYear(subjects) {
     181       // Организирај по години
     182       const subjectsByYear = {};
     183       
     184       subjects.forEach(subject => {
     185           if (!subjectsByYear[subject.year]) {
     186               subjectsByYear[subject.year] = [];
     187           }
     188           subjectsByYear[subject.year].push(subject);
     189       });
     190       
     191       // Креирај HTML за секоја година
     192       let html = '<div class="subjects-grid">';
     193       
     194       Object.keys(subjectsByYear).sort().forEach(year => {
     195           html += `<div class="year-section">
     196               <h3>Година ${year}</h3>
     197               ${['winter', 'summer'].map(semester => {
     198                   const semesterSubjects = subjectsByYear[year]
     199                       .filter(s => s.semester_type === semester);
     200                   
     201                   return `<div class="semester">
     202                       <h4>${semester === 'winter' ? 'Зимски' : 'Летен'}</h4>
     203                       ${semesterSubjects.map(subject => `
     204                           <div class="subject-card">
     205                               <input type="checkbox" name="completed_subjects[]"
     206                                      value="${subject.id}"
     207                                      class="subject-checkbox">
     208                               <strong>${subject.code}</strong> - ${subject.name_mk}
     209                               <span class="credits">(${subject.credits} ECTS)</span>
     210                               <span class="type badge-${subject.type}">
     211                                   ${subject.type === 'mandatory' ? 'Задолжително' : 'Изборно'}
     212                               </span>
     213                           </div>
     214                       `).join('')}
     215                   </div>`;
     216               }).join('')}
     217           </div>`;
     218       });
     219       
     220       html += '</div>';
     221       
     222       document.getElementById('subjectsContainer').innerHTML = html;
     223       document.getElementById('step2').style.display = 'block';
     224   }
     225   }}}
     226
     227**Студентот кликне "ГЕНЕРИРАЈ МОЈ ПЛАН"**
     228    - JavaScript собира податоци од checkbox-ите
     229
     230
     231
     232
     233\\
     234**JavaScript ја повикува POST барање**
     235    {{{#!js
     236    document.getElementById('roadmapForm').addEventListener('submit', function(e) {
     237        e.preventDefault();
     238       
     239        const formData = new FormData(this);
     240       
     241        // Конвертирај во JSON
     242        const data = {
     243            study_program_id: formData.get('study_program_id'),
     244            career_path_id: formData.get('career_path_id'),
     245            completed_subjects: Array.from(document.querySelectorAll(
     246                'input[name="completed_subjects[]"]:checked'
     247            )).map(cb => cb.value),
     248            in_progress_subjects: Array.from(document.querySelectorAll(
     249                'input[name="in_progress_subjects[]"]:checked'
     250            )).map(cb => cb.value)
     251        };
     252       
     253        fetch('/roadmap', {
     254            method: 'POST',
     255            headers: {
     256                'Content-Type': 'application/json',
     257                'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
     258            },
     259            body: JSON.stringify(data)
     260        })
     261        .then(response => response.text())
     262        .then(html => {
     263            document.body.innerHTML = html;
     264        })
     265        .catch(error => console.error('Error:', error));
     266    });
     267    }}}
     268
     269**Серверот прима POST барање и генерира план**
     270    {{{#!php
     271    // RoadmapController.php
     272    public function store(Request $request)
     273    {
     274        $validated = $request->validate([
     275            'study_program_id' => 'required|exists:study_programs,id',
     276            'career_path_id' => 'nullable|exists:career_paths,id',
     277            'completed_subjects' => 'array',
     278            'in_progress_subjects' => 'array'
     279        ]);
     280       
     281        $user = Auth::user();
     282        $studyProgram = StudyProgram::find($validated['study_program_id']);
     283       
     284        // Избриши стари записи
     285        UserProgress::where('user_id', $user->id)
     286            ->where('study_program_id', $studyProgram->id)
     287            ->delete();
     288       
     289        // Создај нови за завршени
     290        foreach ($validated['completed_subjects'] as $subjectId) {
     291            UserProgress::create([
     292                'user_id' => $user->id,
     293                'subject_id' => $subjectId,
     294                'study_program_id' => $studyProgram->id,
     295                'career_path_id' => $validated['career_path_id'] ?? null,
     296                'status' => 'completed',
     297                'completed_at' => now()
     298            ]);
     299        }
     300       
     301        // Создај нови за во-прогрес
     302        foreach ($validated['in_progress_subjects'] as $subjectId) {
     303            UserProgress::create([
     304                'user_id' => $user->id,
     305                'subject_id' => $subjectId,
     306                'study_program_id' => $studyProgram->id,
     307                'career_path_id' => $validated['career_path_id'] ?? null,
     308                'status' => 'in_progress'
     309            ]);
     310        }
     311       
     312        // Генерирај три-слојна препорака
     313        $roadmap = $this->generateRoadmap($user, $studyProgram, $validated);
     314        $semesterRoadmap = $this->generateSemesterRoadmap($roadmap);
     315       
     316        return view('roadmap.show', [
     317            'roadmap' => $roadmap,
     318            'semesterRoadmap' => $semesterRoadmap,
     319            'studyProgram' => $studyProgram,
     320            'careerPath' => $validated['career_path_id']
     321                ? CareerPath::find($validated['career_path_id'])
     322                : null
     323        ]);
     324    }
     325   
     326    // ФИЛТРИРАЊЕ: Три слоја
     327    private function generateRoadmap($user, $studyProgram, $validated)
     328    {
     329        $roadmap = [];
     330        $completedIds = collect($validated['completed_subjects'])->map(fn($id) => (int)$id);
     331        $inProgressIds = collect($validated['in_progress_subjects'])->map(fn($id) => (int)$id);
     332       
     333        $allSubjects = $studyProgram->subjects()->with('prerequisites')->get();
     334       
     335        foreach ($allSubjects as $subject) {
     336            // СЛОЈ 1: Проверка на година
     337            if ($subject->year > $studyProgram->duration_years) {
     338                continue; // Прескочи Year 5+ во 4-годишна програма
     339            }
     340           
     341            // СЛОЈ 2: Веќе завршено?
     342            if ($completedIds->contains($subject->id) || $inProgressIds->contains($subject->id)) {
     343                continue; // Не препорачувај што вече го взел
     344            }
     345           
     346            // СЛОЈ 3: Каријерна патека?
     347            if ($validated['career_path_id']) {
     348                $careerPath = CareerPath::find($validated['career_path_id']);
     349                $isElective = $subject->pivot->type === 'elective';
     350                $isInPath = $careerPath->subjects->contains($subject->id);
     351               
     352                if ($isElective && !$isInPath) {
     353                    continue; // Елективи не во патеката не се препорачуваат
     354                }
     355            }
     356           
     357            // Проверка на предуслови
     358            $prerequisites = $subject->prerequisites()->get();
     359            $ready = true;
     360            $blockedBy = [];
     361           
     362            foreach ($prerequisites as $prereq) {
     363                if (!$completedIds->contains($prereq->id)) {
     364                    $ready = false;
     365                    $blockedBy[] = $prereq;
     366                }
     367            }
     368           
     369            // Додај во roadmap
     370            $roadmap[] = [
     371                'subject' => $subject,
     372                'ready' => $ready,
     373                'blocked_by' => $blockedBy,
     374                'year' => $subject->pivot->year,
     375                'semester' => $subject->pivot->semester_type
     376            ];
     377        }
     378       
     379        // Сортирај: Ready first, потоа по година, потоа по ред
     380        usort($roadmap, function($a, $b) {
     381            if ($a['ready'] !== $b['ready']) {
     382                return $a['ready'] ? -1 : 1; // Ready прво
     383            }
     384            if ($a['year'] !== $b['year']) {
     385                return $a['year'] - $b['year']; // После по година
     386            }
     387            return 0;
     388        });
     389       
     390        return $roadmap;
     391    }
     392   
     393    private function generateSemesterRoadmap($roadmap)
     394    {
     395        $semesterRoadmap = [];
     396       
     397        foreach ($roadmap as $item) {
     398            $year = $item['year'];
     399            $semester = $item['semester'];
     400           
     401            if (!isset($semesterRoadmap[$year])) {
     402                $semesterRoadmap[$year] = [
     403                    'winter' => [],
     404                    'summer' => []
     405                ];
     406            }
     407           
     408            $semesterRoadmap[$year][$semester][] = $item;
     409        }
     410       
     411        return $semesterRoadmap;
     412    }
     413    }}}
     414\\
     415
     416Студентот може да ги види препораките**
     417    - Зелени: Готови за запишување оваа година
     418    - Црвени: Блокирани - мора прво да завршат [Предуслови]
     419
     420Апликацијата прикажува РЕЗУЛТАТ**
     421    - Резиме на напредок (X завршени, Y во-прогрес)
     422    - ECTS прогресна лента
     423    - Семестар-по-семестар план:
     424      - Година 1 Зимски: F23L3W001, F23L3W002...
     425      - Година 1 Летен: F23L3S003...
     426    - Препорачани следни чекори (зелено: готови, црвено: блокирани)
     427
     428\\
     429### Исклучоци:
     430- **Нелогиран корисник**: Редирекција кон `/login`
     431- **Невалидна програма**: 404 грешка
     432- **Нема избрани предмети**: Покажи порака да избере барем еден
     433- **Конфликт на години**: Предмет е во повеќе години (обично задолжително)
    35434
    36435
     
    38437Одржување на информации за предмети (Админ)
    39438
     439Овдека остануваат повеќето crud функционалности
     440
     441
     442
     443
     444
    40445[[Image(admin1.png)]]
    41446CRUD за предмети