Date: Fri, 11 Apr 2025 17:01:05 -0500
Subject: [PATCH 2/4] Changes `toCsv()` macro from Builder facade to Collection
---
app/Http/Livewire/ProfileStudents.php | 24 ++++---
app/Macros/CollectionMacros.php | 36 ++++++++++
app/Providers/AppServiceProvider.php | 25 +------
app/Student.php | 70 +++++++++++++++++++
.../views/livewire/profile-students.blade.php | 1 +
5 files changed, 124 insertions(+), 32 deletions(-)
create mode 100644 app/Macros/CollectionMacros.php
diff --git a/app/Http/Livewire/ProfileStudents.php b/app/Http/Livewire/ProfileStudents.php
index b5893288..ff91060f 100644
--- a/app/Http/Livewire/ProfileStudents.php
+++ b/app/Http/Livewire/ProfileStudents.php
@@ -37,6 +37,8 @@ class ProfileStudents extends Component
public $travel_other_filter = '';
public $tag_filter = '';
+
+ public $filing_status = '';
protected $listeners = [
'profileStudentStatusUpdated' => 'refreshStudents'
@@ -56,7 +58,7 @@ class ProfileStudents extends Component
'semester_filter' => ['except' => '', 'as' => 'semester'],
];
- public function getStudentsBuilderProperty()
+ public function getStudentsProperty()
{
return $this->profile->students()
->submitted()
@@ -70,13 +72,9 @@ public function getStudentsBuilderProperty()
->willTravelOther($this->travel_other_filter)
->willWorkWithAnimals($this->animals_filter)
->needsResearchCredit($this->credit_filter)
- ->with('user:id,email')
- ->orderBy('last_name');
- }
-
- public function getStudentsProperty()
- {
- return $this->getStudentsBuilderProperty()->get();
+ ->with('user:id,email', 'research_profile', 'tags')
+ ->orderBy('last_name')
+ ->get();
}
public function updated($name, $value)
@@ -91,7 +89,15 @@ public function refreshStudents()
public function export()
{
- return $this->getStudentsBuilderProperty()->toCsv();
+ $students = $this->students->where('application.status', $this->filing_status);
+
+ if ($students->isEmpty()) {
+ $this->emit('alert', "No records available for the filters applied", 'danger');
+ }
+ else {
+ $student_apps = Student::exportStudentApps($students, 'student_applications.csv');
+ return $student_apps->toCsv('students_apps.csv');
+ }
}
public function render()
diff --git a/app/Macros/CollectionMacros.php b/app/Macros/CollectionMacros.php
new file mode 100644
index 00000000..531ba9c8
--- /dev/null
+++ b/app/Macros/CollectionMacros.php
@@ -0,0 +1,36 @@
+streamDownload(function () use ($results) {
+ if ($results->isEmpty()) return;
+
+ $titles = implode(',', array_keys((array) $results->first()->getAttributes()));
+
+ $values = $results->map(function ($result) {
+ return collect($result->getAttributes())->map(function ($value) {
+ if (is_null($value)) return '""';
+
+ $value = (string) $value;
+ $value = str_replace(['\\r', '\\n', '\\t'], ' ', $value);
+ $value = preg_replace('/\s+/', ' ', $value);
+ $value = str_replace('"', '""', $value);
+
+ return "\"{$value}\"";
+ })->implode(',');
+ });
+
+ $values->prepend($titles);
+ echo $values->implode("\n");
+ }, $name ?? 'export.csv');
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 7420c002..53469956 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -2,6 +2,7 @@
namespace App\Providers;
+use App\Macros\CollectionMacros;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
use Illuminate\Pagination\Paginator;
@@ -9,7 +10,6 @@
use App\Setting;
use Collective\Html\FormFacade as Form;
use Illuminate\Support\Facades\Cache;
-use Illuminate\Database\Eloquent\Builder;
class AppServiceProvider extends ServiceProvider
{
@@ -47,28 +47,7 @@ public function boot()
view()->share('settings', $settings);
});
- Builder::macro('toCsv', function ($name = null) {
- $query = $this;
-
- return response()->streamDownload(function () use ($query) {
- $results = $query->get();
-
- if ($results->count() < 1) return;
-
- $titles = implode(',', array_keys((array) $results->first()->getAttributes()));
-
- $values = $results->map(function ($result) {
- return implode(',', collect($result->getAttributes())->map(function ($thing) {
- return '"'.$thing.'"';
- })->toArray());
- });
-
- $values->prepend($titles);
-
- echo $values->implode("\n");
- }, $name ?? 'export.csv');
- });
-
+ CollectionMacros::register();
}
/**
diff --git a/app/Student.php b/app/Student.php
index fbecf415..56f75329 100644
--- a/app/Student.php
+++ b/app/Student.php
@@ -125,6 +125,76 @@ public function updateAcceptedStats(Profile $profile, $accepted = true)
}
}
+ public static function exportStudentApps(EloquentCollection $students)
+ {
+ $students->map(function ($student) {
+
+ $st = clone $student;
+
+ $st->email = $st->user->email;
+ $st->link = "https://profiles.utdallas.edu/students/{$st->slug}";
+ $st->brief_intro = $st->research_profile->brief_intro;
+ $st->intro = $st->research_profile->intro;
+ $st->interest = $st->research_profile->interest;
+ $st->major = $st->research_profile->major;
+ $st->schools = $st->research_profile->schools;
+ $st->languages = $st->research_profile->languages;
+ $st->languages_other = $st->research_profile->language_other_name;
+ $st->languages_proficiency = $st->research_profile->lang_proficiency;
+ $st->semesters = $st->research_profile->semesters;
+ $st->availability = $st->research_profile->availability;
+ $st->earn_credit = $st->research_profile->credit;
+ $st->graduation_date = $st->research_profile->graduation_date;
+ $st->bbs_travel_centers = $st->research_profile->travel;
+ $st->bbs_travel_other = $st->research_profile->travel_other;
+ $st->bbs_comfortable_animals = $st->research_profile->animals;
+ $st->other_info = $st->research_profile->other_info;
+
+ $st->earn_credit = match ($st->earn_credit) {
+ '1' => 'credit',
+ '0' => 'volunteer',
+ '-1' => 'no preference',
+ default => $st->earn_credit,
+ };
+
+ foreach ([
+ 'bbs_travel_centers',
+ 'bbs_travel_other',
+ 'bbs_comfortable_animals',
+ ] as $yes_no_field) {
+ $st->$yes_no_field = match ($st->$yes_no_field) {
+ '1' => 'yes',
+ '0' => 'no',
+ default => $st->$yes_no_field,
+ };
+ }
+
+ $st->topics = $st->tags->pluck('name')->implode(", ");
+
+ foreach($st->getAttributes() as $attr => $value) {
+ if (is_array($value)) {
+ if (array_is_list($value)) {
+ $st->$attr = implode(", ", $value);
+ } else {
+ $json = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
+ $json = str_replace([',', ':'], [', ', ': '], $json);
+ $json = str_replace(["\r", "\n"], ' ', $json);
+ $st->$attr = $json;
+ }
+ }
+ if ($value === null) {
+ $st->$attr = '';
+ }
+ }
+
+ unset($st->id, $st->user_id, $st->type, $st->application, $st->tags, $st->research_profile, $st->user);
+
+ return $st;
+ });
+
+ return $students;
+ }
+
/**
* Increments the Student Application view count
*
diff --git a/resources/views/livewire/profile-students.blade.php b/resources/views/livewire/profile-students.blade.php
index 2e272989..234d57cb 100644
--- a/resources/views/livewire/profile-students.blade.php
+++ b/resources/views/livewire/profile-students.blade.php
@@ -21,6 +21,7 @@ class="nav-link @if($loop->first) active @endif"
aria-controls="tab_{{ Str::slug($status) }}"
aria-selected="{{ $loop->first ? 'true' : 'false' }}"
wire:ignore.self
+ wire:click="$set('filing_status', @js($status))"
>
{{ $status_name }}
From b78eaf23a7b6ce772b00b1c38f775cf6381b6751 Mon Sep 17 00:00:00 2001
From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com>
Date: Mon, 14 Apr 2025 13:21:35 -0500
Subject: [PATCH 3/4] Corrects value returned by the exportStudentApps() method
---
app/Student.php | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/Student.php b/app/Student.php
index 56f75329..46f46ce5 100644
--- a/app/Student.php
+++ b/app/Student.php
@@ -127,7 +127,7 @@ public function updateAcceptedStats(Profile $profile, $accepted = true)
public static function exportStudentApps(EloquentCollection $students)
{
- $students->map(function ($student) {
+ $apps = $students->map(function ($student) {
$st = clone $student;
@@ -192,7 +192,7 @@ public static function exportStudentApps(EloquentCollection $students)
return $st;
});
- return $students;
+ return $apps;
}
/**
From c81a9c3d38f1bdb94df94dded6dbafc05e285c57 Mon Sep 17 00:00:00 2001
From: Betsy Castro <5490820+betsyecastro@users.noreply.github.com>
Date: Mon, 14 Apr 2025 16:35:26 -0500
Subject: [PATCH 4/4] Styles 'Export' button, renames the 'export' method to
'exportToCsv' and adds docblocks
---
app/Http/Livewire/ProfileStudents.php | 4 ++--
app/Macros/CollectionMacros.php | 10 +++++++++-
app/Student.php | 8 ++++++++
resources/views/livewire/profile-students.blade.php | 13 +++++++++++--
4 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/app/Http/Livewire/ProfileStudents.php b/app/Http/Livewire/ProfileStudents.php
index ff91060f..f8f53220 100644
--- a/app/Http/Livewire/ProfileStudents.php
+++ b/app/Http/Livewire/ProfileStudents.php
@@ -87,7 +87,7 @@ public function refreshStudents()
$this->students = $this->getStudentsProperty();
}
- public function export()
+ public function exportToCsv()
{
$students = $this->students->where('application.status', $this->filing_status);
@@ -95,7 +95,7 @@ public function export()
$this->emit('alert', "No records available for the filters applied", 'danger');
}
else {
- $student_apps = Student::exportStudentApps($students, 'student_applications.csv');
+ $student_apps = Student::exportStudentApps($students);
return $student_apps->toCsv('students_apps.csv');
}
}
diff --git a/app/Macros/CollectionMacros.php b/app/Macros/CollectionMacros.php
index 531ba9c8..cbc82241 100644
--- a/app/Macros/CollectionMacros.php
+++ b/app/Macros/CollectionMacros.php
@@ -7,7 +7,15 @@ class CollectionMacros
{
public static function register()
{
+ /**
+ * Adds a macro to export the collection to a streamed CSV response.
+ *
+ * @param string|null $name The name of the CSV file to download. Defaults to 'export.csv'.
+ * @return \Symfony\Component\HttpFoundation\StreamedResponse
+ */
Collection::macro('toCsv', function ($name = null) {
+
+ /** @var \Illuminate\Support\Collection $this */
$results = $this;
return response()->streamDownload(function () use ($results) {
@@ -20,7 +28,7 @@ public static function register()
if (is_null($value)) return '""';
$value = (string) $value;
- $value = str_replace(['\\r', '\\n', '\\t'], ' ', $value);
+ $value = str_replace(["\r", "\n", "\t"], ' ', $value);
$value = preg_replace('/\s+/', ' ', $value);
$value = str_replace('"', '""', $value);
diff --git a/app/Student.php b/app/Student.php
index 46f46ce5..32cc2fff 100644
--- a/app/Student.php
+++ b/app/Student.php
@@ -125,6 +125,14 @@ public function updateAcceptedStats(Profile $profile, $accepted = true)
}
}
+ /**
+ * Transforms a collection of students with their associated 'user' and 'research_profile'
+ * data into a flat collection suitable for CSV export.
+ *
+ * @param \Illuminate\Database\Eloquent\Collection $students
+ * @return \Illuminate\Support\Collection
+ *
+ */
public static function exportStudentApps(EloquentCollection $students)
{
$apps = $students->map(function ($student) {
diff --git a/resources/views/livewire/profile-students.blade.php b/resources/views/livewire/profile-students.blade.php
index 234d57cb..8c3288ef 100644
--- a/resources/views/livewire/profile-students.blade.php
+++ b/resources/views/livewire/profile-students.blade.php
@@ -1,6 +1,15 @@