diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php
index 23126a7..36fa9a9 100644
--- a/app/Actions/Fortify/CreateNewUser.php
+++ b/app/Actions/Fortify/CreateNewUser.php
@@ -12,7 +12,6 @@ use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Laravel\Fortify\Contracts\CreatesNewUsers;
use function mb_substr;
-use function sendMessage;
class CreateNewUser implements CreatesNewUsers
{
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
new file mode 100644
index 0000000..872bd89
--- /dev/null
+++ b/app/Exceptions/Handler.php
@@ -0,0 +1,19 @@
+with('warning', $e->getMessage());
+ }
+ return parent::render($request, $e);
+ }
+}
diff --git a/app/Exceptions/TabulationException.php b/app/Exceptions/TabulationException.php
new file mode 100644
index 0000000..425943d
--- /dev/null
+++ b/app/Exceptions/TabulationException.php
@@ -0,0 +1,24 @@
+with('error', $this->getMessage());
+// }
+// return parent::render($request, $e);
+ }
+}
diff --git a/app/Http/Controllers/Admin/EntryController.php b/app/Http/Controllers/Admin/EntryController.php
index 06ea82c..31f27a5 100644
--- a/app/Http/Controllers/Admin/EntryController.php
+++ b/app/Http/Controllers/Admin/EntryController.php
@@ -16,7 +16,7 @@ class EntryController extends Controller
public function index()
{
if(! Auth::user()->is_admin) abort(403);
- $filters = session('adminEntryFilters');
+ $filters = session('adminEntryFilters') ?? null;
$minGrade = Audition::min('minimum_grade');
$maxGrade = Audition::max('maximum_grade');
$auditions = Audition::orderBy('score_order')->get();
@@ -26,6 +26,10 @@ class EntryController extends Controller
$entries = Entry::with(['student.school','audition']);
$entries->orderBy('updated_at','DESC');
if($filters) {
+ if($filters['id']) {
+ $entries->where('id', $filters['id']);
+ }
+
if($filters['audition']) {
$entries->where('audition_id', $filters['audition']);
}
@@ -91,7 +95,9 @@ class EntryController extends Controller
if(! Auth::user()->is_admin) abort(403);
$students = Student::with('school')->orderBy('last_name')->orderBy('first_name')->get();
$auditions = Audition::orderBy('score_order')->get();
- return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]);
+ $scores = $entry->scoreSheets()->get();
+// return view('admin.entries.edit', ['entry' => $entry, 'students' => $students, 'auditions' => $auditions]);
+ return view('admin.entries.edit', compact('entry', 'students', 'auditions','scores'));
}
public function update(Entry $entry)
diff --git a/app/Http/Controllers/Admin/ScoringGuideController.php b/app/Http/Controllers/Admin/ScoringGuideController.php
index e40309d..ac08428 100644
--- a/app/Http/Controllers/Admin/ScoringGuideController.php
+++ b/app/Http/Controllers/Admin/ScoringGuideController.php
@@ -12,7 +12,6 @@ use function abort;
use function dd;
use function request;
use function response;
-use function sendMessage;
class ScoringGuideController extends Controller
{
diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php
index cde34a7..2932b4f 100644
--- a/app/Http/Controllers/EntryController.php
+++ b/app/Http/Controllers/EntryController.php
@@ -8,7 +8,6 @@ use App\Models\School;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use function abort;
-use function sendMessage;
class EntryController extends Controller
{
@@ -43,7 +42,7 @@ class EntryController extends Controller
public function destroy(Request $request, Entry $entry)
{
$entry->delete();
- sendMessage('The ' . $entry->audition->name . 'entry for ' . $entry->student->full_name(). 'has been deleted.','success');
- return redirect('/entries');
+ return redirect('/entries')->with('success','The ' . $entry->audition->name . 'entry for ' . $entry->student->full_name(). 'has been deleted.');
+
}
}
diff --git a/app/Http/Controllers/FilterController.php b/app/Http/Controllers/FilterController.php
index bfda9ab..00e2012 100644
--- a/app/Http/Controllers/FilterController.php
+++ b/app/Http/Controllers/FilterController.php
@@ -9,6 +9,7 @@ class FilterController extends Controller
public function adminEntryFilter(Request $request)
{
$filters = array();
+ $filters['id'] = request('id_filter') ?? null;
$filters['audition'] = request('audition_filter') ? request('audition_filter') : null;
$filters['school'] = request('school_filter') ? request('school_filter') : null;
$filters['grade'] = request('grade_filter') ? request('grade_filter') : null;
diff --git a/app/Http/Controllers/Tabulation/TabulationController.php b/app/Http/Controllers/Tabulation/TabulationController.php
index 16950df..88b8d07 100644
--- a/app/Http/Controllers/Tabulation/TabulationController.php
+++ b/app/Http/Controllers/Tabulation/TabulationController.php
@@ -8,6 +8,7 @@ use App\Models\Entry;
use App\Models\ScoreSheet;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
+use function compact;
use function dump;
use function redirect;
@@ -18,6 +19,11 @@ class TabulationController extends Controller
return view('tabulation.choose_entry');
}
+ public function destroyScore(ScoreSheet $score) {
+ $score->delete();
+ return redirect()->back()->with('success','Score Deleted');
+ }
+
public function entryScoreSheet(Request $request)
{
$existing_sheets = [];
@@ -74,5 +80,22 @@ class TabulationController extends Controller
return redirect()->route('tabulation.chooseEntry')->with('success',count($preparedScoreSheets) . " Scores created");
}
+ public function status()
+ {
+ $auditions = Audition::with('entries.scoreSheets')->with('room.judges')->orderBy('score_order')->get();
+
+ return view('tabulation.status',compact('auditions'));
+ }
+
+ public function auditionSeating(Audition $audition)
+ {
+// $entries = $audition->entries()->with(['student','scoreSheets.audition.scoringGuide','audition.room.judges'])->get();
+// $entries = $entries->sortByDesc(function ($entry) {
+// return $entry->totalScore();
+// });
+ $entries = $audition->rankedEntries()->load('student','scoreSheets.audition.scoringGuide.subscores');
+ $judges = $audition->judges();
+ return view('tabulation.auditionSeating',compact('audition','entries','judges'));
+ }
}
diff --git a/app/Models/Audition.php b/app/Models/Audition.php
index b030f4b..ee3a19c 100644
--- a/app/Models/Audition.php
+++ b/app/Models/Audition.php
@@ -2,11 +2,13 @@
namespace App\Models;
+use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
+use phpDocumentor\Reflection\Types\Boolean;
use PhpParser\Node\Scalar\String_;
use function now;
@@ -30,6 +32,7 @@ class Audition extends Model
return $this->hasMany(Entry::class);
}
+
public function room(): BelongsTo
{
return $this->belongsTo(Room::class);
@@ -119,4 +122,49 @@ class Audition extends Model
}
return null;
}
+
+// public function judges()
+// {
+// // Very inefficient, need a better way
+// return User::join('room_user', 'users.id', '=', 'room_user.user_id')
+// ->join('rooms', 'room_user.room_id', '=', 'rooms.id')
+// ->join('auditions', 'rooms.id', '=', 'auditions.room_id')
+// ->where('auditions.id', $this->id)
+// ->select('users.*') // avoid getting other tables' columns
+// ->get();
+// }
+ /**
+ * @return Collection
+ */
+ public function judges()
+ {
+ return $this->room->judges;
+ }
+
+ public function scoredEntries()
+ {
+ return $this->entries->filter(function($entry) {
+ return $entry->scoreSheets->count() >= $this->judges()->count();
+ });
+ }
+
+ public function rankedEntries()
+ {
+ $entries = $this->entries()->with(['audition.scoringGuide.subscores','scoreSheets.judge'])->get();
+ $entries = $entries->all();
+ usort($entries, function($a,$b) {
+ $aScores = $a->finalScoresArray();
+ $bScores = $b->finalScoresArray();
+
+ $length = min(count($aScores), count($bScores));
+ for ($i=0; $i<$length; $i++) {
+ if ($aScores[$i] !== $bScores[$i]) {
+ return $bScores[$i] - $aScores[$i];
+ }
+ }
+ return 0;
+ });
+ $collection = new \Illuminate\Database\Eloquent\Collection($entries);
+ return $collection;
+ }
}
diff --git a/app/Models/Entry.php b/app/Models/Entry.php
index f6ac8c4..f00bc67 100644
--- a/app/Models/Entry.php
+++ b/app/Models/Entry.php
@@ -2,6 +2,7 @@
namespace App\Models;
+use App\Exceptions\TabulationException;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
@@ -13,6 +14,7 @@ class Entry extends Model
{
use HasFactory;
protected $guarded = [];
+ protected $hasCheckedScoreSheets = false;
public function student(): BelongsTo
{
@@ -38,10 +40,70 @@ class Entry extends Model
public function scoreSheets(): HasMany
{
return $this->hasMany(ScoreSheet::class);
+
}
+ function verifyScoreSheets()
+ {
+ if ($this->hasCheckedScoreSheets) return true;
+ $judges = $this->audition->room->judges;
+ foreach ($this->scoreSheets as $sheet) {
+ if (! $judges->contains($sheet->user_id)) {
+ $invalidJudge = User::find($sheet->user_id);
+// redirect ('/tabulation')->with('warning','Invalid scores for entry ' . $this->id . ' exist from ' . $invalidJudge->full_name());
+ // Abort execution, and redirect to /tabulation with a warning message
+ throw new TabulationException('Invalid scores for entry ' . $this->id . ' exist from ' . $invalidJudge->full_name());
+ }
+ }
+ return true;
+ }
+
+
public function scoreFromJudge($user): ScoreSheet|null
{
- return $this->scoreSheets()->where('user_id','=',$user)->first() ?? null;
+// return $this->scoreSheets()->where('user_id','=',$user)->first() ?? null;
+ return $this->scoreSheets->firstWhere('user_id', $user) ?? null;
+
+ }
+
+ public function totalScore()
+ {
+ $this->verifyScoreSheets();
+ $totalScore = 0;
+ foreach ($this->scoreSheets as $sheet)
+ {
+ $totalScore += $sheet->totalScore();
+ }
+ return $totalScore;
+ }
+
+ /**
+ * @throws TabulationException
+ */
+ public function finalScoresArray()
+ {
+ $this->verifyScoreSheets();
+ $finalScoresArray = [];
+ $subscoresTiebreakOrder = $this->audition->scoringGuide->subscores->sortBy('tiebreak_order');
+ // initialize the return array
+ foreach ($subscoresTiebreakOrder as $subscore) {
+ $finalScoresArray[$subscore->id] = 0;
+ }
+ // add the subscores from each score sheet
+ foreach($this->scoreSheets as $sheet) {
+ foreach($sheet->subscores as $ss) {
+ $finalScoresArray[$ss['subscore_id']] += $ss['score'];
+ }
+ }
+ // calculate weighted final score
+ $totalScore = 0;
+ $totalWeight = 0;
+ foreach ($subscoresTiebreakOrder as $subscore) {
+ $totalScore += ($finalScoresArray[$subscore->id] * $subscore->weight);
+ $totalWeight += $subscore->weight;
+ }
+ $totalScore = ($totalScore / $totalWeight);
+ array_unshift($finalScoresArray,$totalScore);
+ return $finalScoresArray;
}
}
diff --git a/app/Models/RoomUser.php b/app/Models/RoomUser.php
new file mode 100644
index 0000000..061368b
--- /dev/null
+++ b/app/Models/RoomUser.php
@@ -0,0 +1,25 @@
+belongsTo(User::class);
+ }
+
+ public function judge(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ public function room(): BelongsTo
+ {
+ return $this->belongsTo(Room::class);
+ }
+}
diff --git a/app/Models/ScoreSheet.php b/app/Models/ScoreSheet.php
index e86441a..eb1ee73 100644
--- a/app/Models/ScoreSheet.php
+++ b/app/Models/ScoreSheet.php
@@ -5,6 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
+use Illuminate\Database\Eloquent\Relations\HasOneThrough;
class ScoreSheet extends Model
{
@@ -15,6 +16,7 @@ class ScoreSheet extends Model
];
protected $casts = ['subscores' => 'json'];
+ protected $with = ['entry','judge','audition.scoringGuide'];
public function entry(): BelongsTo
{
@@ -26,8 +28,37 @@ class ScoreSheet extends Model
return $this->belongsTo(User::class, 'user_id');
}
+ public function audition(): HasOneThrough
+ {
+ return $this->hasOneThrough(
+ Audition::class, // The final model you want to access
+ Entry::class, // The intermediate model
+ 'id', // Foreign key on the intermediate model (Entry)
+ 'id', // Foreign key on the final model (Audition)
+ 'entry_id', // Local key on the current model (ScoreSheet)
+ 'audition_id' // Local key on the intermediate model (Entry)
+ );
+ }
+
public function getSubscore($id)
{
return $this->subscores[$id]['score'] ?? false;
}
+
+ public function totalScore() {
+ $totalScore = 0;
+ $totalWeights = 0;
+ foreach ( $this->audition->scoringGuide->subscores as $subscore) {
+ $totalScore += $this->getSubscore($subscore->id) * $subscore->weight;
+ $totalWeights += $subscore->weight;
+ }
+ return $totalScore / $totalWeights;
+ }
+
+ public function isValid() {
+ $judges = $this->audition->judges();
+ return $judges->contains('id', $this->judge->id);
+ }
+
+
}
diff --git a/app/Models/ScoringGuide.php b/app/Models/ScoringGuide.php
index bd0963f..6455993 100644
--- a/app/Models/ScoringGuide.php
+++ b/app/Models/ScoringGuide.php
@@ -16,6 +16,7 @@ class ScoringGuide extends Model
{
use HasFactory;
protected $guarded = [];
+ protected $with = ['subscores'];
public function auditions(): HasMany
{
diff --git a/app/Models/User.php b/app/Models/User.php
index 9e29c8b..fa2b74f 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -62,6 +62,12 @@ class User extends Authenticatable implements MustVerifyEmail
return $this->first_name . ' ' . $this->last_name;
}
+ public function short_name(): String
+ {
+ // return the first letter of $this->first_name and the full $this->last_name
+ return $this->first_name[0] . '. ' . $this->last_name;
+ }
+
public function has_school(): bool
{
return $this->school_id !== null;
diff --git a/app/helpers.php b/app/helpers.php
index 78d955c..0542bb6 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -23,18 +23,3 @@ function tw_max_width_class_array() :Array {
return $return;
}
-function getMessages() {
- $flash = Session::get('_flash');
- $messages = $flash['new'];
- $return = [];
- foreach ($messages as $message) {
- if (substr($message, 0,4) != 'msg|') continue;
- $type = Session::get($message);
- $return[] = ['message' => substr($message,4), 'type' => $type];
- }
- return $return;
-}
-
-function sendMessage(String $message, String $type = 'success') {
- Session::flash('msg|'.$message,$type);
-}
diff --git a/database/seeders/ScoreAllAuditions.php b/database/seeders/ScoreAllAuditions.php
new file mode 100644
index 0000000..230199d
--- /dev/null
+++ b/database/seeders/ScoreAllAuditions.php
@@ -0,0 +1,42 @@
+rooms as $room) {
+ foreach ($room->auditions as $audition){
+ $scoringGuide = $audition->scoringGuide;
+ $subscores = $scoringGuide->subscores;
+ foreach ($audition->entries as $entry){
+ $scoreArray = [];
+ foreach ($subscores as $subscore) {
+ $scoreArray[$subscore->id] = [
+ 'score' => mt_rand(0,100),
+ 'subscore_id' => $subscore->id,
+ 'subscore_name' => $subscore->name
+ ];
+ }
+ ScoreSheet::create([
+ 'user_id' => $judge->id,
+ 'entry_id' => $entry->id,
+ 'subscores' => $scoreArray
+ ]);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/resources/views/admin/entries/edit.blade.php b/resources/views/admin/entries/edit.blade.php
index 9a863d1..249b14a 100644
--- a/resources/views/admin/entries/edit.blade.php
+++ b/resources/views/admin/entries/edit.blade.php
@@ -30,6 +30,32 @@
+
+ {{$subscore['subscore_name']}} {{$subscore['score'] }}