From eba8c2bd8e1dfce1fb5687da9c87c73a52a45dcc Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:10:41 -0500 Subject: [PATCH 01/37] =?UTF-8?q?=F0=9F=93=8A=20Adds=20student=20research?= =?UTF-8?q?=20charts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Helpers/Semester.php | 49 +++ app/Http/Controllers/InsightsController.php | 35 ++ ...AcceptedAndFollowUpAppsPercentageChart.php | 46 ++ .../AcceptedStudentAppsPercentageChart.php | 55 +++ app/Http/Livewire/ChartFilters.php | 24 + app/Http/Livewire/InsightsFilter.php | 25 ++ .../StudentAppsViewedNotViewedChart.php | 47 ++ app/Http/Livewire/StudentsAppCountChart.php | 49 +++ .../Livewire/StudentsAppFilingStatusChart.php | 55 +++ app/Insights/Insight.php | 30 ++ .../ProfileStudentInsight.php | 36 ++ .../StudentDataInsight.php | 415 ++++++++++++++++++ app/Profile.php | 7 + app/Student.php | 50 +++ app/StudentData.php | 44 ++ package-lock.json | 30 ++ package.json | 1 + public/css/app.css | 5 + public/mix-manifest.json | 2 +- resources/assets/sass/_core.scss | 5 + resources/views/insights/admin-chart.php | 1 + resources/views/insights/index.blade.php | 88 ++++ resources/views/insights/profiles-chart.php | 1 + resources/views/layout.blade.php | 1 + ...-follow-up-apps-percentage-chart.blade.php | 106 +++++ .../views/livewire/insights-filter.blade.php | 141 ++++++ ...ent-apps-viewed-not-viewed-chart.blade.php | 127 ++++++ .../students-app-count-chart.blade.php | 51 +++ ...students-app-filing-status-chart.blade.php | 51 +++ resources/views/nav.blade.php | 3 + routes/web.php | 2 + 31 files changed, 1581 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/InsightsController.php create mode 100644 app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php create mode 100644 app/Http/Livewire/AcceptedStudentAppsPercentageChart.php create mode 100644 app/Http/Livewire/ChartFilters.php create mode 100644 app/Http/Livewire/InsightsFilter.php create mode 100644 app/Http/Livewire/StudentAppsViewedNotViewedChart.php create mode 100644 app/Http/Livewire/StudentsAppCountChart.php create mode 100644 app/Http/Livewire/StudentsAppFilingStatusChart.php create mode 100644 app/Insights/Insight.php create mode 100644 app/Insights/StudentApplications/ProfileStudentInsight.php create mode 100644 app/Insights/StudentApplications/StudentDataInsight.php create mode 100644 resources/views/insights/admin-chart.php create mode 100644 resources/views/insights/index.blade.php create mode 100644 resources/views/insights/profiles-chart.php create mode 100644 resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php create mode 100644 resources/views/livewire/insights-filter.blade.php create mode 100644 resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php create mode 100644 resources/views/livewire/students-app-count-chart.blade.php create mode 100644 resources/views/livewire/students-app-filing-status-chart.blade.php diff --git a/app/Helpers/Semester.php b/app/Helpers/Semester.php index 91fca00a..29dc18c0 100644 --- a/app/Helpers/Semester.php +++ b/app/Helpers/Semester.php @@ -129,4 +129,53 @@ public static function date(string $name, bool $start_of_season = true) return Carbon::parse($month_and_day . ' ' . $parsed['year']->year); } + + /** + * Sort chronologically an array of semesters in the 'Semester YY' format + * @param array $semesters + * @return array + */ + public static function sortCollectionWithSemestersKeyChronologically() { + return + function($a, $b) { + // Extract the year from the semesters + [$semesterA, $yearA] = explode(' ', $a); + [$semesterB, $yearB] = explode(' ', $b); + + // Define the semester order + $order = ['Spring' => 1, 'Summer' => 2, 'Fall' => 3]; + + // Sort by year first, and by semester order if the years are the same + if ($yearA === $yearB) { + return $order[$semesterA] <=> $order[$semesterB]; + } + + return $yearA <=> $yearB; + }; + } + + /** + * Sort chronologically an array of semesters in the 'Semester YY' format + * @param array $semesters + * @return array + */ + public static function sortSemestersChronologically($semesters) { + return + usort($semesters, function ($a, $b) { + // Extract year and season + preg_match('/(Spring|Summer|Fall) (\d+)/', $a, $matchesA); + preg_match('/(Spring|Summer|Fall) (\d+)/', $b, $matchesB); + + // Map seasons to an order + $seasonOrder = ['Spring' => 1, 'Summer' => 2, 'Fall' => 3]; + + // Compare by year first + if ($matchesA[2] != $matchesB[2]) { + return $matchesA[2] - $matchesB[2]; + } + + // If years are the same, compare by season + return $seasonOrder[$matchesA[1]] - $seasonOrder[$matchesB[1]]; + }); + } } diff --git a/app/Http/Controllers/InsightsController.php b/app/Http/Controllers/InsightsController.php new file mode 100644 index 00000000..c4a7472b --- /dev/null +++ b/app/Http/Controllers/InsightsController.php @@ -0,0 +1,35 @@ +middleware('auth'); + + $this->middleware('can:viewAdminIndex,App\LogEntry')->only('index'); + } + + /** + * Display a listing of the resource. + */ + public function index(Request $request): ViewContract + { + + $schools_options = StudentDataInsight::getLabels('school')->toArray(); + $semesters_options = StudentDataInsight::getLabels('semester')->toArray(); + // $filing_status_params = ["accepted", "maybe later", "not interested", "new", "follow up"]; + + return view('insights.index', compact('schools_options', 'semesters_options')); + } +} diff --git a/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php new file mode 100644 index 00000000..081217fe --- /dev/null +++ b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php @@ -0,0 +1,46 @@ +selected_semesters = $selected_semesters; + $this->selected_schools = $selected_schools; + + $data = $this->getData(); + $this->data = $data; + $this->labels = ['Accepted & Follow Up', 'Other']; + + $this->emit('refreshChart5', $this->data, $this->labels); + } + + public function getData() + { + $report = new StudentDataInsight(); + return $report->getAppsForSemestersAndSchoolsWithFilingStatuses($this->selected_semesters, $this->selected_schools, ["accepted", "follow up"]); + } + + public function render() + { + $data = $this->getData(); + $this->data = $data; + $this->labels = ['Accepted & Follow Up', 'Other']; + + return view('livewire.accepted-and-follow-up-apps-percentage-chart'); + } +} diff --git a/app/Http/Livewire/AcceptedStudentAppsPercentageChart.php b/app/Http/Livewire/AcceptedStudentAppsPercentageChart.php new file mode 100644 index 00000000..cb33e58f --- /dev/null +++ b/app/Http/Livewire/AcceptedStudentAppsPercentageChart.php @@ -0,0 +1,55 @@ +selected_semesters = [Semester::current()]; + //$this->selected_schools = $selected_schools; + // $this->selected_filing_statuses = ["accepted"]; + } + + public function refreshChart3($data, $labels) {} + + public function refreshData3($selected_semesters, $selected_schools) { + $this->selected_semesters = $selected_semesters; + $this->selected_schools = $selected_schools; + + $data = $this->getData(); + + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + $this->emit('refreshChart3', $this->data, $this->labels); + } + + public function getData() + { + $report = new StudentDataInsight(); + return $report->transformDataAcceptedInCurrentSeason($this->selected_semesters, $this->selected_schools); + } + + public function render() + { + $data = $this->getData(); + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + return view('livewire.accepted-student-apps-percentage-chart'); + } +} diff --git a/app/Http/Livewire/ChartFilters.php b/app/Http/Livewire/ChartFilters.php new file mode 100644 index 00000000..cc689e77 --- /dev/null +++ b/app/Http/Livewire/ChartFilters.php @@ -0,0 +1,24 @@ +items = $items; + } + + public function toLivewire() + { + return $this->items; + } + + public static function fromLivewire($value) + { + return new static($value); + } +} \ No newline at end of file diff --git a/app/Http/Livewire/InsightsFilter.php b/app/Http/Livewire/InsightsFilter.php new file mode 100644 index 00000000..ae48e827 --- /dev/null +++ b/app/Http/Livewire/InsightsFilter.php @@ -0,0 +1,25 @@ +emitTo('students-app-count-chart', 'refreshData2', $selected_semesters, $selected_schools); + $this->emitTo('students-app-filing-status-chart', 'refreshData1', $selected_semesters, $selected_schools); + $this->emitTo('accepted-and-follow-up-apps-percentage-chart', 'refreshData5', $selected_semesters, $selected_schools); + $this->emitTo('student-apps-viewed-not-viewed-chart', 'refreshData4', $selected_semesters, $selected_schools); + } + + public function render() + { + return view('livewire.insights-filter'); + } +} diff --git a/app/Http/Livewire/StudentAppsViewedNotViewedChart.php b/app/Http/Livewire/StudentAppsViewedNotViewedChart.php new file mode 100644 index 00000000..8397ef0a --- /dev/null +++ b/app/Http/Livewire/StudentAppsViewedNotViewedChart.php @@ -0,0 +1,47 @@ +selected_semesters = $selected_semesters; + $this->selected_schools = $selected_schools; + + $data = $this->getData(); + + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + $this->emit('refreshChart4', $this->data, $this->labels); + } + + public function getData() + { + $report = new StudentDataInsight(); + return $report->getViewedAndNotViewedApps($this->selected_semesters, $this->selected_schools); + } + + public function render() + { + $data = $this->getData(); + + $this->data = $data['datasets']; + $this->labels = $data['labels']; + return view('livewire.student-apps-viewed-not-viewed-chart'); + } +} diff --git a/app/Http/Livewire/StudentsAppCountChart.php b/app/Http/Livewire/StudentsAppCountChart.php new file mode 100644 index 00000000..2cb1018b --- /dev/null +++ b/app/Http/Livewire/StudentsAppCountChart.php @@ -0,0 +1,49 @@ +selected_semesters = $selected_semesters; + $this->selected_schools = $selected_schools; + + $data = $this->getData(); + + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + $this->emit('refreshChart2', $this->data, $this->labels); + } + + public function getData() + { + $report = new StudentDataInsight(); + return $report->getAppsBySemestersAndSchools($this->selected_semesters, $this->selected_schools, "count", "submitted"); + } + + public function render() + { + $data = $this->getData(); + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + return view('livewire.students-app-count-chart', [ + 'data' => $this->data, + 'labels' => $this->labels, + ]); + } +} diff --git a/app/Http/Livewire/StudentsAppFilingStatusChart.php b/app/Http/Livewire/StudentsAppFilingStatusChart.php new file mode 100644 index 00000000..ad2e548b --- /dev/null +++ b/app/Http/Livewire/StudentsAppFilingStatusChart.php @@ -0,0 +1,55 @@ +selected_filing_statuses = ["accepted", "maybe later", "not interested", "new", "follow up"]; + } + + public function refreshChart1($data, $labels) {} + + public function refreshData1($selected_semesters, $selected_schools) { + + $this->selected_semesters = $selected_semesters; + $this->selected_schools = $selected_schools; + + $data = $this->getData(); + + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + $this->emit('refreshChart1', $this->data, $this->labels); + } + + public function getData() + { + $report = new StudentDataInsight(); + return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_filing_statuses, $this->selected_schools, "count", "submitted"); + } + + public function render() + { + $data = $this->getData(); + $this->data = $data['datasets']; + $this->labels = $data['labels']; + + return view('livewire.students-app-filing-status-chart', [ + 'data' => $this->data, + 'labels' => $this->labels, + ]); + } +} diff --git a/app/Insights/Insight.php b/app/Insights/Insight.php new file mode 100644 index 00000000..05ee6f78 --- /dev/null +++ b/app/Insights/Insight.php @@ -0,0 +1,30 @@ + $chart_options['label'], + 'type' => $chart_options['type'], + 'backgroundColor' => $chart_options['backgroundColor'], + 'borderColor' => $chart_options['borderColor'], + 'data' => $chart_options['data'], + ]; + } + $data['labels'] = $chart_options['labels']; + + return $data; + } + +} diff --git a/app/Insights/StudentApplications/ProfileStudentInsight.php b/app/Insights/StudentApplications/ProfileStudentInsight.php new file mode 100644 index 00000000..3a3c6daf --- /dev/null +++ b/app/Insights/StudentApplications/ProfileStudentInsight.php @@ -0,0 +1,36 @@ + $chart_options['label'], + 'backgroundColor' => $chart_options['backgroundColor'], + 'borderColor' => $chart_options['borderColor'], + 'data' => $chart_options['data'], + ] + ]; + + $data['labels'] = $chart_options['labels']; + + return $data; + } + + + + public function semesterToDate($semester) + { + return Semester::date($semester)?->toDateString(); + } + +} diff --git a/app/Insights/StudentApplications/StudentDataInsight.php b/app/Insights/StudentApplications/StudentDataInsight.php new file mode 100644 index 00000000..8300a2f3 --- /dev/null +++ b/app/Insights/StudentApplications/StudentDataInsight.php @@ -0,0 +1,415 @@ + 'Applications for Semester', + 'type' => 'bar', + 'backgroundColor' => 'rgba(15,64,97,255)', + 'borderColor' => 'rgba(15,64,97,255)', + // 'data' => $this->getDataSemesterAndSchool($semesters_params, $schools_params, $type, $status), + //'labels' => $this->getLabels($semester), + ]; + + return $this->getDataArray($insight_parameters); + } + + //Query to retrieve student applications for given semesters and schools + public function appsForSemestersAndSchools($semesters_params, $schools_params) + { + return Student::query() + ->submitted() + ->withWhereHas('research_profile', function($q) use ($semesters_params, $schools_params) { + $q->where(function($q) use ($semesters_params) { + foreach ($semesters_params as $semester) { + $q->orDataContains('semesters', $semester); + } + }); + $q->where(function($q) use ($schools_params) { + foreach ($schools_params as $school) { + $q->orDataContains('schools', $school); + } + }); + }); + } + + /** + * DOUGHNUT CHART #1 DATA - APPLICATIONS PERCENTAGE BY FILING STATUS FOR SEMESTERS AND SCHOOLS + */ + + public function getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + $fs = implode('-', $filing_status_params); + + return Cache::remember( + "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}-{$fs}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->withWhereHas('stats', function($q) use ($filing_status_params) { + $q->whereNotNull('data->status_history'); + $q->where(function($q) use ($filing_status_params) { + foreach ($filing_status_params as $key => $filing_status) { + $q = $key == 0 ? + $q->whereJsonContains('data->status_history', ['new_status' => $filing_status]) : + $q->orWhereJsonContains('data->status_history', ['new_status' => $filing_status]); + } + }); + })->get() + ); + } + + public function getCachedAppsForSemestersAndSchoolsWithoutFilingStatuses($semesters_params, $schools_params, $filing_status_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-without-flstatus-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->withWhereHas('stats', function($q) use ($filing_status_params) { + $q->whereNot(function($q) use ($filing_status_params) { + foreach ($filing_status_params as $key => $filing_status) { + $q = $key == 0 ? + $q->whereJsonContains('data->status_history', ['new_status' => $filing_status]) : + $q->orWhereJsonContains('data->status_history', ['new_status' => $filing_status]); + } + }); + })->get() + ); + } + + public function groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps) + { + $semesters_params_start_end = $this->semestersParamsStartAndEnd($semesters_params); + + $filing_status_count = 0; + + foreach ($semesters_params_start_end as $semester_start_end) { + $start_date = $semester_start_end['start']; + $end_date = $semester_start_end['end']; + + $filing_status_count = $filing_status_count + + $student_apps->where(function($app) use ($filing_status_params, $start_date, $end_date, &$filing_status_count) { + return collect($app->stats->data['status_history']) + ->groupBy('profile') + ->where(function($group) use ($app, $filing_status_params, $start_date, $end_date, &$filing_status_count) { + $last_update = $group->sortByDesc(function ($item) { + return Carbon::parse($item['updated_at']); + })->first(); + $filing_date = Carbon::parse($last_update['updated_at']); + // if (in_array($last_update['new_status'], $filing_status_params) && $filing_date->between($start_date, $end_date)) { + // // dump($app->stats->id); + // $filing_status_count = $filing_status_count + 1; + // } + return (in_array($last_update['new_status'], $filing_status_params) && $filing_date->between($start_date, $end_date)); + })->count(); + })->count(); + + } + return $filing_status_count; + } + + public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params) + { + $student_apps = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params); + $filing_status_params_total = $this->groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps); + + $filing_status_params = ['maybe later', 'not interested']; + $student_apps = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params); + $others_total = $this->groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps); + + // $filing_status_params_total = $grouped_student_apps_with_flstatus; + // $others_total = $grouped_student_apps_without_flstatus; + // $filing_status_params_total = array_sum($grouped_student_apps_with_flstatus); + // $others_total = array_sum($grouped_student_apps_without_flstatus); + + // $total = $filing_status_params_total + $others_total; + + // if ($total > 0) { // Avoid division by zero + // $result[0] = round(($filing_status_params_total / $total) * 100); + // $result[1] = round(($others_total / $total) * 100); + // } else { + // $result[0] = 0; + // $result[1] = 0; + // } + return [$filing_status_params_total, $others_total]; + } + + /** + * DOUGHNUT CHART #2 DATA - APPLICATIONS COUNT VIEWED AND NOT FOR SEMESTERS AND SCHOOLS + */ + + public function getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-not-viewed-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->where(function($query) { + $query->whereHas('stats', function($q) { + $q->whereNull('data->views'); + }); + $query->orDoesntHave('stats'); + })->count() + ); + } + + public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-viewed-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->where(function($query) { + $query->whereHas('stats', function($q) { + $q->whereNotNull('data->views'); + $q->where('data->views', '>', 0); + }); + })->count() + ); + } + + public function getViewedAndNotViewedApps(array $semesters_params, array $schools_params,) + { + $result = []; + + $submitted_not_viewed = $this->getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params); + + $submitted_and_viewed = $this->getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params); + + // $total = $submitted_and_viewed + $submitted_not_viewed; + + // if ($total > 0) { // Avoid division by zero + // $result[0] = round(($submitted_and_viewed / $total) * 100); + // $result[1] = round(($submitted_not_viewed / $total) * 100); + // } else { + // $result[0] = 0; + // $result[1] = 0; + // } + + return [ + 'labels' => [ 'Viewed', 'Not Viewed' ], + 'datasets' => [$submitted_and_viewed, $submitted_not_viewed], + ]; + } + + /** + * BAR CHART #3 DATA - APPLICATIONS COUNT BY SEMESTER AND SCHOOL + */ + + public function getCachedAppsForSemestersAndSchools($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->get() + ); + } + + public function groupAppsBySemestersAndSchools(array $semesters_params, array $schools_params) + { + $student_applications = $this->getCachedAppsForSemestersAndSchools($semesters_params, $schools_params); + + return $student_applications->flatMap(function ($application) use ($semesters_params, $schools_params) { + + if (!empty($application->research_profile->data['semesters']) && !empty($application->research_profile->data['schools'])) { + + $semesters_values = array_intersect($application->research_profile->data['semesters'], $semesters_params); + $schools_values = array_intersect($application->research_profile->data['schools'], $schools_params); + + return array_map(function ($school_value) use ($application, $semesters_values) { + return array_map(function ($semester_value) use ($application, $school_value) { + return [ + 'id' => $application->id, + 'semester' => $semester_value, + 'school' => $school_value, + ]; + }, $semesters_values); + }, $schools_values); + } + })->flatten(1); + } + + public function getAppsBySemestersAndSchools(array $semesters_params, array $schools_params, $type, $status) + { + $applications = $this->groupAppsBySemestersAndSchools($semesters_params, $schools_params); + + $counted_apps = $applications + ->groupBy(['semester', 'school']) + ->map(function ($semester_group) { + return $semester_group->map(function ($school_group) { + return $school_group->count(); + }); + }); + $semesters_sort_closure = Semester::sortCollectionWithSemestersKeyChronologically(); + $all_semesters = $applications->pluck('semester')->unique()->sort()->values(); + // $all_semesters = $applications->pluck('semester')->unique()->sortBy($semesters_sort_closure)->values(); + $all_schools = $applications->pluck('school')->unique()->sort()->values(); + + $datasets = $all_schools->mapWithKeys(function ($school) use ($all_semesters) { // Initialize datasets for each school + return [ $school => ['label' => $school, 'data' => array_fill(0, $all_semesters->count(), 0)]]; + })->toArray(); + + // $sorted_apps = $counted_apps->sortKeysUsing(Semester::sortCollectionWithSemestersKeyChronologically()); + foreach ($counted_apps as $semester => $school_counts) { + $semester_index = $all_semesters->search($semester); + foreach ($school_counts as $school => $count) { + $datasets[$school]['data'][$semester_index] = $count; + } + } + + return [ + 'labels' => $all_semesters->toArray(), + 'datasets' => array_values($datasets), + ]; + + } + + /** + * BAR CHART #4 DATA - APPLICATIONS COUNT BY SEMESTER, SCHOOL AND FILING STATUS + */ + + public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->with('stats')->get() + ); + } + + public function groupAppsBySemestersAndSchoolsWithFilingStatus(array $semesters_params, array $filing_status_params, array $schools_params) + { + $student_applications = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params); + + return $student_applications->flatMap(function ($application) use ($semesters_params, $filing_status_params) { + + if (!empty($application->research_profile->data['semesters']) && !empty($application->stats->data['status'])) { + + $semesters_values = array_intersect($application->research_profile->data['semesters'], $semesters_params); + $status_values = array_intersect(array_keys($application->stats->data['status']), $filing_status_params); + + return array_map(function ($status_value) use ($application, $semesters_values) { + return array_map(function ($semester_value) use ($application, $status_value) { + return [ + 'id' => $application->id, + 'semester' => $semester_value, + 'status' => $status_value, + 'status_count' => $application->stats->data['status'][$status_value], + ]; + }, $semesters_values); + }, $status_values); + } + })->flatten(1); + } + + public function getAppsBySemestersAndSchoolsWithFilingStatus(array $semesters_params, array $filing_status_params, array $schools_params, $type, $status) + { + $applications = $this->groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $filing_status_params, $schools_params); + + $counted_apps = $applications + ->groupBy(['semester', 'status']) + ->map(function ($semester_group) { + return $semester_group->map(function ($status_group) { + return $status_group->sum('status_count'); + }); + }); + + $all_semesters = $applications->pluck('semester')->unique()->sort()->values(); + $all_filing_statuses = $applications->pluck('status')->unique()->sort()->values(); + + $datasets = $all_filing_statuses->mapWithKeys(function ($filing_status) use ($all_semesters) { // Initialize datasets for each status + return [$filing_status => ['label' => $filing_status, 'data' => array_fill(0, $all_semesters->count(), 0)]]; + })->toArray(); + + foreach ($counted_apps as $semester => $filing_status_counts) { + $semester_index = $all_semesters->search($semester); + foreach ($filing_status_counts as $filing_status => $count) { + $datasets[$filing_status]['data'][$semester_index] = $count; + } + } + + return [ + 'labels' => $all_semesters->toArray(), + 'datasets' => array_values($datasets), + ]; + } + + /** AUXILIARY METHODS */ + public static function getLabels(string $criteria) + { + switch ($criteria) { + case 'school': + return StudentData::uniqueValuesFor('research_profile', 'schools')->sort()->values(); + break; + case 'semester': + return StudentData::uniqueValuesFor('research_profile', 'semesters') + ->sortBy(function($semester, $key) { + return Semester::date($semester)?->toDateString(); + }) + ->values(); + break; + case 'faculty': + return StudentData::uniqueValuesFor('research_profile', 'faculty') + ->sort()->values(); + break; + default: + $semesters = StudentData::uniqueValuesFor('research_profile', 'semesters') + ->map(fn($semester) => Semester::date($semester)?->toDateString()); + return Semester::sortSemestersChronologically($semesters); + break; + } + } + + public function semestersParamsStartAndEnd($semesters_params, $weeks_before_start = 3, $weeks_before_end = 3) : array { + $result = []; + + foreach ($semesters_params as $semester_params) { + + $semester = explode(' ', $semester_params); + $start_date = Carbon::createFromFormat('M j Y', Semester::seasonDates()[$semester[0]][0].' '.$semester[1]) + ->subweeks($weeks_before_start) + ->format('Y-m-d'); + $end_date = Carbon::createFromFormat('M j Y', Semester::seasonDates()[$semester[0]][1].' '.$semester[1]) + ->subweeks($weeks_before_end) + ->format('Y-m-d'); + + $result[] = [ 'start' => $start_date, 'end' => $end_date ]; + } + + return $result; + } + +} diff --git a/app/Profile.php b/app/Profile.php index 04f34c7b..7ee53260 100644 --- a/app/Profile.php +++ b/app/Profile.php @@ -511,6 +511,13 @@ public function scopeStudentsPendingReviewWithSemester($query, $semester) }); } + public function scopeNotAcceptingStudents($query) { + return $query->whereHas('data', function($q) { + $q->where('type', 'information') + ->whereJsonContains('data->not_accepting_students', '1'); + }); + } + /////////////////////////////////// // Mutators & Virtual Attributes // /////////////////////////////////// diff --git a/app/Student.php b/app/Student.php index fbecf415..66a86c09 100644 --- a/app/Student.php +++ b/app/Student.php @@ -313,6 +313,45 @@ public function scopeDataContains($query, $key, $value) return $query; } + public function scopeDataOrContains($query, $key, $value) + { + if ($value !== '') { + $query->whereHas('research_profile', function ($q) use ($key, $value) { + $q->orWhereJsonContains("data->{$key}", $value); + }); + } + + return $query; + } + + public function scopeWithDataSelected($query, $key) + { + $query->whereHas('research_profile', function ($q) use ($key) { + $q->orWhereJsonLength("data->{$key}", ">=", "1"); + }); + + return $query; + } + + // public function scopeWithSemesterSelected($query) + // { + // return $query->whereHas('research_profile', function ($q) { + // $q->whereJsonLength("data->semesters", ">=", "1"); + // }); + // } + // public function scopeWithSchoolSelected($query) + // { + // return $query->whereHas('research_profile', function ($q) { + // $q->whereJsonLength("data->schools", ">=", "1"); + // }); + // } + // public function scopeWithExistingStatus($query) + // { + // return $query->whereHas('stats', function ($q) { + // $q->whereJsonLength("data->status", ">=", "1"); + // }); + // } + public function scopeDataEquals($query, $key, $value) { if ($value !== '') { @@ -352,6 +391,17 @@ public function scopeWithStatusNotInterested($query) return $query->where('profile_student.status', '=', 'not interested'); } + /** + * Query scope for Students whose application status is 'not interested'. To be used through the Profile relation. + * + * @param \Illuminate\Database\Eloquent\Builder $query + * @return \Illuminate\Database\Eloquent\Builder + */ + public function scopeWithStatusAccepted($query) + { + return $query->where('profile_student.status', '=', 'accepted'); + } + /////////////// // Relations // /////////////// diff --git a/app/StudentData.php b/app/StudentData.php index f24b8405..e6e99a78 100644 --- a/app/StudentData.php +++ b/app/StudentData.php @@ -5,7 +5,9 @@ use App\ProfileData; use App\Setting; use App\Student; +use Carbon\Carbon; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\DB; class StudentData extends ProfileData { @@ -102,6 +104,48 @@ public function scopeStats($query) return $query->where('type', 'stats'); } + /** + * Query scope for research profile + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Query\Builder + */ + public function scopeResearchProfileSubmittedWithSemesterSelected($query, $status) + { + return $query->where('type', 'research_profile') + ->whereJsonLength("data->semesters", ">=", "1") + ->whereRelation("student", "status", $status); + } + + /** + * Query scope for research profile + * + * @param \Illuminate\Database\Query\Builder $query + * @return \Illuminate\Database\Query\Builder + */ + public function scopeDataContains($query, $key, $value) + { + if ($value !== '') { + $query->whereJsonContains("data->{$key}", $value); + } + + return $query; + } + + public function scopeOrDataContains($query, $key, $value) + { + if ($value !== '') { + $query->orWhereJsonContains("data->{$key}", $value); + } + + return $query; + } + + public function scopeWithDataLengthGreaterThan($query, $key, $length) + { + return $query->whereJsonLength("data->{$key}", ">=", $length); + } + /////////////// // Relations // /////////////// diff --git a/package-lock.json b/package-lock.json index 9b32e0af..8ee59ee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "packages": { "": { "dependencies": { + "chart.js": "^4.4.3", "puppeteer": "^16.1.0" }, "devDependencies": { @@ -2128,6 +2129,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -3721,6 +3727,17 @@ "node": "*" } }, + "node_modules/chart.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -12802,6 +12819,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, "@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -14122,6 +14144,14 @@ "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true }, + "chart.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "requires": { + "@kurkle/color": "^0.3.0" + } + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", diff --git a/package.json b/package.json index 48c9d2ea..ebf94d4d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "vue-template-compiler": "^2.6.14" }, "dependencies": { + "chart.js": "^4.4.3", "puppeteer": "^16.1.0" } } diff --git a/public/css/app.css b/public/css/app.css index e5d02272..8927914a 100644 --- a/public/css/app.css +++ b/public/css/app.css @@ -10189,6 +10189,11 @@ input[type=file]:invalid { padding: 30px; } +.coi-reports-export .disabled-element { + opacity: 0.4; + cursor: not-allowed; +} + #footer-container { background-color: #919191; border-top: 10px solid #154734; diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 5c378da8..dcf455b2 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,6 +1,6 @@ { "/js/app.js": "/js/app.js?id=d6cc6e82bb4e678d3a96d87b3c38835c", "/js/manifest.js": "/js/manifest.js?id=dc9ead3d7857b522d7de22d75063453c", - "/css/app.css": "/css/app.css?id=54db96a1c960aab96ee5b38d5cbbf2f1", + "/css/app.css": "/css/app.css?id=ce72e6936532058ae2b3e054c9d036c4", "/js/vendor.js": "/js/vendor.js?id=4d3313683b3a2faf8ca0278ce47f3880" } diff --git a/resources/assets/sass/_core.scss b/resources/assets/sass/_core.scss index 782c10a5..b2bcb9ac 100644 --- a/resources/assets/sass/_core.scss +++ b/resources/assets/sass/_core.scss @@ -460,3 +460,8 @@ input[type="file"]:invalid{ .flash-container .flash-message { padding: 30px; } + +.coi-reports-export .disabled-element { + opacity: 0.4; + cursor: not-allowed; + } \ No newline at end of file diff --git a/resources/views/insights/admin-chart.php b/resources/views/insights/admin-chart.php new file mode 100644 index 00000000..053ee227 --- /dev/null +++ b/resources/views/insights/admin-chart.php @@ -0,0 +1 @@ +

Admin

\ No newline at end of file diff --git a/resources/views/insights/index.blade.php b/resources/views/insights/index.blade.php new file mode 100644 index 00000000..33778bc7 --- /dev/null +++ b/resources/views/insights/index.blade.php @@ -0,0 +1,88 @@ +@extends('layout') +@section('title', 'Profiles Data Update Insights') +@section('header') + @include('nav') +@stop + + +@once + @push('scripts') + + + + @endpush +@endonce + +@push('scripts') + +@endpush + +@section('content') +
+

Insights

+ + @include('errors/list') + + + + +
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+ @include('insights.profiles-chart') +
+ +
+ @include('insights.admin-chart') +
+ +
+@stop + diff --git a/resources/views/insights/profiles-chart.php b/resources/views/insights/profiles-chart.php new file mode 100644 index 00000000..b457c066 --- /dev/null +++ b/resources/views/insights/profiles-chart.php @@ -0,0 +1 @@ +

Profiles

\ No newline at end of file diff --git a/resources/views/layout.blade.php b/resources/views/layout.blade.php index 2bc33c50..08f6bdfb 100644 --- a/resources/views/layout.blade.php +++ b/resources/views/layout.blade.php @@ -70,6 +70,7 @@ + @yield('scripts') @stack('scripts') @if(isset($settings['primary_color']) || isset($settings['secondary_color']) || isset($settings['tertiary_color'] )) diff --git a/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php new file mode 100644 index 00000000..dffc67c1 --- /dev/null +++ b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php @@ -0,0 +1,106 @@ +
+
Accepted & Follow Up Applications Count
+
+ +
+
diff --git a/resources/views/livewire/insights-filter.blade.php b/resources/views/livewire/insights-filter.blade.php new file mode 100644 index 00000000..8748e817 --- /dev/null +++ b/resources/views/livewire/insights-filter.blade.php @@ -0,0 +1,141 @@ + @push('scripts') + +@endpush + +
+
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ +
+
+ To ensure accurate results, the data displayed is set by default to cover a period from three weeks before the start of the semester to three weeks before its end, as student research applications are reviewed and filed for the following semester. You can adjust this timeframe by using the sliders below to select the number of weeks that best suits your needs. +
+
+ Weeks before semester start: + +
+
+ Weeks before semester end: + +
+
+ + +
+ + diff --git a/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php new file mode 100644 index 00000000..3db03bb0 --- /dev/null +++ b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php @@ -0,0 +1,127 @@ + +
+
Viewed Applications Count
+
+ +
+
diff --git a/resources/views/livewire/students-app-count-chart.blade.php b/resources/views/livewire/students-app-count-chart.blade.php new file mode 100644 index 00000000..08890eff --- /dev/null +++ b/resources/views/livewire/students-app-count-chart.blade.php @@ -0,0 +1,51 @@ + +
+
Student Research Applications Status Count
+
+
+ +@push('scripts') + +@endpush diff --git a/resources/views/livewire/students-app-filing-status-chart.blade.php b/resources/views/livewire/students-app-filing-status-chart.blade.php new file mode 100644 index 00000000..d2865978 --- /dev/null +++ b/resources/views/livewire/students-app-filing-status-chart.blade.php @@ -0,0 +1,51 @@ + +
+
Student Research Applications Filing Status Count
+
+
+ +@push('scripts') + +@endpush diff --git a/resources/views/nav.blade.php b/resources/views/nav.blade.php index 6efbcb4a..5d7415c4 100644 --- a/resources/views/nav.blade.php +++ b/resources/views/nav.blade.php @@ -105,6 +105,9 @@ Activity Logs @endif + + Profiles Update Data +
@endif diff --git a/routes/web.php b/routes/web.php index b0d54d39..4f8b4f85 100644 --- a/routes/web.php +++ b/routes/web.php @@ -15,6 +15,7 @@ * Auth ******************/ +use App\Http\Controllers\InsightsController; use App\Http\Controllers\ProfilesController; use App\Http\Controllers\UsersController; @@ -27,6 +28,7 @@ ******************/ Route::name('app.')->group(function () { Route::name('logs.index')->get('/logs', 'LogsController@index'); + Route::name('insights.index')->get('insights', [InsightsController::class, 'index']); Route::name('settings.edit')->get('/settings', 'SettingsController@edit'); Route::name('settings.update')->post('/settings', 'SettingsController@update'); Route::name('settings.update-image')->post('/settings/image/{image}', 'SettingsController@updateImage') From e2a1510c89ac255e5f11de79eee17b1751e9adaa Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:24:43 -0500 Subject: [PATCH 02/37] Refactors methods to retrieve student applications by semesters, schools and filing status For 'accepted & follow up' applications count doughnut chart and applications count by filing status bar chart. Reorganizes and adds doc blocks to StudentDataInsight model. Removes unnecessary parameters for applications count bar chart. --- ...AcceptedAndFollowUpAppsPercentageChart.php | 13 +- app/Http/Livewire/StudentsAppCountChart.php | 2 +- .../Livewire/StudentsAppFilingStatusChart.php | 2 +- .../StudentDataInsight.php | 442 +++++++++--------- 4 files changed, 223 insertions(+), 236 deletions(-) diff --git a/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php index 081217fe..e470c1a7 100644 --- a/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php +++ b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php @@ -13,12 +13,19 @@ class AcceptedAndFollowUpAppsPercentageChart extends Component public array $data; public array $selected_semesters; public array $selected_schools; - public $selected_filing_statuses; + public array $filing_statuses_category_1; + public array $filing_statuses_category_2; protected $listeners = ['refreshData5', 'refreshChart5']; + public function mount() + { + $this->filing_statuses_category_1 = ['accepted', 'follow up']; + $this->filing_statuses_category_2= ['not interested', 'maybe later']; + } public function refreshChart5($data, $labels) {} - public function refreshData5($selected_semesters, $selected_schools) { + public function refreshData5($selected_semesters, $selected_schools) + { $this->selected_semesters = $selected_semesters; $this->selected_schools = $selected_schools; @@ -32,7 +39,7 @@ public function refreshData5($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsForSemestersAndSchoolsWithFilingStatuses($this->selected_semesters, $this->selected_schools, ["accepted", "follow up"]); + return $report->getAppsForSemestersAndSchoolsWithFilingStatuses($this->selected_semesters, $this->selected_schools, $this->filing_statuses_category_1, $this->filing_statuses_category_2); } public function render() diff --git a/app/Http/Livewire/StudentsAppCountChart.php b/app/Http/Livewire/StudentsAppCountChart.php index 2cb1018b..52a4a5b1 100644 --- a/app/Http/Livewire/StudentsAppCountChart.php +++ b/app/Http/Livewire/StudentsAppCountChart.php @@ -32,7 +32,7 @@ public function refreshData2($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsBySemestersAndSchools($this->selected_semesters, $this->selected_schools, "count", "submitted"); + return $report->getAppsBySemestersAndSchools($this->selected_semesters, $this->selected_school); } public function render() diff --git a/app/Http/Livewire/StudentsAppFilingStatusChart.php b/app/Http/Livewire/StudentsAppFilingStatusChart.php index ad2e548b..93895735 100644 --- a/app/Http/Livewire/StudentsAppFilingStatusChart.php +++ b/app/Http/Livewire/StudentsAppFilingStatusChart.php @@ -38,7 +38,7 @@ public function refreshData1($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_filing_statuses, $this->selected_schools, "count", "submitted"); + return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_filing_statuses, $this->selected_schools); } public function render() diff --git a/app/Insights/StudentApplications/StudentDataInsight.php b/app/Insights/StudentApplications/StudentDataInsight.php index 8300a2f3..c466dc42 100644 --- a/app/Insights/StudentApplications/StudentDataInsight.php +++ b/app/Insights/StudentApplications/StudentDataInsight.php @@ -9,6 +9,7 @@ use App\StudentData; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; @@ -16,252 +17,204 @@ class StudentDataInsight extends Insight { - // public function getDataSet(string $criteria, ?string $type = null, ?string $status = 'submitted', ?string $stats_progress = null) - public function getDataSet(?array $semesters_params = [], ?array $schools_params = [], ?string $type = null, ?string $status = 'submitted') + /** + * DOUGHNUT CHART #1 DATA - APPLICATIONS COUNT BY FILING STATUS FOR SEMESTERS AND SCHOOLS + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @param array $filing_status_category_1 Filing statuses for the first category. Example: ['accepted', 'follow up']. + * @param array $filing_status_category_2 Filing statuses for the first category. Example: ['not interested', 'maybe later']. + * @return array + */ + public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_category_1, $filing_status_category_2) { - $insight_parameters[] = [ - 'label' => 'Applications for Semester', - 'type' => 'bar', - 'backgroundColor' => 'rgba(15,64,97,255)', - 'borderColor' => 'rgba(15,64,97,255)', - // 'data' => $this->getDataSemesterAndSchool($semesters_params, $schools_params, $type, $status), - //'labels' => $this->getLabels($semester), - ]; + $filing_status_category_1_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_1)->count(); + $filing_status_category_2_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_2)->count(); - return $this->getDataArray($insight_parameters); + return [$filing_status_category_1_total, $filing_status_category_2_total]; } - //Query to retrieve student applications for given semesters and schools - public function appsForSemestersAndSchools($semesters_params, $schools_params) - { - return Student::query() - ->submitted() - ->withWhereHas('research_profile', function($q) use ($semesters_params, $schools_params) { - $q->where(function($q) use ($semesters_params) { - foreach ($semesters_params as $semester) { - $q->orDataContains('semesters', $semester); - } - }); - $q->where(function($q) use ($schools_params) { - foreach ($schools_params as $school) { - $q->orDataContains('schools', $school); - } - }); - }); - } + /** + * Auxiliary caching and grouping methods for Chart #1 (Doughnut) and Chart #4 (Bar). + */ /** - * DOUGHNUT CHART #1 DATA - APPLICATIONS PERCENTAGE BY FILING STATUS FOR SEMESTERS AND SCHOOLS + * Retrieve and cache a collection of student applications whose last filing status matches + * the provided filing status parameters for the specified semesters and schools. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. + * @return \Illuminate\Support\Collection */ - - public function getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params) + public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) { $sm = implode('-', $semesters_params); $sch = implode('-', $schools_params); - $fs = implode('-', $filing_status_params); - - return Cache::remember( - "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}-{$fs}", - 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) - ->withWhereHas('stats', function($q) use ($filing_status_params) { - $q->whereNotNull('data->status_history'); - $q->where(function($q) use ($filing_status_params) { - foreach ($filing_status_params as $key => $filing_status) { - $q = $key == 0 ? - $q->whereJsonContains('data->status_history', ['new_status' => $filing_status]) : - $q->orWhereJsonContains('data->status_history', ['new_status' => $filing_status]); - } - }); - })->get() - ); - } + $fls = implode('-', $filing_status_params); - public function getCachedAppsForSemestersAndSchoolsWithoutFilingStatuses($semesters_params, $schools_params, $filing_status_params) - { - $sm = implode('-', $semesters_params); - $sch = implode('-', $schools_params); - return Cache::remember( - "student-apps-for-semesters-schools-without-flstatus-{$sm}-{$sch}", + "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}-{$fls}", 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) - ->withWhereHas('stats', function($q) use ($filing_status_params) { - $q->whereNot(function($q) use ($filing_status_params) { - foreach ($filing_status_params as $key => $filing_status) { - $q = $key == 0 ? - $q->whereJsonContains('data->status_history', ['new_status' => $filing_status]) : - $q->orWhereJsonContains('data->status_history', ['new_status' => $filing_status]); - } - }); - })->get() + fn() => $this->groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) ); } - public function groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps) + /** + * Return a collection of student applications whose last filing status matches + * the provided filing status parameters for the specified semesters and schools. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. + * @return \Illuminate\Support\Collection + */ + public function groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) { + $student_applications = $this->getCachedAppsForSemestersAndSchoolsWithStats($semesters_params, $schools_params); $semesters_params_start_end = $this->semestersParamsStartAndEnd($semesters_params); - - $filing_status_count = 0; - - foreach ($semesters_params_start_end as $semester_start_end) { - $start_date = $semester_start_end['start']; - $end_date = $semester_start_end['end']; + $results = []; - $filing_status_count = $filing_status_count + - $student_apps->where(function($app) use ($filing_status_params, $start_date, $end_date, &$filing_status_count) { - return collect($app->stats->data['status_history']) - ->groupBy('profile') - ->where(function($group) use ($app, $filing_status_params, $start_date, $end_date, &$filing_status_count) { - $last_update = $group->sortByDesc(function ($item) { - return Carbon::parse($item['updated_at']); - })->first(); - $filing_date = Carbon::parse($last_update['updated_at']); - // if (in_array($last_update['new_status'], $filing_status_params) && $filing_date->between($start_date, $end_date)) { - // // dump($app->stats->id); - // $filing_status_count = $filing_status_count + 1; - // } - return (in_array($last_update['new_status'], $filing_status_params) && $filing_date->between($start_date, $end_date)); - })->count(); - })->count(); - - } - return $filing_status_count; - } - - public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params) - { - $student_apps = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params); - $filing_status_params_total = $this->groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps); - - $filing_status_params = ['maybe later', 'not interested']; - $student_apps = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params); - $others_total = $this->groupAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_params, $student_apps); - - // $filing_status_params_total = $grouped_student_apps_with_flstatus; - // $others_total = $grouped_student_apps_without_flstatus; - // $filing_status_params_total = array_sum($grouped_student_apps_with_flstatus); - // $others_total = array_sum($grouped_student_apps_without_flstatus); - - // $total = $filing_status_params_total + $others_total; - - // if ($total > 0) { // Avoid division by zero - // $result[0] = round(($filing_status_params_total / $total) * 100); - // $result[1] = round(($others_total / $total) * 100); - // } else { - // $result[0] = 0; - // $result[1] = 0; - // } - return [$filing_status_params_total, $others_total]; + $student_applications->each(function ($application) use ($semesters_params_start_end, $filing_status_params, &$results) { + if (!empty($application->research_profile->data['semesters']) && !empty($application->stats->data['status_history'])) { + foreach ($semesters_params_start_end as $semester => $semester_start_end) { + $start_date = $semester_start_end['start']; + $end_date = $semester_start_end['end']; + foreach ($filing_status_params as $filing_status) { + collect($application->stats->data['status_history']) + ->groupBy('profile') + ->each(function($group) use ($start_date, $end_date, $filing_status, $semester, $application, &$results) { + $last_update = $group->sortByDesc(function ($item) { + return Carbon::parse($item['updated_at']); + })->first(); + $filing_date = Carbon::parse($last_update['updated_at']); + if ($last_update['new_status'] === $filing_status && $filing_date->between($start_date, $end_date)) { + $results[] = [ + 'id' => $application->stats->id, + 'semester' => $semester, + 'filing_status' => $filing_status, + ]; + } + }); + } + } + } + }); + return collect($results); } /** - * DOUGHNUT CHART #2 DATA - APPLICATIONS COUNT VIEWED AND NOT FOR SEMESTERS AND SCHOOLS - */ - - public function getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params) - { - $sm = implode('-', $semesters_params); - $sch = implode('-', $schools_params); - - return Cache::remember( - "student-apps-for-semesters-schools-not-viewed-{$sm}-{$sch}", - 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) - ->where(function($query) { - $query->whereHas('stats', function($q) { - $q->whereNull('data->views'); - }); - $query->orDoesntHave('stats'); - })->count() - ); - } - - public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params) - { + * Retrieve and cache a collection of student applications for given semesters and schools with stats. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return \Illuminate\Support\Collection + */ + public function getCachedAppsForSemestersAndSchoolsWithStats($semesters_params, $schools_params) + { $sm = implode('-', $semesters_params); $sch = implode('-', $schools_params); return Cache::remember( - "student-apps-for-semesters-schools-viewed-{$sm}-{$sch}", + "student-apps-for-semesters-schools-with-stats-{$sm}-{$sch}", 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) - ->where(function($query) { - $query->whereHas('stats', function($q) { - $q->whereNotNull('data->views'); - $q->where('data->views', '>', 0); - }); - })->count() + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->with('stats')->get() ); } - public function getViewedAndNotViewedApps(array $semesters_params, array $schools_params,) + /** + * Return query builder to retrieve students records with applications ('research_profile') for given semesters and schools. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return Illuminate\Database\Eloquent\Builder + */ + public function appsForSemestersAndSchools($semesters_params, $schools_params) { - $result = []; - - $submitted_not_viewed = $this->getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params); - - $submitted_and_viewed = $this->getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params); - - // $total = $submitted_and_viewed + $submitted_not_viewed; + return Student::query() + ->submitted() + ->withWhereHas('research_profile', function($q) use ($semesters_params, $schools_params) { + $q->where(function($q) use ($semesters_params) { + foreach ($semesters_params as $semester) { + $q->orDataContains('semesters', $semester); + } + }); + $q->where(function($q) use ($schools_params) { + foreach ($schools_params as $school) { + $q->orDataContains('schools', $school); + } + }); + }); + } - // if ($total > 0) { // Avoid division by zero - // $result[0] = round(($submitted_and_viewed / $total) * 100); - // $result[1] = round(($submitted_not_viewed / $total) * 100); - // } else { - // $result[0] = 0; - // $result[1] = 0; - // } + /** + * DOUGHNUT CHART #2 DATA - APPLICATIONS COUNT VIEWED AND NOT VIEWED FOR SEMESTERS AND SCHOOLS + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return array + */ + public function getViewedAndNotViewedApps($semesters_params, $schools_params,) + { + $submitted_and_viewed = $this->getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params)->count(); + $submitted_not_viewed = $this->getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params)->count(); return [ 'labels' => [ 'Viewed', 'Not Viewed' ], 'datasets' => [$submitted_and_viewed, $submitted_not_viewed], ]; } - /** - * BAR CHART #3 DATA - APPLICATIONS COUNT BY SEMESTER AND SCHOOL - */ - - public function getCachedAppsForSemestersAndSchools($semesters_params, $schools_params) + * Retrieve and cache a collection of student applications for given semesters and schools that have been viewed. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return Illuminate\Database\Eloquent\Builder + */ + public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-viewed-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->where(function($query) { + $query->whereHas('stats', function($q) { + $q->whereNotNull('data->views'); + $q->where('data->views', '>', 0); + }); + }) + ); + } + + /** + * Retrieve and cache a collection of student applications for given semesters and schools that have not been viewed. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return Illuminate\Database\Eloquent\Builder + */ + public function getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params) { $sm = implode('-', $semesters_params); $sch = implode('-', $schools_params); - + return Cache::remember( - "student-apps-for-semesters-schools-{$sm}-{$sch}", + "student-apps-for-semesters-schools-not-viewed-{$sm}-{$sch}", 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->get() + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params) + ->where(function($query) { + $query->whereHas('stats', function($q) { + $q->whereNull('data->views'); + }); + $query->orDoesntHave('stats'); + }) ); } - public function groupAppsBySemestersAndSchools(array $semesters_params, array $schools_params) - { - $student_applications = $this->getCachedAppsForSemestersAndSchools($semesters_params, $schools_params); - - return $student_applications->flatMap(function ($application) use ($semesters_params, $schools_params) { - - if (!empty($application->research_profile->data['semesters']) && !empty($application->research_profile->data['schools'])) { - - $semesters_values = array_intersect($application->research_profile->data['semesters'], $semesters_params); - $schools_values = array_intersect($application->research_profile->data['schools'], $schools_params); - - return array_map(function ($school_value) use ($application, $semesters_values) { - return array_map(function ($semester_value) use ($application, $school_value) { - return [ - 'id' => $application->id, - 'semester' => $semester_value, - 'school' => $school_value, - ]; - }, $semesters_values); - }, $schools_values); - } - })->flatten(1); - } - - public function getAppsBySemestersAndSchools(array $semesters_params, array $schools_params, $type, $status) + /** + * BAR CHART #3 DATA - APPLICATIONS COUNT BY SEMESTER AND SCHOOL + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return array + */ + public function getAppsBySemestersAndSchools($semesters_params, $schools_params) { - $applications = $this->groupAppsBySemestersAndSchools($semesters_params, $schools_params); + $applications = $this->transformAppsBySemestersAndSchools($semesters_params, $schools_params); $counted_apps = $applications ->groupBy(['semester', 'school']) @@ -293,62 +246,74 @@ public function getAppsBySemestersAndSchools(array $semesters_params, array $sch ]; } - /** - * BAR CHART #4 DATA - APPLICATIONS COUNT BY SEMESTER, SCHOOL AND FILING STATUS - */ - - public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params) - { - $sm = implode('-', $semesters_params); - $sch = implode('-', $schools_params); - - return Cache::remember( - "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}", - 15 * 60, - fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->with('stats')->get() - ); - } - - public function groupAppsBySemestersAndSchoolsWithFilingStatus(array $semesters_params, array $filing_status_params, array $schools_params) + * Auxiliary method for Chart #3 to transform student applications by semesters and schools. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return \Illuminate\Support\Collection + */ + public function transformAppsBySemestersAndSchools(array $semesters_params, array $schools_params) { - $student_applications = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params); + $student_applications = $this->getCachedAppsForSemestersAndSchools($semesters_params, $schools_params); - return $student_applications->flatMap(function ($application) use ($semesters_params, $filing_status_params) { + return $student_applications->flatMap(function ($application) use ($semesters_params, $schools_params) { - if (!empty($application->research_profile->data['semesters']) && !empty($application->stats->data['status'])) { + if (!empty($application->research_profile->data['semesters']) && !empty($application->research_profile->data['schools'])) { $semesters_values = array_intersect($application->research_profile->data['semesters'], $semesters_params); - $status_values = array_intersect(array_keys($application->stats->data['status']), $filing_status_params); + $schools_values = array_intersect($application->research_profile->data['schools'], $schools_params); - return array_map(function ($status_value) use ($application, $semesters_values) { - return array_map(function ($semester_value) use ($application, $status_value) { + return array_map(function ($school_value) use ($application, $semesters_values) { + return array_map(function ($semester_value) use ($application, $school_value) { return [ 'id' => $application->id, 'semester' => $semester_value, - 'status' => $status_value, - 'status_count' => $application->stats->data['status'][$status_value], + 'school' => $school_value, ]; }, $semesters_values); - }, $status_values); + }, $schools_values); } })->flatten(1); } - public function getAppsBySemestersAndSchoolsWithFilingStatus(array $semesters_params, array $filing_status_params, array $schools_params, $type, $status) + /** + * Retrieve and cache a collection of student applications for given semesters and schools. + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @return \Illuminate\Support\Collection + */ + public function getCachedAppsForSemestersAndSchools($semesters_params, $schools_params) + { + $sm = implode('-', $semesters_params); + $sch = implode('-', $schools_params); + + return Cache::remember( + "student-apps-for-semesters-schools-{$sm}-{$sch}", + 15 * 60, + fn() => $this->appsForSemestersAndSchools($semesters_params, $schools_params)->get() + ); + } + + /** + * BAR CHART #4 DATA - APPLICATIONS COUNT BY SEMESTER, SCHOOL AND FILING STATUS + * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. + * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. + * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. + */ + public function getAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) { - $applications = $this->groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $filing_status_params, $schools_params); + $applications = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params); $counted_apps = $applications - ->groupBy(['semester', 'status']) + ->groupBy(['semester', 'filing_status']) ->map(function ($semester_group) { return $semester_group->map(function ($status_group) { - return $status_group->sum('status_count'); + return $status_group->count(); }); }); $all_semesters = $applications->pluck('semester')->unique()->sort()->values(); - $all_filing_statuses = $applications->pluck('status')->unique()->sort()->values(); + $all_filing_statuses = $applications->pluck('filing_status')->unique()->sort()->values(); $datasets = $all_filing_statuses->mapWithKeys(function ($filing_status) use ($all_semesters) { // Initialize datasets for each status return [$filing_status => ['label' => $filing_status, 'data' => array_fill(0, $all_semesters->count(), 0)]]; @@ -367,6 +332,21 @@ public function getAppsBySemestersAndSchoolsWithFilingStatus(array $semesters_pa ]; } + // public function getDataSet(string $criteria, ?string $type = null, ?string $status = 'submitted', ?string $stats_progress = null) + public function getDataSet(?array $semesters_params = [], ?array $schools_params = [], ?string $type = null, ?string $status = 'submitted') + { + $insight_parameters[] = [ + 'label' => 'Applications for Semester', + 'type' => 'bar', + 'backgroundColor' => 'rgba(15,64,97,255)', + 'borderColor' => 'rgba(15,64,97,255)', + // 'data' => $this->getDataSemesterAndSchool($semesters_params, $schools_params, $type, $status), + //'labels' => $this->getLabels($semester), + ]; + + return $this->getDataArray($insight_parameters); + } + /** AUXILIARY METHODS */ public static function getLabels(string $criteria) { @@ -393,7 +373,7 @@ public static function getLabels(string $criteria) } } - public function semestersParamsStartAndEnd($semesters_params, $weeks_before_start = 3, $weeks_before_end = 3) : array { + public function semestersParamsStartAndEnd($semesters_params, $weeks_before_start = 4, $weeks_before_end = 4) : array { $result = []; foreach ($semesters_params as $semester_params) { @@ -406,7 +386,7 @@ public function semestersParamsStartAndEnd($semesters_params, $weeks_before_star ->subweeks($weeks_before_end) ->format('Y-m-d'); - $result[] = [ 'start' => $start_date, 'end' => $end_date ]; + $result[$semester_params] = [ 'start' => $start_date, 'end' => $end_date ]; } return $result; From 588ddef5c8e6367f68ceb98bb0f1f4a7c3300991 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:27:04 -0500 Subject: [PATCH 03/37] Refactors progressTextPlugin plugin for doughnut charts to refresh data percentage upon chart refresh. --- ...-follow-up-apps-percentage-chart.blade.php | 35 +++++------ ...ent-apps-viewed-not-viewed-chart.blade.php | 61 ++++++------------- 2 files changed, 37 insertions(+), 59 deletions(-) diff --git a/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php index dffc67c1..45267e33 100644 --- a/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php +++ b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php @@ -5,30 +5,34 @@ labels: @entangle('labels'), selected_semesters: @entangle('selected_semesters'), init() { + if (this.data.every(value => value === 0)) { + this.data = [1, 1]; // Fallback to ensure the chart renders + } + var progress = this.data[0]; - var total = Number(this.data[0]) + Number(this.data[1]); - var progress_percentage = total === 0 ? 0 : Math.round((this.data[0] / total) * 100); const progressTextPlugin = { id: 'progressText', afterDraw: function(chart) { - var ctx = chart.ctx; - var width = chart.width; - var height = chart.height; + const {ctx, data} = chart; - ctx.restore(); + var progress = Number(data.datasets[0].data[0]); + var remaining = Number(data.datasets[0].data[1]); + var total = progress + remaining; + var progress_percentage = total === 0 ? 0 : Math.round((progress / total) * 100); + ctx.save(); + const xCoor = chart. getDatasetMeta(0).data[0].x; + const yCoor = chart. getDatasetMeta(0).data[0].y; + + var height = chart.height; var fontSize = (height / 140).toFixed(2); ctx.font = fontSize + 'em Roboto'; - ctx.textBaseline = 'middle'; ctx.fillStyle = '#198754'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(`${progress_percentage}%`, xCoor, yCoor); - var text = progress_percentage + '%', - textX = Math.round((width - ctx.measureText(text).width) / 2), - textY = height / 2.15; - - ctx.fillText(text, textX, textY); - ctx.save(); } }; @@ -78,11 +82,9 @@ plugins: [progressTextPlugin], }); - animateProgress(chart_instance, progress, false); + //animateProgress(chart_instance, progress, false); Livewire.on('refreshChart5', (data, labels) => { - var progress = data[0]; - if (data.every(value => value === 0)) { var data = [1, 1]; // Fallback to ensure the chart renders } @@ -93,7 +95,6 @@ backgroundColor: ['#4CAF50', '#E0E0E0'], borderWidth: 3 }]; - chart_instance.data.plugins = [progressTextPlugin]; chart_instance.update(); }); } diff --git a/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php index 3db03bb0..d365bb0f 100644 --- a/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php +++ b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php @@ -6,56 +6,36 @@ labels: @entangle('labels'), selected_semesters: @entangle('selected_semesters'), init() { + if (this.data.every(value => value === 0)) { + this.data = [1, 1]; // Fallback to ensure the chart renders + } + var progress = this.data[0]; - var total = Number(this.data[0]) + Number(this.data[1]); - var progress_percentage = total === 0 ? 0 : Math.round((this.data[0] / total) * 100); const progressTextPlugin = { id: 'progressText', afterDraw: function(chart) { - var ctx = chart.ctx; - var width = chart.width; - var height = chart.height; + const {ctx, data} = chart; - ctx.restore(); + var progress = Number(data.datasets[0].data[0]); + var remaining = Number(data.datasets[0].data[1]); + var total = progress + remaining; + var progress_percentage = total === 0 ? 0 : Math.round((progress / total) * 100); + ctx.save(); + const xCoor = chart. getDatasetMeta(0).data[0].x; + const yCoor = chart. getDatasetMeta(0).data[0].y; + + var height = chart.height; var fontSize = (height / 140).toFixed(2); ctx.font = fontSize + 'em Roboto'; - ctx.textBaseline = 'middle'; ctx.fillStyle = '#198754'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(`${progress_percentage}%`, xCoor, yCoor); - var text = progress_percentage + '%', - textX = Math.round((width - ctx.measureText(text).width) / 2), - textY = height / 2.15; - - ctx.fillText(text, textX, textY); - ctx.save(); } }; - // var progress = this.data[0]; - - // const progressTextPlugin = { - // id: 'progressText', - // afterDraw: function(chart) { - // var ctx = chart.ctx; - // var width = chart.width; - // var height = chart.height; - - // ctx.restore(); - - // var fontSize = (height / 140).toFixed(2); - // ctx.font = 'bold ' + fontSize + 'em Roboto'; - // ctx.textBaseline = 'middle'; - // ctx.fillStyle = '#198754'; - - // var text = progress, - // textX = Math.round((width - ctx.measureText(text).width) / 2), - // textY = height / 2.15; - - // ctx.fillText(text, textX, textY); - // ctx.save(); - // } - // }; const chart_options = { cutout: '35%', @@ -102,10 +82,8 @@ animateProgress(chart_instance, progress, false); Livewire.on('refreshChart4', (data, labels) => { - var progress = data[0]; - - if (this.data.every(value => value === 0)) { - this.data = [1, 1]; // Fallback to ensure the chart renders + if (data.every(value => value === 0)) { + data = [1, 1]; // Fallback to ensure the chart renders } chart_instance.data.labels = labels; @@ -114,7 +92,6 @@ backgroundColor: ['#4CAF50', '#E0E0E0'], borderWidth: 3 }]; - chart_instance.data.plugins = [progressTextPlugin]; chart_instance.update(); }); } From c35bea8d3679ad66ec1ddc611ff93e9601ba7b75 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:34:37 -0500 Subject: [PATCH 04/37] =?UTF-8?q?Refactoring=20corrections=20=F0=9F=A5=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Livewire/StudentsAppCountChart.php | 2 +- app/Http/Livewire/StudentsAppFilingStatusChart.php | 2 +- app/Insights/StudentApplications/StudentDataInsight.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Http/Livewire/StudentsAppCountChart.php b/app/Http/Livewire/StudentsAppCountChart.php index 52a4a5b1..dead13b8 100644 --- a/app/Http/Livewire/StudentsAppCountChart.php +++ b/app/Http/Livewire/StudentsAppCountChart.php @@ -32,7 +32,7 @@ public function refreshData2($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsBySemestersAndSchools($this->selected_semesters, $this->selected_school); + return $report->getAppsBySemestersAndSchools($this->selected_semesters, $this->selected_schools); } public function render() diff --git a/app/Http/Livewire/StudentsAppFilingStatusChart.php b/app/Http/Livewire/StudentsAppFilingStatusChart.php index 93895735..9ada6aeb 100644 --- a/app/Http/Livewire/StudentsAppFilingStatusChart.php +++ b/app/Http/Livewire/StudentsAppFilingStatusChart.php @@ -38,7 +38,7 @@ public function refreshData1($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_filing_statuses, $this->selected_schools); + return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_schools, $this->selected_filing_statuses); } public function render() diff --git a/app/Insights/StudentApplications/StudentDataInsight.php b/app/Insights/StudentApplications/StudentDataInsight.php index c466dc42..c43c5f41 100644 --- a/app/Insights/StudentApplications/StudentDataInsight.php +++ b/app/Insights/StudentApplications/StudentDataInsight.php @@ -162,7 +162,7 @@ public function getViewedAndNotViewedApps($semesters_params, $schools_params,) * Retrieve and cache a collection of student applications for given semesters and schools that have been viewed. * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. - * @return Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Support\Collection */ public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $schools_params) { @@ -178,7 +178,7 @@ public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $sc $q->whereNotNull('data->views'); $q->where('data->views', '>', 0); }); - }) + })->get() ); } @@ -186,7 +186,7 @@ public function getCachedAppsForSemestersAndSchoolsViewed($semesters_params, $sc * Retrieve and cache a collection of student applications for given semesters and schools that have not been viewed. * @param array $semesters_params Semesters filter. Example: ["Summer 2023", "Fall 2023"]. * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. - * @return Illuminate\Database\Eloquent\Builder + * @return \Illuminate\Support\Collection */ public function getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $schools_params) { @@ -202,7 +202,7 @@ public function getCachedAppsForSemestersAndSchoolsNotViewed($semesters_params, $q->whereNull('data->views'); }); $query->orDoesntHave('stats'); - }) + })->get() ); } From c7351f006ba34c7e10a666c45d447688e22319d7 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:17:22 -0500 Subject: [PATCH 05/37] Adjustments on parameters for weeks before and end of the semester --- ...AcceptedAndFollowUpAppsPercentageChart.php | 10 +- app/Http/Livewire/InsightsFilter.php | 10 +- .../Livewire/StudentsAppFilingStatusChart.php | 10 +- .../StudentDataInsight.php | 30 ++--- .../views/livewire/insights-filter.blade.php | 119 +++++++++--------- 5 files changed, 97 insertions(+), 82 deletions(-) diff --git a/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php index e470c1a7..0e3982db 100644 --- a/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php +++ b/app/Http/Livewire/AcceptedAndFollowUpAppsPercentageChart.php @@ -13,19 +13,25 @@ class AcceptedAndFollowUpAppsPercentageChart extends Component public array $data; public array $selected_semesters; public array $selected_schools; + public $weeks_before_semester_start; + public $weeks_before_semester_end; public array $filing_statuses_category_1; public array $filing_statuses_category_2; protected $listeners = ['refreshData5', 'refreshChart5']; public function mount() { + $this->weeks_before_semester_start = 4; + $this->weeks_before_semester_end = 4; $this->filing_statuses_category_1 = ['accepted', 'follow up']; $this->filing_statuses_category_2= ['not interested', 'maybe later']; } public function refreshChart5($data, $labels) {} - public function refreshData5($selected_semesters, $selected_schools) + public function refreshData5($selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end) { + $this->weeks_before_semester_start = $weeks_before_semester_start; + $this->weeks_before_semester_end = $weeks_before_semester_end; $this->selected_semesters = $selected_semesters; $this->selected_schools = $selected_schools; @@ -39,7 +45,7 @@ public function refreshData5($selected_semesters, $selected_schools) public function getData() { $report = new StudentDataInsight(); - return $report->getAppsForSemestersAndSchoolsWithFilingStatuses($this->selected_semesters, $this->selected_schools, $this->filing_statuses_category_1, $this->filing_statuses_category_2); + return $report->getAppsForSemestersAndSchoolsWithFilingStatuses($this->selected_semesters, $this->selected_schools, $this->filing_statuses_category_1, $this->filing_statuses_category_2, $this->weeks_before_semester_start, $this->weeks_before_semester_end); } public function render() diff --git a/app/Http/Livewire/InsightsFilter.php b/app/Http/Livewire/InsightsFilter.php index ae48e827..0bca77ff 100644 --- a/app/Http/Livewire/InsightsFilter.php +++ b/app/Http/Livewire/InsightsFilter.php @@ -11,11 +11,11 @@ class InsightsFilter extends Component public $school_options = []; protected $listeners = ['applyFilters']; - public function applyFilters($selected_semesters, $selected_schools) { - $this->emitTo('students-app-count-chart', 'refreshData2', $selected_semesters, $selected_schools); - $this->emitTo('students-app-filing-status-chart', 'refreshData1', $selected_semesters, $selected_schools); - $this->emitTo('accepted-and-follow-up-apps-percentage-chart', 'refreshData5', $selected_semesters, $selected_schools); - $this->emitTo('student-apps-viewed-not-viewed-chart', 'refreshData4', $selected_semesters, $selected_schools); + public function applyFilters($selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end) { + $this->emitTo('students-app-count-chart', 'refreshData2', $selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end); + $this->emitTo('students-app-filing-status-chart', 'refreshData1', $selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end); + $this->emitTo('accepted-and-follow-up-apps-percentage-chart', 'refreshData5', $selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end); + $this->emitTo('student-apps-viewed-not-viewed-chart', 'refreshData4', $selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end); } public function render() diff --git a/app/Http/Livewire/StudentsAppFilingStatusChart.php b/app/Http/Livewire/StudentsAppFilingStatusChart.php index 9ada6aeb..ad790b96 100644 --- a/app/Http/Livewire/StudentsAppFilingStatusChart.php +++ b/app/Http/Livewire/StudentsAppFilingStatusChart.php @@ -12,18 +12,24 @@ class StudentsAppFilingStatusChart extends Component public array $data; public $selected_semesters = []; public $selected_schools = []; + public $weeks_before_semester_start; + public $weeks_before_semester_end; public $selected_filing_statuses; protected $listeners = ['refreshData1', 'refreshChart1']; public function mount() { + $this->weeks_before_semester_start = 4; + $this->weeks_before_semester_end = 4; $this->selected_filing_statuses = ["accepted", "maybe later", "not interested", "new", "follow up"]; } public function refreshChart1($data, $labels) {} - public function refreshData1($selected_semesters, $selected_schools) { + public function refreshData1($selected_semesters, $selected_schools, $weeks_before_semester_start, $weeks_before_semester_end) { + $this->weeks_before_semester_start = $weeks_before_semester_start; + $this->weeks_before_semester_end = $weeks_before_semester_end; $this->selected_semesters = $selected_semesters; $this->selected_schools = $selected_schools; @@ -38,7 +44,7 @@ public function refreshData1($selected_semesters, $selected_schools) { public function getData() { $report = new StudentDataInsight(); - return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_schools, $this->selected_filing_statuses); + return $report->getAppsBySemestersAndSchoolsWithFilingStatus($this->selected_semesters, $this->selected_schools, $this->selected_filing_statuses, $this->weeks_before_semester_start, $this->weeks_before_semester_end); } public function render() diff --git a/app/Insights/StudentApplications/StudentDataInsight.php b/app/Insights/StudentApplications/StudentDataInsight.php index c43c5f41..dc5e8a5f 100644 --- a/app/Insights/StudentApplications/StudentDataInsight.php +++ b/app/Insights/StudentApplications/StudentDataInsight.php @@ -25,10 +25,10 @@ class StudentDataInsight extends Insight * @param array $filing_status_category_2 Filing statuses for the first category. Example: ['not interested', 'maybe later']. * @return array */ - public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_category_1, $filing_status_category_2) + public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_params, $schools_params, $filing_status_category_1, $filing_status_category_2, $weeks_before_semester_start, $weeks_before_semester_end) { - $filing_status_category_1_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_1)->count(); - $filing_status_category_2_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_2)->count(); + $filing_status_category_1_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_1, $weeks_before_semester_start, $weeks_before_semester_end)->count(); + $filing_status_category_2_total = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_category_2, $weeks_before_semester_start, $weeks_before_semester_end)->count(); return [$filing_status_category_1_total, $filing_status_category_2_total]; } @@ -45,16 +45,18 @@ public function getAppsForSemestersAndSchoolsWithFilingStatuses($semesters_param * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. * @return \Illuminate\Support\Collection */ - public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) + public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params, $weeks_before_semester_start, $weeks_before_semester_end) { $sm = implode('-', $semesters_params); $sch = implode('-', $schools_params); $fls = implode('-', $filing_status_params); + $wbs = $weeks_before_semester_start; + $wbe = $weeks_before_semester_end; return Cache::remember( - "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}-{$fls}", + "student-apps-for-semesters-schools-with-flstatus-{$sm}-{$sch}-{$fls}-{$wbs}-{$wbe}", 15 * 60, - fn() => $this->groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) + fn() => $this->groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params, $weeks_before_semester_start, $weeks_before_semester_end) ); } @@ -66,10 +68,10 @@ public function getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_p * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. * @return \Illuminate\Support\Collection */ - public function groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) + public function groupAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params, $weeks_before_semester_start, $weeks_before_semester_end) { $student_applications = $this->getCachedAppsForSemestersAndSchoolsWithStats($semesters_params, $schools_params); - $semesters_params_start_end = $this->semestersParamsStartAndEnd($semesters_params); + $semesters_params_start_end = $this->semestersParamsStartAndEnd($semesters_params, $weeks_before_semester_start, $weeks_before_semester_end); $results = []; $student_applications->each(function ($application) use ($semesters_params_start_end, $filing_status_params, &$results) { @@ -300,9 +302,9 @@ public function getCachedAppsForSemestersAndSchools($semesters_params, $schools_ * @param array $schools_params Schools filter. Example: ["BBS", "NSM"]. * @param array $filing_status_params Filing status filter for the last status of the applications. Example: ['accepted', 'follow up']. */ - public function getAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params) + public function getAppsBySemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params, $weeks_before_semester_start, $weeks_before_semester_end) { - $applications = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params); + $applications = $this->getCachedAppsForSemestersAndSchoolsWithFilingStatus($semesters_params, $schools_params, $filing_status_params, $weeks_before_semester_start, $weeks_before_semester_end); $counted_apps = $applications ->groupBy(['semester', 'filing_status']) @@ -373,17 +375,17 @@ public static function getLabels(string $criteria) } } - public function semestersParamsStartAndEnd($semesters_params, $weeks_before_start = 4, $weeks_before_end = 4) : array { + public function semestersParamsStartAndEnd($semesters_params, $weeks_before_start, $weeks_before_end) : array { $result = []; - + foreach ($semesters_params as $semester_params) { $semester = explode(' ', $semester_params); $start_date = Carbon::createFromFormat('M j Y', Semester::seasonDates()[$semester[0]][0].' '.$semester[1]) - ->subweeks($weeks_before_start) + ->subweeks((int) $weeks_before_start) ->format('Y-m-d'); $end_date = Carbon::createFromFormat('M j Y', Semester::seasonDates()[$semester[0]][1].' '.$semester[1]) - ->subweeks($weeks_before_end) + ->subweeks((int) $weeks_before_end) ->format('Y-m-d'); $result[$semester_params] = [ 'start' => $start_date, 'end' => $end_date ]; diff --git a/resources/views/livewire/insights-filter.blade.php b/resources/views/livewire/insights-filter.blade.php index 8748e817..6002e01f 100644 --- a/resources/views/livewire/insights-filter.blade.php +++ b/resources/views/livewire/insights-filter.blade.php @@ -1,11 +1,13 @@ - @push('scripts') +@push('scripts') @endpush -
-
- From 3dc13ea35a8e0157554a34bfc1495e707ea70abd Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:59:22 -0500 Subject: [PATCH 16/37] Removes unnecessary variables from charts and removes original animation from doughnut charts --- .../accepted-and-follow-up-apps-percentage-chart.blade.php | 3 --- .../livewire/student-apps-viewed-not-viewed-chart.blade.php | 4 +--- resources/views/livewire/students-app-count-chart.blade.php | 2 +- .../views/livewire/students-app-filing-status-chart.blade.php | 2 +- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php index 9a1a56ec..826d3016 100644 --- a/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php +++ b/resources/views/livewire/accepted-and-follow-up-apps-percentage-chart.blade.php @@ -9,7 +9,6 @@ this.data = [1, 1]; // Fallback to ensure the chart renders } - var chart5_complete = false; var progress = this.data[0]; const progressTextPlugin = { @@ -85,8 +84,6 @@ plugins: [progressTextPlugin], }); - // animateProgress(chart_instance, progress, false); - Livewire.on('refreshChart5', (data, labels) => { if (data.every(value => value === 0)) { var data = [1, 1]; // Fallback to ensure the chart renders diff --git a/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php index 0c4b39e6..4262a671 100644 --- a/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php +++ b/resources/views/livewire/student-apps-viewed-not-viewed-chart.blade.php @@ -9,7 +9,7 @@ if (this.data.every(value => value === 0)) { this.data = [1, 1]; // Fallback to ensure the chart renders } - var chart4_complete = false; + var progress = this.data[0]; const progressTextPlugin = { @@ -82,8 +82,6 @@ plugins: [progressTextPlugin], }); - // animateProgress(chart_instance, progress, false); - Livewire.on('refreshChart4', (data, labels) => { if (data.every(value => value === 0)) { data = [1, 1]; // Fallback to ensure the chart renders diff --git a/resources/views/livewire/students-app-count-chart.blade.php b/resources/views/livewire/students-app-count-chart.blade.php index 1590c019..336bf3bd 100644 --- a/resources/views/livewire/students-app-count-chart.blade.php +++ b/resources/views/livewire/students-app-count-chart.blade.php @@ -5,7 +5,7 @@ labels: @entangle('labels'), selected_semesters: @entangle('selected_semesters'), init() { - var chart2_complete = false; + const appCountBySemesterChart = new Chart( this.$refs.appCountBySemester, { diff --git a/resources/views/livewire/students-app-filing-status-chart.blade.php b/resources/views/livewire/students-app-filing-status-chart.blade.php index 6327ab3a..7e240cc8 100644 --- a/resources/views/livewire/students-app-filing-status-chart.blade.php +++ b/resources/views/livewire/students-app-filing-status-chart.blade.php @@ -5,7 +5,7 @@ labels: @entangle('labels'), selected_semesters: @entangle('selected_semesters'), init() { - var chart1_complete = false; + const appCountFilingStatusChart = new Chart( this.$refs.appCountFilingStatus, { From 695a042efccfa7b2d3ca87f9d37ec91421f23ad2 Mon Sep 17 00:00:00 2001 From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com> Date: Tue, 24 Sep 2024 10:10:52 -0500 Subject: [PATCH 17/37] Draws vertical line on bar charts to mark current semester --- app/Http/Livewire/StudentsAppCountChart.php | 8 ++++ .../Livewire/StudentsAppFilingStatusChart.php | 4 ++ .../students-app-count-chart.blade.php | 35 +++++++++++++++- ...students-app-filing-status-chart.blade.php | 41 ++++++++++++++++++- 4 files changed, 86 insertions(+), 2 deletions(-) diff --git a/app/Http/Livewire/StudentsAppCountChart.php b/app/Http/Livewire/StudentsAppCountChart.php index dead13b8..2a894fe0 100644 --- a/app/Http/Livewire/StudentsAppCountChart.php +++ b/app/Http/Livewire/StudentsAppCountChart.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire; +use App\Helpers\Semester; use App\Insights\StudentApplications\StudentDataInsight; use Livewire\Component; @@ -10,10 +11,16 @@ class StudentsAppCountChart extends Component public array $labels; public array $data; + public $current; public $selected_semesters = []; public $selected_schools = []; protected $listeners = ['refreshData2', 'refreshChart2']; + public function mount() + { + $this->current = Semester::current(); + } + public function refreshChart2($data, $labels) {} public function refreshData2($selected_semesters, $selected_schools) { @@ -44,6 +51,7 @@ public function render() return view('livewire.students-app-count-chart', [ 'data' => $this->data, 'labels' => $this->labels, + 'current' => [$this->current], ]); } } diff --git a/app/Http/Livewire/StudentsAppFilingStatusChart.php b/app/Http/Livewire/StudentsAppFilingStatusChart.php index ad790b96..cec21ef8 100644 --- a/app/Http/Livewire/StudentsAppFilingStatusChart.php +++ b/app/Http/Livewire/StudentsAppFilingStatusChart.php @@ -2,6 +2,7 @@ namespace App\Http\Livewire; +use App\Helpers\Semester; use App\Insights\StudentApplications\StudentDataInsight; use Livewire\Component; @@ -10,6 +11,7 @@ class StudentsAppFilingStatusChart extends Component public array $labels; public array $data; + public $current; public $selected_semesters = []; public $selected_schools = []; public $weeks_before_semester_start; @@ -21,6 +23,7 @@ public function mount() { $this->weeks_before_semester_start = 4; $this->weeks_before_semester_end = 4; + $this->current = Semester::current(); $this->selected_filing_statuses = ["accepted", "maybe later", "not interested", "new", "follow up"]; } @@ -56,6 +59,7 @@ public function render() return view('livewire.students-app-filing-status-chart', [ 'data' => $this->data, 'labels' => $this->labels, + 'current' => $this->current, ]); } } diff --git a/resources/views/livewire/students-app-count-chart.blade.php b/resources/views/livewire/students-app-count-chart.blade.php index 336bf3bd..c9d8a341 100644 --- a/resources/views/livewire/students-app-count-chart.blade.php +++ b/resources/views/livewire/students-app-count-chart.blade.php @@ -3,8 +3,40 @@ x-data="{ data: @entangle('data'), labels: @entangle('labels'), + current: @entangle('current'), selected_semesters: @entangle('selected_semesters'), init() { + var current = this.current; + + const currentSemesterPlugin = { + id: 'currentSemesterPlugin', + afterDatasetsDraw: function(chart) { + const ctx = chart.ctx; + const xAxis = chart.scales['x']; // X-axis + const yAxis = chart.scales['y']; // Y-axis + + // Determine where to draw the line + const currentIndex = chart.data.labels.indexOf(current); // Label of the current period + if (currentIndex >= 0) { + const xPos = xAxis.getPixelForValue(currentIndex); + + // Draw the line + ctx.save(); + ctx.beginPath(); + ctx.moveTo(xPos, yAxis.top); + ctx.lineTo(xPos, yAxis.bottom); + ctx.lineWidth = 2; + ctx.strokeStyle = 'gray'; + ctx.stroke(); + + // Optional: Add a label for the line + ctx.font = '12px Arial'; + ctx.fillStyle = 'gray'; + ctx.fillText('Current', xPos + 5, yAxis.top - 5); + ctx.restore(); + } + } + }; const appCountBySemesterChart = new Chart( this.$refs.appCountBySemester, @@ -40,6 +72,7 @@ } } }, + plugins: [currentSemesterPlugin], } ); Livewire.on('refreshChart2', (data, labels) => { @@ -55,7 +88,7 @@ -
+