This commit is contained in:
23
app/Livewire/Party/Index.php
Normal file
23
app/Livewire/Party/Index.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Party;
|
||||||
|
|
||||||
|
use App\Enums\Parties as PartyEnum;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
$parties = collect(PartyEnum::cases())->map(function ($party) {
|
||||||
|
return [
|
||||||
|
'code' => $party->value,
|
||||||
|
'name' => $party->label(),
|
||||||
|
'logo' => $party->logo(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('livewire.party.index', compact('parties'))
|
||||||
|
->title('Partier - Riksdagen App');
|
||||||
|
}
|
||||||
|
}
|
||||||
229
app/Livewire/Party/Show.php
Normal file
229
app/Livewire/Party/Show.php
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Party;
|
||||||
|
|
||||||
|
use App\Enums\Parties as PartyEnum;
|
||||||
|
use App\Services\RiksdagenService;
|
||||||
|
use Asantibanez\LivewireCharts\Models\ColumnChartModel;
|
||||||
|
use Asantibanez\LivewireCharts\Models\PieChartModel;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Livewire\Attributes\Computed;
|
||||||
|
use Livewire\Attributes\Lazy;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
#[Lazy()]
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public $partyCode;
|
||||||
|
|
||||||
|
public $party;
|
||||||
|
|
||||||
|
public $motions = [];
|
||||||
|
|
||||||
|
public $members = [];
|
||||||
|
|
||||||
|
public $votes = [];
|
||||||
|
|
||||||
|
public $selectedYear = '';
|
||||||
|
|
||||||
|
private RiksdagenService $service;
|
||||||
|
|
||||||
|
public function mount($partyCode)
|
||||||
|
{
|
||||||
|
$this->partyCode = strtoupper($partyCode);
|
||||||
|
$this->service = app(RiksdagenService::class);
|
||||||
|
|
||||||
|
// Find party info
|
||||||
|
$this->party = collect(PartyEnum::cases())
|
||||||
|
->firstWhere('value', $this->partyCode);
|
||||||
|
|
||||||
|
if (! $this->party) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadPartyData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadPartyData()
|
||||||
|
{
|
||||||
|
// Load party motions
|
||||||
|
$this->loadMotions();
|
||||||
|
|
||||||
|
// Load party members
|
||||||
|
$this->loadMembers();
|
||||||
|
|
||||||
|
// Load voting data for members
|
||||||
|
$this->loadVotingData();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadMotions()
|
||||||
|
{
|
||||||
|
$result = Cache::remember('party_motions_'.$this->partyCode, 60 * 30, function () {
|
||||||
|
return $this->service->getPartyMotions($this->partyCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->motions = $result->original->dokumentlista->dokument ?? [];
|
||||||
|
|
||||||
|
// Set default selected year if motions exist
|
||||||
|
if (! empty($this->motions)) {
|
||||||
|
$years = collect($this->motions)->pluck('rm')->unique()->sort()->reverse();
|
||||||
|
$this->selectedYear = $years->first();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadMembers()
|
||||||
|
{
|
||||||
|
$result = Cache::remember('party_members_'.$this->partyCode, 24 * 60 * 60, function () {
|
||||||
|
return $this->service->searchPerson(party: $this->partyCode);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->members = $result->original->personlista->person ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadVotingData()
|
||||||
|
{
|
||||||
|
// For now, load voting data for a sample of members due to API limitations
|
||||||
|
$sampleMembers = collect($this->members)->take(5);
|
||||||
|
|
||||||
|
foreach ($sampleMembers as $member) {
|
||||||
|
$memberVotes = Cache::remember('member_votes_'.$member->intressent_id, 60 * 60, function () use ($member) {
|
||||||
|
return $this->service->searchVotes(mp_id: $member->intressent_id);
|
||||||
|
});
|
||||||
|
|
||||||
|
$votes = $memberVotes->original->voteringlista->votering ?? [];
|
||||||
|
$this->votes = array_merge($this->votes, is_array($votes) ? $votes : [$votes]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function selectYear($year)
|
||||||
|
{
|
||||||
|
$this->selectedYear = $year;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function motionsByYear()
|
||||||
|
{
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($this->motions as $motion) {
|
||||||
|
$rm = $motion->rm;
|
||||||
|
if (! isset($grouped[$rm])) {
|
||||||
|
$grouped[$rm] = [];
|
||||||
|
}
|
||||||
|
$grouped[$rm][] = $motion;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function motionStatistics()
|
||||||
|
{
|
||||||
|
$stats = [
|
||||||
|
'total' => count($this->motions),
|
||||||
|
'by_year' => [],
|
||||||
|
'by_type' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($this->motions as $motion) {
|
||||||
|
// By year
|
||||||
|
$year = $motion->rm;
|
||||||
|
$stats['by_year'][$year] = ($stats['by_year'][$year] ?? 0) + 1;
|
||||||
|
|
||||||
|
// By type
|
||||||
|
$type = $motion->subtyp ?? 'Okänd';
|
||||||
|
$stats['by_type'][$type] = ($stats['by_type'][$type] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function votingStatistics()
|
||||||
|
{
|
||||||
|
$stats = [];
|
||||||
|
foreach ($this->votes as $vote) {
|
||||||
|
$voteType = $vote->rost ?? 'Okänd';
|
||||||
|
$stats[$voteType] = ($stats[$voteType] ?? 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function motionsColumnChart()
|
||||||
|
{
|
||||||
|
$motionStats = $this->motionStatistics;
|
||||||
|
|
||||||
|
$chart = (new ColumnChartModel)
|
||||||
|
->setTitle('Motioner per år')
|
||||||
|
->setAnimated(true)
|
||||||
|
->withDataLabels();
|
||||||
|
|
||||||
|
$colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6'];
|
||||||
|
$colorIndex = 0;
|
||||||
|
|
||||||
|
foreach ($motionStats['by_year'] as $year => $count) {
|
||||||
|
$chart->addColumn($year, $count, $colors[$colorIndex % count($colors)]);
|
||||||
|
$colorIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function motionTypePieChart()
|
||||||
|
{
|
||||||
|
$motionStats = $this->motionStatistics;
|
||||||
|
|
||||||
|
$chart = (new PieChartModel)
|
||||||
|
->setTitle('Motioner per typ')
|
||||||
|
->setAnimated(true)
|
||||||
|
->withDataLabels();
|
||||||
|
|
||||||
|
$colors = ['#3B82F6', '#10B981', '#F59E0B', '#EF4444'];
|
||||||
|
$colorIndex = 0;
|
||||||
|
|
||||||
|
foreach ($motionStats['by_type'] as $type => $count) {
|
||||||
|
$chart->addSlice($type, $count, $colors[$colorIndex % count($colors)]);
|
||||||
|
$colorIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chart;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Computed()]
|
||||||
|
public function votingPieChart()
|
||||||
|
{
|
||||||
|
$votingStats = $this->votingStatistics;
|
||||||
|
|
||||||
|
$chart = (new PieChartModel)
|
||||||
|
->setTitle('Röstfördelning (urval av ledamöter)')
|
||||||
|
->setAnimated(true)
|
||||||
|
->withDataLabels();
|
||||||
|
|
||||||
|
$colors = [
|
||||||
|
'Ja' => '#10b981',
|
||||||
|
'Nej' => '#ef4444',
|
||||||
|
'Frånvarande' => '#6b7280',
|
||||||
|
'Avstår' => '#f59e0b',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($votingStats as $voteType => $count) {
|
||||||
|
$color = $colors[$voteType] ?? '#8b5cf6';
|
||||||
|
$chart->addSlice($voteType, $count, $color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $chart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function placeholder()
|
||||||
|
{
|
||||||
|
return view('livewire.person.show-skeleton');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.party.show')
|
||||||
|
->title($this->party->label().' - Riksdagen App');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -123,6 +123,55 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<!-- Party Index Card -->
|
||||||
|
<a wire:navigate href="{{ route('party.index') }}" class="group">
|
||||||
|
<div
|
||||||
|
class="bg-white rounded-lg h-full shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border border-gray-200 overflow-hidden"
|
||||||
|
>
|
||||||
|
<div class="p-6">
|
||||||
|
<div
|
||||||
|
class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4 group-hover:bg-purple-200 transition-colors"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-6 h-6 text-purple-600"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">
|
||||||
|
Partier
|
||||||
|
</h3>
|
||||||
|
<p class="text-gray-600 mb-4">
|
||||||
|
Utforska partiernas motioner, ledamöter och röststatistik
|
||||||
|
</p>
|
||||||
|
<div class="flex items-center text-purple-600 group-hover:text-purple-700">
|
||||||
|
<span class="text-sm font-medium">Visa partier</span>
|
||||||
|
<svg
|
||||||
|
class="w-4 h-4 ml-2 transform group-hover:translate-x-1 transition-transform"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
<!-- Placeholder Card 1 -->
|
<!-- Placeholder Card 1 -->
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden opacity-60"
|
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden opacity-60"
|
||||||
@@ -192,40 +241,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Placeholder Card 4 -->
|
|
||||||
<div
|
|
||||||
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden opacity-60"
|
|
||||||
>
|
|
||||||
<div class="p-6">
|
|
||||||
<div
|
|
||||||
class="w-12 h-12 bg-gray-100 rounded-lg flex items-center justify-center mb-4"
|
|
||||||
>
|
|
||||||
<svg
|
|
||||||
class="w-6 h-6 text-gray-400"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="2"
|
|
||||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-xl font-semibold text-gray-500 mb-2">
|
|
||||||
Partier
|
|
||||||
</h3>
|
|
||||||
<p class="text-gray-400 mb-4">
|
|
||||||
Utforska partier och deras ståndpunkter i olika frågor
|
|
||||||
</p>
|
|
||||||
<div class="flex items-center text-gray-400">
|
|
||||||
<span class="text-sm font-medium">Kommer snart</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Placeholder Card 5 -->
|
<!-- Placeholder Card 5 -->
|
||||||
<div
|
<div
|
||||||
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden opacity-60"
|
class="bg-white rounded-lg shadow-md border border-gray-200 overflow-hidden opacity-60"
|
||||||
|
|||||||
68
resources/views/livewire/party/index.blade.php
Normal file
68
resources/views/livewire/party/index.blade.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||||
|
<div class="container mx-auto py-12 px-4">
|
||||||
|
<!-- Breadcrumb Navigation -->
|
||||||
|
<nav class="mb-8" aria-label="Breadcrumb">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 px-4 py-3">
|
||||||
|
<ol class="flex items-center space-x-2 text-sm text-gray-600">
|
||||||
|
<li>
|
||||||
|
<a wire:navigate href="{{ route('home') }}" class="text-blue-600 hover:text-blue-700 flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Hem
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
<li class="font-medium text-gray-900">
|
||||||
|
Partier
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-900 mb-4">Sveriges Riksdag - Partier</h1>
|
||||||
|
<p class="text-lg text-gray-600">Utforska information om de politiska partierna i Sveriges riksdag</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Party Grid -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 max-w-7xl mx-auto">
|
||||||
|
@foreach ($parties as $party)
|
||||||
|
<a wire:navigate href="{{ route('party.show', $party['code']) }}" class="group">
|
||||||
|
<div class="bg-white rounded-lg shadow-md hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex items-center justify-center mb-4">
|
||||||
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center group-hover:bg-gray-200 transition-colors">
|
||||||
|
@if ($party['logo'])
|
||||||
|
<img src="{{ $party['logo'] }}" alt="{{ $party['name'] }}" class="w-12 h-12 object-contain">
|
||||||
|
@else
|
||||||
|
<span class="text-2xl font-bold text-gray-600">{{ $party['code'] }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 text-center mb-2">{{ $party['name'] }}</h3>
|
||||||
|
<p class="text-sm text-gray-600 text-center mb-4">{{ $party['code'] }}</p>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center text-blue-600 group-hover:text-blue-700">
|
||||||
|
<span class="text-sm font-medium">Visa detaljer</span>
|
||||||
|
<svg class="w-4 h-4 ml-2 transform group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
256
resources/views/livewire/party/show.blade.php
Normal file
256
resources/views/livewire/party/show.blade.php
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
<div class="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||||||
|
<div class="container mx-auto py-12 px-4">
|
||||||
|
<!-- Breadcrumb Navigation -->
|
||||||
|
<nav class="mb-8" aria-label="Breadcrumb">
|
||||||
|
<div class="bg-white rounded-lg shadow-sm border border-gray-200 px-4 py-3">
|
||||||
|
<ol class="flex items-center space-x-2 text-sm text-gray-600">
|
||||||
|
<li>
|
||||||
|
<a wire:navigate href="{{ route('home') }}" class="text-blue-600 hover:text-blue-700 flex items-center">
|
||||||
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Hem
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a wire:navigate href="{{ route('party.index') }}" class="text-blue-600 hover:text-blue-700">
|
||||||
|
Partier
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<svg class="w-4 h-4 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</li>
|
||||||
|
<li class="font-medium text-gray-900">
|
||||||
|
{{ $party->label() }}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Party Header -->
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-8 mb-8">
|
||||||
|
<div class="flex flex-col md:flex-row items-center gap-6">
|
||||||
|
<div class="w-24 h-24 bg-gray-100 rounded-full flex items-center justify-center">
|
||||||
|
@if ($party->logo())
|
||||||
|
<img src="{{ $party->logo() }}" alt="{{ $party->label() }}" class="w-20 h-20 object-contain">
|
||||||
|
@else
|
||||||
|
<span class="text-3xl font-bold text-gray-600">{{ $party->value }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="text-center md:text-left">
|
||||||
|
<h1 class="text-4xl font-bold text-gray-900 mb-2">{{ $party->label() }}</h1>
|
||||||
|
<p class="text-xl text-gray-600 mb-4">{{ $party->value }}</p>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div class="bg-blue-50 rounded-lg p-3">
|
||||||
|
<div class="text-sm font-medium text-blue-600">Totalt motioner</div>
|
||||||
|
<div class="text-2xl font-bold text-blue-900">{{ $this->motionStatistics['total'] }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-green-50 rounded-lg p-3">
|
||||||
|
<div class="text-sm font-medium text-green-600">Ledamöter</div>
|
||||||
|
<div class="text-2xl font-bold text-green-900">{{ count($members) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-purple-50 rounded-lg p-3">
|
||||||
|
<div class="text-sm font-medium text-purple-600">Röster (urval)</div>
|
||||||
|
<div class="text-2xl font-bold text-purple-900">{{ count($votes) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Statistics Section -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||||
|
<!-- Motion Statistics -->
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6">
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
|
||||||
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Motioner per år</h2>
|
||||||
|
</div>
|
||||||
|
@if (!empty($this->motionStatistics['by_year']))
|
||||||
|
<div style="height: 20rem;">
|
||||||
|
<livewire:livewire-column-chart :column-chart-model="$this->motionsColumnChart" />
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-gray-500 text-center py-8">Ingen motionsstatistik tillgänglig</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Motion Types -->
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6">
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-3">
|
||||||
|
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Motionstyper</h2>
|
||||||
|
</div>
|
||||||
|
@if (!empty($this->motionStatistics['by_type']))
|
||||||
|
<div style="height: 20rem;">
|
||||||
|
<livewire:livewire-pie-chart :pie-chart-model="$this->motionTypePieChart" />
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-gray-500 text-center py-8">Ingen motionsstatistik tillgänglig</p>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Voting Statistics -->
|
||||||
|
@if (!empty($this->votingStatistics))
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6 mb-8">
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center mr-3">
|
||||||
|
<svg class="w-5 h-5 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Röststatistik (urval av ledamöter)</h2>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col lg:flex-row gap-6">
|
||||||
|
<div style="height: 20rem;" class="lg:w-1/2">
|
||||||
|
<livewire:livewire-pie-chart :pie-chart-model="$this->votingPieChart" />
|
||||||
|
</div>
|
||||||
|
<div class="lg:w-1/2 my-auto">
|
||||||
|
<div class="space-y-2">
|
||||||
|
@php($totalVotes = array_sum($this->votingStatistics))
|
||||||
|
@foreach ($this->votingStatistics as $voteType => $count)
|
||||||
|
<div class="flex justify-between items-center p-3 bg-gray-50 rounded">
|
||||||
|
<span class="text-black font-medium">{{ $voteType }}</span>
|
||||||
|
<span class="text-black text-lg font-bold">
|
||||||
|
{{ $totalVotes > 0 ? round(($count / $totalVotes) * 100, 2) : 0 }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Motions Section -->
|
||||||
|
@if (!empty($this->motionsByYear))
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6 mb-8">
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-3">
|
||||||
|
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Partiets motioner</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Year tabs -->
|
||||||
|
<div class="border-b border-gray-200 mb-4">
|
||||||
|
<nav class="-mb-px flex space-x-8">
|
||||||
|
@foreach (array_keys($this->motionsByYear) as $year)
|
||||||
|
<button wire:click="selectYear('{{ $year }}')"
|
||||||
|
class="cursor-pointer py-2 px-1 border-b-2 font-medium text-sm {{ $selectedYear == $year ? 'border-green-500 text-green-600' : 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300' }}">
|
||||||
|
{{ $year }} ({{ count($this->motionsByYear[$year]) }})
|
||||||
|
</button>
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if ($selectedYear && isset($this->motionsByYear[$selectedYear]))
|
||||||
|
<div class="space-y-4">
|
||||||
|
@foreach (collect($this->motionsByYear[$selectedYear])->take(10) as $motion)
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 hover:bg-gray-100 transition-colors">
|
||||||
|
<div class="flex flex-col lg:flex-row lg:items-start gap-4">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||||
|
<a wire:navigate href="{{ route('motion.show', $motion->dok_id) }}"
|
||||||
|
class="hover:text-blue-600 transition-colors">
|
||||||
|
{{ $motion->titel }}
|
||||||
|
</a>
|
||||||
|
</h3>
|
||||||
|
@if (!empty($motion->undertitel))
|
||||||
|
<p class="text-sm text-gray-600 mb-2">{{ $motion->undertitel }}</p>
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-wrap gap-4 text-sm text-gray-500">
|
||||||
|
<span>{{ $motion->rm }}:{{ $motion->beteckning }}</span>
|
||||||
|
<span>{{ \Carbon\Carbon::parse($motion->datum)->format('Y-m-d') }}</span>
|
||||||
|
<span class="px-2 py-1 bg-gray-200 rounded">{{ $motion->subtyp }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a wire:navigate href="{{ route('motion.show', $motion->dok_id) }}"
|
||||||
|
class="px-3 py-1 text-sm font-medium text-green-600 bg-green-100 rounded-md hover:bg-green-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
||||||
|
Visa motion
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@if (count($this->motionsByYear[$selectedYear]) > 10)
|
||||||
|
<div class="text-center pt-4">
|
||||||
|
<a wire:navigate href="{{ route('motion.search', ['party' => $party->value, 'dateInterval' => $selectedYear]) }}"
|
||||||
|
class="px-4 py-2 text-sm font-medium text-blue-600 bg-blue-100 rounded-md hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
||||||
|
Visa alla {{ count($this->motionsByYear[$selectedYear]) }} motioner för {{ $selectedYear }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Members Section -->
|
||||||
|
@if (!empty($members))
|
||||||
|
<div class="bg-white rounded-lg shadow-md border border-gray-200 p-6">
|
||||||
|
<div class="flex items-center mb-6">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center mr-3">
|
||||||
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-bold text-gray-900">Partiets ledamöter ({{ count($members) }})</h2>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
@foreach ($members as $member)
|
||||||
|
<a wire:navigate href="{{ route('person.show', $member->intressent_id) }}">
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 hover:bg-gray-100 transition-colors border border-gray-200">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="font-semibold text-gray-900">{{ $member->tilltalsnamn }} {{ $member->efternamn }}</div>
|
||||||
|
<div class="text-sm text-gray-600">{{ $member->valkrets }}</div>
|
||||||
|
@if (!empty($member->status))
|
||||||
|
<div class="text-xs text-gray-500">{{ $member->status }}</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
use App\Livewire\HomePage;
|
use App\Livewire\HomePage;
|
||||||
use App\Livewire\Motion\Search as MotionSearch;
|
use App\Livewire\Motion\Search as MotionSearch;
|
||||||
use App\Livewire\Motion\Show as MotionShow;
|
use App\Livewire\Motion\Show as MotionShow;
|
||||||
|
use App\Livewire\Party\Index as PartyIndex;
|
||||||
|
use App\Livewire\Party\Show as PartyShow;
|
||||||
use App\Livewire\Person\Search as PersonSearch;
|
use App\Livewire\Person\Search as PersonSearch;
|
||||||
use App\Livewire\Person\Show as PersonShow;
|
use App\Livewire\Person\Show as PersonShow;
|
||||||
use App\Livewire\Settings\Appearance;
|
use App\Livewire\Settings\Appearance;
|
||||||
@@ -20,6 +22,9 @@ Route::get('/ledamot/{personId}', PersonShow::class)->name('person.show');
|
|||||||
Route::get('/motion', MotionSearch::class)->name('motion.search');
|
Route::get('/motion', MotionSearch::class)->name('motion.search');
|
||||||
Route::get('/motion/{motionId}', MotionShow::class)->name('motion.show');
|
Route::get('/motion/{motionId}', MotionShow::class)->name('motion.show');
|
||||||
|
|
||||||
|
Route::get('/parti', PartyIndex::class)->name('party.index');
|
||||||
|
Route::get('/parti/{partyCode}', PartyShow::class)->name('party.show');
|
||||||
|
|
||||||
Route::view('dashboard', 'dashboard')
|
Route::view('dashboard', 'dashboard')
|
||||||
->middleware(['auth', 'verified'])
|
->middleware(['auth', 'verified'])
|
||||||
->name('dashboard');
|
->name('dashboard');
|
||||||
|
|||||||
Reference in New Issue
Block a user