diff --git a/app/Concerns/CanGroupByDataLevel.php b/app/Concerns/CanGroupByDataLevel.php new file mode 100644 index 0000000..04b6218 --- /dev/null +++ b/app/Concerns/CanGroupByDataLevel.php @@ -0,0 +1,80 @@ +is(DataLevel::TOTAL)) { + $query->groupByTotal(); + } + + if ($level->is(DataLevel::DIASPORA)) { + $query->whereNotNull('country_id') + ->when($country, fn (Builder $query) => $query->where('country_id', $country)); + + if (! $aggregate) { + $query->groupByCountry(); + } + } + + if ($level->is(DataLevel::NATIONAL)) { + $query->whereNull('country_id'); + + if (filled($locality)) { + $query->where('locality_id', $locality) + ->groupByLocality(); + } elseif (filled($county)) { + $query->where('county_id', $county); + + if ($aggregate) { + $query->groupByCounty(); + } else { + $query->groupByLocality(); + } + } else { + $query->whereNotNull('locality_id') + ->whereNotNull('county_id'); + + if ($aggregate) { + $query->groupByTotal(); + } else { + $query->groupByCounty(); + } + } + } + + return $query; + } + + public function scopeGroupByCountry(Builder $query): Builder + { + return $query->groupBy('country_id') + ->addSelect(new Alias('country_id', 'place')); + } + + public function scopeGroupByCounty(Builder $query): Builder + { + return $query->groupBy('county_id') + ->addSelect(new Alias('county_id', 'place')); + } + + public function scopeGroupByLocality(Builder $query): Builder + { + return $query->groupBy('locality_id') + ->addSelect(new Alias('locality_id', 'place')); + } + + public function scopeGroupByTotal(Builder $query): Builder + { + return $query->addSelect(new Alias(new Value(0), 'place')); + } +} diff --git a/app/Concerns/CanGroupByPlace.php b/app/Concerns/CanGroupByPlace.php deleted file mode 100644 index 883a81d..0000000 --- a/app/Concerns/CanGroupByPlace.php +++ /dev/null @@ -1,35 +0,0 @@ -groupBy('country_id') - ->addSelect(new Alias('country_id', 'place')); - } - - public function scopeGroupByCounty(Builder $query): Builder - { - return $query->groupBy('county_id') - ->addSelect(new Alias('county_id', 'place')); - } - - public function scopeGroupByLocality(Builder $query): Builder - { - return $query->groupBy('locality_id') - ->addSelect(new Alias('locality_id', 'place')); - } - - public function scopeGroupByTotal(Builder $query): Builder - { - return $query->addSelect(new Alias(new Value(0), 'place')); - } -} diff --git a/app/Livewire/Pages/ElectionResults.php b/app/Livewire/Pages/ElectionResults.php index 58822cc..faf3ea9 100644 --- a/app/Livewire/Pages/ElectionResults.php +++ b/app/Livewire/Pages/ElectionResults.php @@ -4,6 +4,10 @@ namespace App\Livewire\Pages; +use App\Models\Vote; +use Illuminate\Support\Collection; +use Illuminate\Support\Number; +use Livewire\Attributes\Computed; use Livewire\Attributes\Layout; class ElectionResults extends ElectionPage @@ -13,4 +17,27 @@ public function render() { return view('livewire.pages.election-results'); } + + #[Computed] + public function data(): Collection + { + return Vote::query() + ->whereBelongsTo($this->election) + ->forLevel( + level: $this->level, + country: null, + county: $this->county, + locality: null, + ) + ->with('votable') + ->get() + ->mapWithKeys(function (Vote $vote) { + return [ + $vote->place => [ + 'value' => Number::format(ensureNumeric($vote->votes)), + 'color' => $vote->votable->color, + ], + ]; + }); + } } diff --git a/app/Models/Turnout.php b/app/Models/Turnout.php index e69b9c9..99f5c81 100644 --- a/app/Models/Turnout.php +++ b/app/Models/Turnout.php @@ -5,7 +5,7 @@ namespace App\Models; use App\Concerns\BelongsToElection; -use App\Concerns\CanGroupByPlace; +use App\Concerns\CanGroupByDataLevel; use App\Concerns\HasTemporaryTable; use App\Contracts\TemporaryTable; use App\Enums\Area; @@ -22,7 +22,7 @@ class Turnout extends Model implements TemporaryTable { use BelongsToElection; - use CanGroupByPlace; + use CanGroupByDataLevel; /** @use HasFactory */ use HasFactory; use HasTemporaryTable; @@ -109,55 +109,12 @@ public function locality(): BelongsTo public function scopeForLevel(Builder $query, DataLevel $level, ?string $country = null, ?int $county = null, ?int $locality = null, bool $aggregate = false): Builder { - $query->select([ - new Alias(new Sum('initial_total'), 'initial_total'), - new Alias(new Sum('total'), 'total'), - ]); - - if ($level->is(DataLevel::TOTAL)) { - $query->groupByTotal(); - } - - if ($level->is(DataLevel::DIASPORA)) { - $query - ->whereNotNull('country_id') - ->when( - $country, - fn (Builder $query) => $query->where('country_id', $country), - ); - - if (! $aggregate) { - $query->groupByCountry(); - } - } - - if ($level->is(DataLevel::NATIONAL)) { - $query->whereNull('country_id'); - - if (filled($locality)) { - $query->where('locality_id', $locality) - ->groupByLocality(); - } elseif (filled($county)) { - $query->where('county_id', $county); - - if ($aggregate) { - $query->groupByCounty(); - } else { - $query->groupByLocality(); - } - } else { - $query->whereNotNull('locality_id') - ->whereNotNull('county_id'); - - if ($aggregate) { - $query->groupByTotal(); - } else { - $query->groupByCounty(); - } - } - } - - return $query; + return $query + ->select([ + new Alias(new Sum('initial_total'), 'initial_total'), + new Alias(new Sum('total'), 'total'), + ]) + ->forDataLevel($level, $country, $county, $locality, $aggregate); } public static function segments(): Collection diff --git a/app/Models/Vote.php b/app/Models/Vote.php index 07f9c9a..ebba431 100644 --- a/app/Models/Vote.php +++ b/app/Models/Vote.php @@ -5,17 +5,23 @@ namespace App\Models; use App\Concerns\BelongsToElection; +use App\Concerns\CanGroupByDataLevel; use App\Concerns\HasTemporaryTable; use App\Contracts\TemporaryTable; +use App\Enums\DataLevel; use App\Enums\Part; use Database\Factories\VoteFactory; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Tpetry\QueryExpressions\Function\Aggregate\Sum; +use Tpetry\QueryExpressions\Language\Alias; class Vote extends Model implements TemporaryTable { use BelongsToElection; + use CanGroupByDataLevel; /** @use HasFactory */ use HasFactory; use HasTemporaryTable; @@ -29,6 +35,7 @@ class Vote extends Model implements TemporaryTable */ protected $fillable = [ 'election_id', + 'country_id', 'county_id', 'locality_id', 'section', @@ -46,7 +53,7 @@ class Vote extends Model implements TemporaryTable protected function casts(): array { return [ - // 'part' => Part::class, + 'part' => Part::class, 'votes' => 'integer', ]; } @@ -56,6 +63,18 @@ public function votable(): MorphTo return $this->morphTo(); } + public function scopeForLevel(Builder $query, DataLevel $level, ?string $country = null, ?int $county = null, ?int $locality = null, bool $aggregate = false): Builder + { + return $query + ->select([ + 'votable_type', + 'votable_id', + new Alias(new Sum('votes'), 'votes'), + ]) + ->groupBy('votable_type', 'votable_id') + ->forDataLevel($level, $country, $county, $locality, $aggregate); + } + public function getTemporaryTableUniqueColumns(): array { return ['election_id', 'county_id', 'country_id', 'section', 'votable_type', 'votable_id']; diff --git a/resources/views/livewire/pages/election-results.blade.php b/resources/views/livewire/pages/election-results.blade.php index 49aa255..63b3336 100644 --- a/resources/views/livewire/pages/election-results.blade.php +++ b/resources/views/livewire/pages/election-results.blade.php @@ -6,7 +6,10 @@ + :county="$county" + :level="$level" + :actionUrl="route('front.elections.results', $election)" + :data="$this->data->toArray()" /> {{ $election }}