diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..ed3b5ef --- /dev/null +++ b/.prettierrc @@ -0,0 +1,13 @@ +{ + "plugins": ["prettier-plugin-blade"], + "overrides": [ + { + "files": ["*.blade.php"], + "options": { + "parser": "blade" + } + } + ], + "singleQuote": true, + "tabWidth": 4 +} diff --git a/app/Console/Commands/ImportRiksdagData.php b/app/Console/Commands/ImportRiksdagData.php new file mode 100644 index 0000000..5740661 --- /dev/null +++ b/app/Console/Commands/ImportRiksdagData.php @@ -0,0 +1,93 @@ +argument('file'); + $url = $this->option('url'); + + if (! $filePath && ! $url) { + $this->error('Either provide a file path or use --url option'); + + return 1; + } + + if ($url) { + // Process ZIP from URL + $this->info("Starting ZIP download and import from: {$url}"); + + if ($this->option('queue')) { + ImportRiksdagDataJob::dispatch($url, true); + $this->info('ZIP import job has been queued. Check queue workers for progress.'); + } else { + try { + $job = new ImportRiksdagDataJob($url, true); + $job->handle(); + $this->info('ZIP import completed successfully!'); + } catch (\Exception $e) { + $this->error('ZIP import failed: '.$e->getMessage()); + + return 1; + } + } + } else { + // Process single file + if (! file_exists($filePath)) { + $this->error("File not found: {$filePath}"); + + return 1; + } + + // Validate JSON format + $jsonContent = file_get_contents($filePath); + $testDecode = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + $this->error('Invalid JSON format: '.json_last_error_msg()); + + return 1; + } + + $this->info("Starting import from: {$filePath}"); + + if ($this->option('queue')) { + ImportRiksdagDataJob::dispatch($filePath, false); + $this->info('Import job has been queued. Check queue workers for progress.'); + } else { + try { + $job = new ImportRiksdagDataJob($filePath, false); + $job->handle(); + $this->info('Import completed successfully!'); + } catch (\Exception $e) { + $this->error('Import failed: '.$e->getMessage()); + + return 1; + } + } + } + + return 0; + } +} diff --git a/app/Jobs/ImportRiksdagDataJob.php b/app/Jobs/ImportRiksdagDataJob.php new file mode 100644 index 0000000..d463c4e --- /dev/null +++ b/app/Jobs/ImportRiksdagDataJob.php @@ -0,0 +1,562 @@ +url = $urlOrPath; + $this->isUrl = $isUrl; + $this->filePath = $urlOrPath; + } + + public function handle() + { + try { + if ($this->isUrl) { + $this->processZipFromUrl(); + } else { + $this->processSingleFile($this->filePath); + } + } catch (\Exception $e) { + Log::error('Error in ImportRiksdagDataJob: '.$e->getMessage()); + throw $e; + } + } + + protected function processZipFromUrl() + { + Log::info('Starting download from: '.$this->url); + + // Download the ZIP file + $response = Http::timeout(300)->get($this->url); + + if (! $response->successful()) { + throw new \Exception('Failed to download ZIP file from: '.$this->url); + } + + // Create temporary directory + $tempDir = storage_path('app/temp/riksdag_import_'.time()); + $zipPath = $tempDir.'/download.zip'; + + if (! is_dir($tempDir)) { + mkdir($tempDir, 0755, true); + } + + // Save ZIP file + file_put_contents($zipPath, $response->body()); + Log::info('ZIP file downloaded to: '.$zipPath); + + // Extract ZIP file + $extractPath = $tempDir.'/extracted'; + $this->extractZipFile($zipPath, $extractPath); + + // Process all JSON files in extracted directory + $this->processJsonFiles($extractPath); + + // Clean up temporary files + $this->cleanupTempDirectory($tempDir); + + Log::info('ZIP processing completed successfully'); + } + + protected function extractZipFile($zipPath, $extractPath) + { + $zip = new ZipArchive; + $result = $zip->open($zipPath); + + if ($result !== true) { + throw new \Exception('Failed to open ZIP file: '.$zipPath.' (Error code: '.$result.')'); + } + + if (! is_dir($extractPath)) { + mkdir($extractPath, 0755, true); + } + + $zip->extractTo($extractPath); + $zip->close(); + + Log::info('ZIP file extracted to: '.$extractPath); + } + + protected function processJsonFiles($directory) + { + $jsonFiles = glob($directory.'/*.json'); + + if (empty($jsonFiles)) { + // Check subdirectories + $subdirs = glob($directory.'/*', GLOB_ONLYDIR); + foreach ($subdirs as $subdir) { + $jsonFiles = array_merge($jsonFiles, glob($subdir.'/*.json')); + } + } + + Log::info('Found '.count($jsonFiles).' JSON files to process'); + + foreach ($jsonFiles as $jsonFile) { + Log::info('Processing file: '.basename($jsonFile)); + $this->processSingleFile($jsonFile); + } + } + + protected function cleanupTempDirectory($tempDir) + { + $this->deleteDirectory($tempDir); + Log::info('Temporary directory cleaned up: '.$tempDir); + } + + protected function deleteDirectory($dir) + { + if (! is_dir($dir)) { + return; + } + + $files = array_diff(scandir($dir), ['.', '..']); + foreach ($files as $file) { + $path = $dir.'/'.$file; + is_dir($path) ? $this->deleteDirectory($path) : unlink($path); + } + rmdir($dir); + } + + protected function processSingleFile($filePath) + { + try { + $jsonContent = file_get_contents($filePath); + $data = json_decode($jsonContent, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new \Exception('Invalid JSON format: '.json_last_error_msg()); + } + + DB::beginTransaction(); + + // Handle new JSON structure with dokumentstatus wrapper + if (isset($data['dokumentstatus'])) { + $this->processDocumentStatus($data['dokumentstatus']); + } + // Legacy support for old structure + elseif (isset($data['dokumentlista'])) { + $this->importDokumentData($data['dokumentlista']); + } elseif (isset($data['personlista'])) { + $this->importPersonData($data['personlista']); + } elseif (isset($data['organlista'])) { + $this->importOrganData($data['organlista']); + } + // Handle single document structure + elseif (isset($data['dokument'])) { + $this->importSingleDokument($data['dokument']); + } + + DB::commit(); + Log::info('Successfully imported data from: '.$filePath); + + } catch (\Exception $e) { + DB::rollBack(); + Log::error("Error importing data from {$filePath}: ".$e->getMessage()); + throw $e; + } + } + + protected function processDocumentStatus($dokumentStatus) + { + // Process the main document + if (isset($dokumentStatus['dokument'])) { + $this->processDokument($dokumentStatus['dokument']); + } + + // Get hangar_id for related data processing + $hangarId = $dokumentStatus['dokument']['hangar_id'] ?? null; + + if (! $hangarId) { + throw new \Exception('No hangar_id found in document data'); + } + + // Process document proposals (forslag) + if (isset($dokumentStatus['dokforslag']['forslag']) && is_array($dokumentStatus['dokforslag']['forslag'])) { + $this->processForslag($hangarId, $dokumentStatus['dokforslag']['forslag']); + } + + // Process document activities (aktivitet) + if (isset($dokumentStatus['dokaktivitet']['aktivitet']) && is_array($dokumentStatus['dokaktivitet']['aktivitet'])) { + $this->processAktiviteter($hangarId, $dokumentStatus['dokaktivitet']['aktivitet']); + } + + // Process document interested parties (intressent) + if (isset($dokumentStatus['dokintressent']['intressent']) && is_array($dokumentStatus['dokintressent']['intressent'])) { + $this->processIntressenter($hangarId, $dokumentStatus['dokintressent']['intressent']); + } + + // Process document information (uppgift) + if (isset($dokumentStatus['dokuppgift']['uppgift']) && is_array($dokumentStatus['dokuppgift']['uppgift'])) { + $this->processUppgifter($hangarId, $dokumentStatus['dokuppgift']['uppgift']); + } + + // Process document attachments (bilaga) + if (isset($dokumentStatus['dokbilaga']['bilaga']) && is_array($dokumentStatus['dokbilaga']['bilaga'])) { + $this->processBilagor($hangarId, $dokumentStatus['dokbilaga']['bilaga']); + } + + // Process document references (referens) + if (isset($dokumentStatus['dokreferens']['referens']) && is_array($dokumentStatus['dokreferens']['referens'])) { + $this->processReferenser($hangarId, $dokumentStatus['dokreferens']['referens']); + } + } + + protected function importDokumentData($dokumentData) + { + $documents = isset($dokumentData['dokument']) ? $dokumentData['dokument'] : []; + + // Handle both single document and array of documents + if (! is_array($documents) || (isset($documents['hangar_id']) && ! is_numeric(array_keys($documents)[0]))) { + $documents = [$documents]; + } + + foreach ($documents as $dokData) { + $this->processDokument($dokData); + } + } + + protected function processDokument($dokData) + { + // Create main document + $dokument = Dokument::updateOrCreate( + ['dok_id' => $dokData['dok_id'] ?? null], + [ + 'hangar_id' => $dokData['hangar_id'] ?? null, + 'rm' => $dokData['rm'] ?? null, + 'beteckning' => $dokData['beteckning'] ?? null, + 'typ' => $dokData['typ'] ?? null, + 'subtyp' => $dokData['subtyp'] ?? null, + 'doktyp' => $dokData['doktyp'] ?? null, + 'typrubrik' => $dokData['typrubrik'] ?? null, + 'dokumentnamn' => $dokData['dokumentnamn'] ?? null, + 'debattnamn' => $dokData['debattnamn'] ?? null, + 'tempbeteckning' => $dokData['tempbeteckning'] ?? null, + 'organ' => $dokData['organ'] ?? null, + 'mottagare' => $dokData['mottagare'] ?? null, + 'nummer' => $dokData['nummer'] ?? null, + 'slutnummer' => $dokData['slutnummer'] ?? null, + 'datum' => isset($dokData['datum']) ? $this->parseDate($dokData['datum']) : null, + 'systemdatum' => isset($dokData['systemdatum']) ? $this->parseDate($dokData['systemdatum']) : null, + 'publicerad' => isset($dokData['publicerad']) ? $this->parseDate($dokData['publicerad']) : null, + 'titel' => $dokData['titel'] ?? null, + 'subtitel' => $dokData['subtitel'] ?? null, + 'status' => $dokData['status'] ?? null, + 'htmlformat' => $dokData['htmlformat'] ?? null, + 'relaterat_id' => $dokData['relaterat_id'] ?? null, + 'source' => $dokData['source'] ?? null, + 'sourceid' => $dokData['sourceid'] ?? null, + 'dokument_url_text' => $dokData['dokument_url_text'] ?? null, + 'dokument_url_html' => $dokData['dokument_url_html'] ?? null, + 'dokumentstatus_url_xml' => $dokData['dokumentstatus_url_xml'] ?? null, + 'utskottsforslag_url_xml' => $dokData['utskottsforslag_url_xml'] ?? null, + ] + ); + + // Process related data + if (isset($dokData['aktivitet']) && is_array($dokData['aktivitet'])) { + $this->processAktiviteter($dokument->hangar_id, $dokData['aktivitet']); + } + + if (isset($dokData['intressent']) && is_array($dokData['intressent'])) { + $this->processIntressenter($dokument->hangar_id, $dokData['intressent']); + } + + if (isset($dokData['bilaga']) && is_array($dokData['bilaga'])) { + $this->processBilagor($dokument->hangar_id, $dokData['bilaga']); + } + + if (isset($dokData['forslag']) && is_array($dokData['forslag'])) { + $this->processForslag($dokument->hangar_id, $dokData['forslag']); + } + + if (isset($dokData['uppgift']) && is_array($dokData['uppgift'])) { + $this->processUppgifter($dokument->hangar_id, $dokData['uppgift']); + } + + if (isset($dokData['referens']) && is_array($dokData['referens'])) { + $this->processReferenser($dokument->hangar_id, $dokData['referens']); + } + } + + protected function processAktiviteter($hangarId, $aktiviteter) + { + foreach ($aktiviteter as $aktivitet) { + DokAktivitet::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'kod' => $aktivitet['kod'] ?? null, + 'datum' => isset($aktivitet['datum']) ? $this->parseDate($aktivitet['datum']) : null, + ], + [ + 'namn' => $aktivitet['namn'] ?? null, + 'status' => $aktivitet['status'] ?? null, + 'ordning' => $aktivitet['ordning'] ?? null, + 'process' => $aktivitet['process'] ?? null, + ] + ); + } + } + + protected function processIntressenter($hangarId, $intressenter) + { + foreach ($intressenter as $intressent) { + dump($intressent); + DokIntressent::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'intressent_id' => $intressent['intressent_id'] ?? null, + ], + [ + 'namn' => $intressent['namn'] ?? null, + 'partibet' => $intressent['partibet'] ?? null, + 'ordning' => $intressent['ordning'] ?? null, + 'roll' => $intressent['roll'] ?? null, + ] + ); + } + } + + protected function processBilagor($hangarId, $bilagor) + { + foreach ($bilagor as $bilaga) { + DokBilaga::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'dok_id' => $bilaga['dok_id'] ?? null, + 'filnamn' => $bilaga['filnamn'] ?? null, + ], + [ + 'titel' => $bilaga['titel'] ?? null, + 'subtitel' => $bilaga['subtitel'] ?? null, + 'filstorlek' => $bilaga['filstorlek'] ?? null, + 'filtyp' => $bilaga['filtyp'] ?? null, + 'fil_url' => $bilaga['fil_url'] ?? null, + ] + ); + } + } + + protected function processForslag($hangarId, $forslag) + { + foreach ($forslag as $forslagItem) { + DokForslag::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'nummer' => $forslagItem['nummer'] ?? null, + ], + [ + 'beteckning' => $forslagItem['beteckning'] ?? null, + 'lydelse' => $forslagItem['lydelse'] ?? null, + 'lydelse2' => $forslagItem['lydelse2'] ?? null, + 'utskottet' => $forslagItem['utskottet'] ?? null, + 'kammaren' => $forslagItem['kammaren'] ?? null, + 'behandlas_i' => $forslagItem['behandlas_i'] ?? null, + 'behandlas_i_punkt' => $forslagItem['behandlas_i_punkt'] ?? null, + 'kammarbeslutstyp' => $forslagItem['kammarbeslutstyp'] ?? null, + 'intressent' => $forslagItem['intressent'] ?? null, + 'avsnitt' => $forslagItem['avsnitt'] ?? null, + 'grundforfattning' => $forslagItem['grundforfattning'] ?? null, + 'andringsforfattning' => $forslagItem['andringsforfattning'] ?? null, + ] + ); + } + } + + protected function processUppgifter($hangarId, $uppgifter) + { + foreach ($uppgifter as $uppgift) { + DokUppgift::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'kod' => $uppgift['kod'] ?? null, + ], + [ + 'namn' => $uppgift['namn'] ?? null, + 'text' => $uppgift['text'] ?? null, + 'dok_id' => $uppgift['dok_id'] ?? null, + 'systemdatum' => isset($uppgift['systemdatum']) ? $this->parseDate($uppgift['systemdatum']) : null, + ] + ); + } + } + + protected function processReferenser($hangarId, $referenser) + { + foreach ($referenser as $referens) { + DokReferens::updateOrCreate( + [ + 'hangar_id' => $hangarId, + 'referenstyp' => $referens['referenstyp'] ?? null, + 'ref_dok_id' => $referens['ref_dok_id'] ?? null, + ], + [ + 'uppgift' => $referens['uppgift'] ?? null, + 'ref_dok_typ' => $referens['ref_dok_typ'] ?? null, + 'ref_dok_rm' => $referens['ref_dok_rm'] ?? null, + 'ref_dok_bet' => $referens['ref_dok_bet'] ?? null, + 'ref_dok_titel' => $referens['ref_dok_titel'] ?? null, + 'ref_dok_subtitel' => $referens['ref_dok_subtitel'] ?? null, + 'ref_dok_subtyp' => $referens['ref_dok_subtyp'] ?? null, + 'ref_dok_dokumentnamn' => $referens['ref_dok_dokumentnamn'] ?? null, + ] + ); + } + } + + protected function importPersonData($personData) + { + $persons = isset($personData['person']) ? $personData['person'] : []; + + // Handle both single person and array of persons + if (! is_array($persons) || (isset($persons['intressent_id']) && ! is_numeric(array_keys($persons)[0]))) { + $persons = [$persons]; + } + + foreach ($persons as $personInfo) { + $this->processPerson($personInfo); + } + } + + protected function processPerson($personInfo) + { + $person = Person::updateOrCreate( + ['intressent_id' => $personInfo['intressent_id'] ?? null], + [ + 'född_år' => $personInfo['fodd_ar'] ?? $personInfo['född_år'] ?? null, + 'kön' => $personInfo['kon'] ?? $personInfo['kön'] ?? null, + 'efternamn' => $personInfo['efternamn'] ?? null, + 'tilltalsnamn' => $personInfo['tilltalsnamn'] ?? null, + 'sorteringsnamn' => $personInfo['sorteringsnamn'] ?? null, + 'iort' => $personInfo['iort'] ?? null, + 'parti' => $personInfo['parti'] ?? null, + 'valkrets' => $personInfo['valkrets'] ?? null, + 'status' => $personInfo['status'] ?? null, + ] + ); + + // Process person assignments + if (isset($personInfo['personuppdrag']['uppdrag']) && is_array($personInfo['personuppdrag']['uppdrag'])) { + $this->processPersonUppdrag($person->intressent_id, $personInfo['personuppdrag']['uppdrag']); + } + + // Process person information + if (isset($personInfo['personuppgift']['uppgift']) && is_array($personInfo['personuppgift']['uppgift'])) { + $this->processPersonUppgift($person->intressent_id, $personInfo['personuppgift']['uppgift']); + } + } + + protected function processPersonUppdrag($intressentId, $uppdragList) + { + foreach ($uppdragList as $uppdrag) { + PersonUppdrag::updateOrCreate( + [ + 'intressent_id' => $intressentId, + 'organ_kod' => $uppdrag['organ_kod'] ?? null, + 'roll_kod' => $uppdrag['roll_kod'] ?? null, + 'ordningsnummer' => $uppdrag['ordningsnummer'] ?? null, + ], + [ + 'status' => $uppdrag['status'] ?? null, + 'typ' => $uppdrag['typ'] ?? null, + 'from' => isset($uppdrag['from']) ? $this->parseDate($uppdrag['from']) : null, + 'tom' => isset($uppdrag['tom']) ? $this->parseDate($uppdrag['tom']) : null, + 'uppgift' => $uppdrag['uppgift'] ?? null, + ] + ); + } + } + + protected function processPersonUppgift($intressentId, $uppgiftList) + { + foreach ($uppgiftList as $uppgift) { + PersonUppgift::updateOrCreate( + [ + 'intressent_id' => $intressentId, + 'uppgift_kod' => $uppgift['kod'] ?? null, + ], + [ + 'uppgift' => $uppgift['uppgift'] ?? null, + 'uppgift_typ' => $uppgift['typ'] ?? null, + ] + ); + } + } + + protected function importOrganData($organData) + { + $organs = isset($organData['organ']) ? $organData['organ'] : []; + + // Handle both single organ and array of organs + if (! is_array($organs) || (isset($organs['id']) && ! is_numeric(array_keys($organs)[0]))) { + $organs = [$organs]; + } + + foreach ($organs as $organInfo) { + Organ::updateOrCreate( + ['id' => $organInfo['id'] ?? null], + [ + 'kod' => $organInfo['kod'] ?? null, + 'namn' => $organInfo['namn'] ?? null, + 'typ' => $organInfo['typ'] ?? null, + 'status' => $organInfo['status'] ?? null, + 'sortering' => $organInfo['sortering'] ?? null, + 'namn_en' => $organInfo['namn_en'] ?? null, + 'domän' => $organInfo['doman'] ?? $organInfo['domän'] ?? null, + 'beskrivning' => $organInfo['beskrivning'] ?? null, + ] + ); + } + } + + protected function importSingleDokument($dokument) + { + $this->processDokument($dokument); + } + + protected function parseDate($dateString) + { + if (empty($dateString)) { + return null; + } + + try { + return \Carbon\Carbon::parse($dateString); + } catch (\Exception $e) { + Log::warning("Could not parse date: {$dateString}"); + + return null; + } + } +} diff --git a/app/Livewire/Motion/Search.php b/app/Livewire/Motion/Search.php new file mode 100644 index 0000000..7896ca9 --- /dev/null +++ b/app/Livewire/Motion/Search.php @@ -0,0 +1,146 @@ +searchMotions(); + } + + public function updatedQuery() + { + $this->resetPage(); + $this->searchMotions(); + } + + public function updatedParty() + { + $this->resetPage(); + $this->searchMotions(); + } + + public function updatedDateInterval() + { + $this->resetPage(); + $this->searchMotions(); + } + + public function searchMotions() + { + if (empty($this->query) && empty($this->party) && empty($this->dateInterval)) { + $this->paginator = null; + + return; + } + + $this->loading = true; + + $cacheKey = 'motions_'.md5($this->query.$this->party.$this->dateInterval.$this->getPage()); + + $result = Cache::remember($cacheKey, 60 * 5, function () { + return app(RiksdagenService::class)->searchMotions( + query: $this->query, + party: $this->party, + dateInterval: $this->dateInterval, + page: $this->getPage() + ); + }); + + $motions = $result->original->dokumentlista->dokument ?? []; + $totalResults = (int) ($result->original->dokumentlista->{'@traffar'} ?? 0); + $perPage = 20; // Default items per page from Riksdag API + $currentPage = $this->getPage(); + + $this->paginator = new LengthAwarePaginator( + items: collect($motions), + total: $totalResults, + perPage: $perPage, + currentPage: $currentPage, + options: [ + 'path' => request()->url(), + 'pageName' => 'page', + ] + ); + + $this->loading = false; + } + + #[Computed()] + public function motions() + { + return $this->paginator ? collect($this->paginator->items()) : collect(); + } + + #[Computed()] + public function totalResults() + { + return $this->paginator ? $this->paginator->total() : 0; + } + + public function clearFilters() + { + $this->query = ''; + $this->party = ''; + $this->dateInterval = ''; + $this->resetPage(); + $this->searchMotions(); + } + + #[Computed()] + public function parties() + { + return collect(PartyEnum::cases())->mapWithKeys(function ($party) { + return [$party->value => $party->label()]; + }); + } + + #[Computed()] + public function dateIntervals() + { + return [ + '2025/26' => '2025/26', + '2024/25' => '2024/25', + '2023/24' => '2023/24', + '2022/23' => '2022/23', + '2021/22' => '2021/22', + '2020/21' => '2020/21', + '2019/20' => '2019/20', + '2018/19' => '2018/19', + ]; + } + + public function render() + { + $this->searchMotions(); + + return view('livewire.motion.search', [ + 'paginatedMotions' => $this->paginator, + ])->title('Sök Motioner - Riksdagen App'); + } +} diff --git a/app/Livewire/Motion/Show.php b/app/Livewire/Motion/Show.php new file mode 100644 index 0000000..933f831 --- /dev/null +++ b/app/Livewire/Motion/Show.php @@ -0,0 +1,71 @@ +motionId = $motionId; + $this->service = app(RiksdagenService::class); + + $result = Cache::remember('motion_'.$motionId, 24 * 60 * 60, function () use ($motionId) { + return $this->service->getMotion($motionId); + }); + + $this->motion = $result->original->dokumentlista->dokument[0] ?? null; + } + + #[Computed()] + public function riksdagenUrl() + { + return 'https://www.riksdagen.se/sv/dokument-lagar/dokument/'.$this->motionId; + } + + #[Computed()] + public function authors() + { + if (! $this->motion || ! isset($this->motion->dokintressent->intressent)) { + return collect(); + } + + $intressenter = $this->motion->dokintressent->intressent; + if (! is_array($intressenter)) { + $intressenter = [$intressenter]; + } + + return collect($intressenter)->where('roll', 'undertecknare'); + } + + #[Computed()] + public function attachments() + { + if (! $this->motion || ! isset($this->motion->filbilaga->fil)) { + return collect(); + } + + $files = $this->motion->filbilaga->fil; + if (! is_array($files)) { + $files = [$files]; + } + + return collect($files); + } + + public function render() + { + return view('livewire.motion.show') + ->title(($this->motion->titel ?? 'Motion').' - Riksdagen App'); + } +} diff --git a/app/Livewire/Person/Search.php b/app/Livewire/Person/Search.php index b2ad70a..41b32c2 100644 --- a/app/Livewire/Person/Search.php +++ b/app/Livewire/Person/Search.php @@ -2,30 +2,35 @@ namespace App\Livewire\Person; -use Livewire\Component; -use App\Services\RiksdagenService; use App\Enums\Parties; +use App\Services\RiksdagenService; +use Livewire\Component; class Search extends Component { public $firstName = ''; + public $lastName = ''; + public $party = ''; + public $results = []; + public $parties = []; public function mount() { - $this->parties = collect(Parties::cases())->sortBy(fn($party) => $party->label())->toArray(); + $this->parties = collect(Parties::cases())->sortBy(fn ($party) => $party->label())->toArray(); } public function search() { $service = app(RiksdagenService::class); + $this->results = $service->searchPerson( firstName: $this->firstName, lastName: $this->lastName, - party: $this->party + party: $this->party, )->original; } diff --git a/app/Livewire/Person/Show.php b/app/Livewire/Person/Show.php index cb9213b..6452b66 100644 --- a/app/Livewire/Person/Show.php +++ b/app/Livewire/Person/Show.php @@ -2,11 +2,12 @@ namespace App\Livewire\Person; -use Livewire\Component; use App\Services\RiksdagenService; -use Livewire\Attributes\Computed; 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 @@ -17,9 +18,15 @@ class Show extends Component public $votes = []; + public $motions = []; + public $votesByYear = []; - public $selectedYear; + public $motionsByYear = []; + + public $votesSelectedYear; + + public $motionsSelectedYear; public $selectedUppdragTab = 'current'; @@ -27,47 +34,87 @@ class Show extends Component public $previousUppdrag = []; + private RiksdagenService $service; + public function mount($personId) { $this->personId = $personId; - $service = app(RiksdagenService::class); - $result = $service->searchPerson(mp_id: $personId); + $this->service = app(RiksdagenService::class); + $result = Cache::remember('person_'.$personId, 24 * 60 * 60, function () use ($personId) { + return $this->service->searchPerson(mp_id: $personId); + }); $this->person = $result->original->personlista->person ?? null; $this->getPersonVotes(); + $this->getPersonMotions(); $this->groupUppdrag(); } #[Computed()] public function riksdagenUrl() { - return "https://www.riksdagen.se/sv/ledamoter-partier/ledamot/" . $this->person->tilltalsnamn . "-" . $this->person->efternamn . "_" . $this->personId; + return 'https://www.riksdagen.se/sv/ledamoter-partier/ledamot/'.$this->person->tilltalsnamn.'-'.$this->person->efternamn.'_'.$this->personId; } public function getPersonVotes() { - $service = app(RiksdagenService::class); - $result = $service->searchVotes(mp_id: $this->personId); + $result = Cache::remember('person_votes_'.$this->personId, 24 * 60 * 60, function () { + return $this->service->searchVotes(mp_id: $this->personId); + }); + $votesList = $result->original->voteringlista->votering ?? []; - // Group votes by year (from rm field like 2025/26) + // Group votes by "rm" field (e.g., 2024/25) foreach ($votesList as $vote) { - $year = explode('/', $vote->rm)[0]; // Extract year from rm like "2025/26" - if (!isset($this->votesByYear[$year])) { - $this->votesByYear[$year] = []; + $rm = $vote->rm; + if (! isset($this->votesByYear[$rm])) { + $this->votesByYear[$rm] = []; } - $this->votesByYear[$year][] = $vote; + $this->votesByYear[$rm][] = $vote; } // Set default selected year to the most recent - if (!empty($this->votesByYear)) { - $this->selectedYear = max(array_keys($this->votesByYear)); + if (! empty($this->votesByYear)) { + // Sort keys as strings (e.g., 2024/25, 2023/24, ...) + $years = array_keys($this->votesByYear); + rsort($years, SORT_STRING); + $this->votesSelectedYear = $years[0]; } } - public function selectYear($year) + public function getPersonMotions() { - $this->selectedYear = $year; + $result = Cache::remember('person_motions_'.$this->personId, 24 * 60 * 60, function () { + return $this->service->getPersonMotions(mp_id: $this->personId); + }); + $motionsList = $result->original->dokumentlista->dokument ?? []; + + // Group votes by "rm" field (e.g., 2024/25) + foreach ($motionsList as $motion) { + $rm = $motion->rm; + if (! isset($this->motionsByYear[$rm])) { + $this->motionsByYear[$rm] = []; + } + $this->motionsByYear[$rm][] = $motion; + } + + // Set default selected year to the most recent + if (! empty($this->motionsByYear)) { + // Sort keys as strings (e.g., 2024/25, 2023/24, ...) + $years = array_keys($this->motionsByYear); + rsort($years, SORT_STRING); + $this->motionsSelectedYear = $years[0]; + } + } + + public function selectVotesYear($year) + { + $this->votesSelectedYear = $year; + } + + public function selectMotionsYear($year) + { + $this->motionsSelectedYear = $year; } public function selectUppdragTab($tab) @@ -77,7 +124,7 @@ class Show extends Component public function groupUppdrag() { - if (!$this->person || !isset($this->person->personuppdrag->uppdrag)) { + if (! $this->person || ! isset($this->person->personuppdrag->uppdrag)) { return; } @@ -98,11 +145,11 @@ class Show extends Component } // Sort by date (most recent first) - usort($this->currentUppdrag, function($a, $b) { + usort($this->currentUppdrag, function ($a, $b) { return \Carbon\Carbon::parse($b->from)->timestamp - \Carbon\Carbon::parse($a->from)->timestamp; }); - usort($this->previousUppdrag, function($a, $b) { + usort($this->previousUppdrag, function ($a, $b) { return \Carbon\Carbon::parse($b->tom ?: $b->from)->timestamp - \Carbon\Carbon::parse($a->tom ?: $a->from)->timestamp; }); } @@ -110,16 +157,16 @@ class Show extends Component #[Computed()] public function votingStatistics() { - if (!$this->selectedYear || !isset($this->votesByYear[$this->selectedYear])) { + if (! $this->votesSelectedYear || ! isset($this->votesByYear[$this->votesSelectedYear])) { return []; } - $votes = $this->votesByYear[$this->selectedYear]; + $votes = $this->votesByYear[$this->votesSelectedYear]; $statistics = []; foreach ($votes as $vote) { $voteType = $vote->rost; - if (!isset($statistics[$voteType])) { + if (! isset($statistics[$voteType])) { $statistics[$voteType] = 0; } $statistics[$voteType]++; @@ -133,8 +180,8 @@ class Show extends Component { $statistics = $this->votingStatistics; - $pieChart = (new PieChartModel()) - ->setTitle('Voteringsstatistik för ' . $this->selectedYear) + $pieChart = (new PieChartModel) + ->setTitle('Voteringsstatistik för '.$this->votesSelectedYear) ->setAnimated(true) ->withDataLabels(); diff --git a/app/Models/Anforande.php b/app/Models/Anforande.php new file mode 100644 index 0000000..6e108a3 --- /dev/null +++ b/app/Models/Anforande.php @@ -0,0 +1,20 @@ +belongsTo(Dokument::class, 'dok_hangar_id', 'hangar_id'); + } + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Models/Debatt.php b/app/Models/Debatt.php new file mode 100644 index 0000000..e5d780a --- /dev/null +++ b/app/Models/Debatt.php @@ -0,0 +1,20 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Models/DokAktivitet.php b/app/Models/DokAktivitet.php new file mode 100644 index 0000000..446b215 --- /dev/null +++ b/app/Models/DokAktivitet.php @@ -0,0 +1,27 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokBilaga.php b/app/Models/DokBilaga.php new file mode 100644 index 0000000..999d395 --- /dev/null +++ b/app/Models/DokBilaga.php @@ -0,0 +1,28 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokForslag.php b/app/Models/DokForslag.php new file mode 100644 index 0000000..dec4d30 --- /dev/null +++ b/app/Models/DokForslag.php @@ -0,0 +1,34 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokIntressent.php b/app/Models/DokIntressent.php new file mode 100644 index 0000000..aabcb61 --- /dev/null +++ b/app/Models/DokIntressent.php @@ -0,0 +1,31 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Models/DokMotforslag.php b/app/Models/DokMotforslag.php new file mode 100644 index 0000000..28f0b57 --- /dev/null +++ b/app/Models/DokMotforslag.php @@ -0,0 +1,15 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokReferens.php b/app/Models/DokReferens.php new file mode 100644 index 0000000..a8ee5e6 --- /dev/null +++ b/app/Models/DokReferens.php @@ -0,0 +1,31 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokUppgift.php b/app/Models/DokUppgift.php new file mode 100644 index 0000000..f130b30 --- /dev/null +++ b/app/Models/DokUppgift.php @@ -0,0 +1,30 @@ + 'datetime', + ]; + + public function dokument() + { + return $this->belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/DokUtskottsforslag.php b/app/Models/DokUtskottsforslag.php new file mode 100644 index 0000000..acbd75f --- /dev/null +++ b/app/Models/DokUtskottsforslag.php @@ -0,0 +1,15 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/Dokument.php b/app/Models/Dokument.php new file mode 100644 index 0000000..2119f0b --- /dev/null +++ b/app/Models/Dokument.php @@ -0,0 +1,102 @@ +hasMany(DokUtskottsforslag::class, 'hangar_id', 'hangar_id'); + } + + public function motforslag() + { + return $this->hasMany(DokMotforslag::class, 'hangar_id', 'hangar_id'); + } + + public function aktiviteter() + { + return $this->hasMany(DokAktivitet::class, 'hangar_id', 'hangar_id'); + } + + public function intressenter() + { + return $this->hasMany(DokIntressent::class, 'hangar_id', 'hangar_id'); + } + + public function forslag() + { + return $this->hasMany(DokForslag::class, 'hangar_id', 'hangar_id'); + } + + public function uppgifter() + { + return $this->hasMany(DokUppgift::class, 'hangar_id', 'hangar_id'); + } + + public function bilagor() + { + return $this->hasMany(DokBilaga::class, 'hangar_id', 'hangar_id'); + } + + public function referenser() + { + return $this->hasMany(DokReferens::class, 'hangar_id', 'hangar_id'); + } + + public function debatter() + { + return $this->hasMany(Debatt::class, 'hangar_id', 'hangar_id'); + } + + public function voteringar() + { + return $this->hasMany(Votering::class, 'hangar_id', 'hangar_id'); + } + + public function anforanden() + { + return $this->hasMany(Anforande::class, 'dok_hangar_id', 'hangar_id'); + } +} diff --git a/app/Models/Organ.php b/app/Models/Organ.php new file mode 100644 index 0000000..fe4032a --- /dev/null +++ b/app/Models/Organ.php @@ -0,0 +1,31 @@ +hasMany(PersonUppdrag::class, 'organ_kod', 'kod'); + } +} diff --git a/app/Models/Person.php b/app/Models/Person.php new file mode 100644 index 0000000..85d8b75 --- /dev/null +++ b/app/Models/Person.php @@ -0,0 +1,61 @@ +hasMany(PersonUppdrag::class, 'intressent_id', 'intressent_id'); + } + + public function uppgifter() + { + return $this->hasMany(PersonUppgift::class, 'intressent_id', 'intressent_id'); + } + + public function voteringar() + { + return $this->hasMany(Votering::class, 'intressent_id', 'intressent_id'); + } + + public function anforanden() + { + return $this->hasMany(Anforande::class, 'intressent_id', 'intressent_id'); + } + + public function debatter() + { + return $this->hasMany(Debatt::class, 'intressent_id', 'intressent_id'); + } + + public function dokIntressenter() + { + return $this->hasMany(DokIntressent::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Models/PersonUppdrag.php b/app/Models/PersonUppdrag.php new file mode 100644 index 0000000..95807a3 --- /dev/null +++ b/app/Models/PersonUppdrag.php @@ -0,0 +1,44 @@ + 'datetime', + 'tom' => 'datetime', + ]; + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } + + public function organ() + { + return $this->belongsTo(Organ::class, 'organ_kod', 'kod'); + } + + public function roll() + { + return $this->belongsTo(Roll::class, 'roll_kod', 'kod'); + } +} diff --git a/app/Models/PersonUppgift.php b/app/Models/PersonUppgift.php new file mode 100644 index 0000000..5862b90 --- /dev/null +++ b/app/Models/PersonUppgift.php @@ -0,0 +1,24 @@ +belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Models/Planering.php b/app/Models/Planering.php new file mode 100644 index 0000000..c5f1070 --- /dev/null +++ b/app/Models/Planering.php @@ -0,0 +1,31 @@ + 'datetime', + 'publicerad' => 'datetime', + 'uppdaterad' => 'datetime', + 'wn_expires' => 'datetime', + 'timestamp' => 'datetime', + 'slutdatum' => 'datetime', + 'webbtvlive' => 'boolean', + ]; + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } + + public function mottagare() + { + return $this->belongsTo(Person::class, 'mottagare_id', 'intressent_id'); + } +} diff --git a/app/Models/Riksmote.php b/app/Models/Riksmote.php new file mode 100644 index 0000000..571599c --- /dev/null +++ b/app/Models/Riksmote.php @@ -0,0 +1,16 @@ + 'datetime', + 'slut' => 'datetime', + ]; +} diff --git a/app/Models/Roll.php b/app/Models/Roll.php new file mode 100644 index 0000000..7f76feb --- /dev/null +++ b/app/Models/Roll.php @@ -0,0 +1,16 @@ +hasMany(PersonUppdrag::class, 'roll_kod', 'kod'); + } +} diff --git a/app/Models/Votering.php b/app/Models/Votering.php new file mode 100644 index 0000000..49636f6 --- /dev/null +++ b/app/Models/Votering.php @@ -0,0 +1,20 @@ +belongsTo(Dokument::class, 'hangar_id', 'hangar_id'); + } + + public function person() + { + return $this->belongsTo(Person::class, 'intressent_id', 'intressent_id'); + } +} diff --git a/app/Services/RiksdagenService.php b/app/Services/RiksdagenService.php index 466a7bc..22aab93 100644 --- a/app/Services/RiksdagenService.php +++ b/app/Services/RiksdagenService.php @@ -22,7 +22,7 @@ class RiksdagenService string $lastName = '', string $party = '' ): JsonResponse { - $response = $this->http->get('personlista/', [ + $response = $this->http->get('personlista/', [ 'query' => [ 'iid' => $mp_id, 'parti' => $party, @@ -30,8 +30,8 @@ class RiksdagenService 'enamn' => $lastName, 'utformat' => 'json', 'sort' => 'sorteringsnamn', - 'sortorder' => 'asc' - ] + 'sortorder' => 'asc', + ], ])->getBody()->getContents(); $data = json_decode($response); @@ -44,14 +44,92 @@ class RiksdagenService string $party = '', string $date = '', ): JsonResponse { - $response = $this->http->get('voteringlista/', [ + $response = $this->http->get('voteringlista/', [ 'query' => [ 'iid' => $mp_id, 'rm' => $date, 'parti' => $party, 'utformat' => 'json', - 'sz' => '500', - ] + 'sz' => '3000', + ], + ])->getBody()->getContents(); + + $data = json_decode($response); + + return response()->json($data); + } + + public function getPersonMotions( + string $mp_id, + string $date = '', + ): JsonResponse { + $response = $this->http->get('dokumentlista/', [ + 'query' => [ + 'iid' => $mp_id, + 'rm' => $date, + 'doktyp' => 'mot', + 'utformat' => 'json', + ], + ])->getBody()->getContents(); + + $data = json_decode($response); + + return response()->json($data); + } + + public function getPartyMotions( + string $party, + string $date = '', + + ): JsonResponse { + $response = $this->http->get('dokumentlista/', [ + 'query' => [ + 'parti' => $party, + 'rm' => $date, + 'doktyp' => 'mot', + 'utformat' => 'json', + ], + ])->getBody()->getContents(); + + $data = json_decode($response); + + return response()->json($data); + } + + public function searchMotions( + string $query = '', + string $party = '', + string $dateInterval = '', + int $page = 1 + ): JsonResponse { + $response = $this->http->get('dokumentlista/', [ + 'query' => array_filter([ + 'sok' => $query, + 'parti' => $party, + 'rm' => $dateInterval, + 'doktyp' => 'mot', + 'utformat' => 'json', + 'p' => $page, + ]), + ])->getBody()->getContents(); + + $data = json_decode($response); + + return response()->json($data); + } + + public function getMotion( + string $bet, + string $tempbet, + string $nr, + ): JsonResponse { + $response = $this->http->get('dokumentlista/', [ + 'query' => [ + 'bet' => $bet, + 'tempbet' => $tempbet, + 'nr' => $nr, + 'utformat' => 'json', + ], ])->getBody()->getContents(); $data = json_decode($response); diff --git a/database/migrations/2025_12_27_000000_create_riksdagen_tables.php b/database/migrations/2025_12_27_000000_create_riksdagen_tables.php new file mode 100644 index 0000000..a8fe563 --- /dev/null +++ b/database/migrations/2025_12_27_000000_create_riksdagen_tables.php @@ -0,0 +1,340 @@ +integer('hangar_id'); + $table->string('dok_id'); + $table->string('rm'); + $table->string('beteckning')->nullable(); + $table->string('typ')->nullable(); + $table->string('subtyp')->nullable(); + $table->string('doktyp')->nullable(); + $table->string('typrubrik')->nullable(); + $table->string('dokumentnamn')->nullable(); + $table->string('debattnamn')->nullable(); + $table->string('tempbeteckning')->nullable(); + $table->string('organ')->nullable(); + $table->string('mottagare')->nullable(); + $table->integer('nummer')->nullable(); + $table->integer('slutnummer')->nullable(); + $table->dateTime('datum')->nullable(); + $table->dateTime('systemdatum')->nullable(); + $table->dateTime('publicerad')->nullable(); + $table->string('titel')->nullable(); + $table->string('subtitel')->nullable(); + $table->string('status')->nullable(); + $table->string('htmlformat')->nullable(); + $table->string('relaterat_id')->nullable(); + $table->string('source')->nullable(); + $table->string('sourceid')->nullable(); + $table->string('dokument_url_text')->nullable(); + $table->string('dokument_url_html')->nullable(); + $table->string('dokumentstatus_url_xml')->nullable(); + $table->string('utskottsforslag_url_xml')->nullable(); + $table->text('html')->nullable(); + }); + // dokutskottsforslag + Schema::create('dokutskottsforslag', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->integer('punkt'); + $table->string('beteckning', 50)->nullable(); + $table->string('rubrik')->nullable(); + $table->string('forslag')->nullable(); + $table->text('forslag_del2')->nullable(); + $table->string('beslutstyp')->nullable(); + $table->string('beslut', 100)->nullable(); + $table->integer('motforslag_nummer')->nullable(); + $table->string('motforslag_partier')->nullable(); + $table->string('votering_id')->nullable(); + $table->string('votering_sammanfattning_html')->nullable(); + $table->string('votering_ledamot_url_xml')->nullable(); + $table->string('rm')->nullable(); + $table->string('bet')->nullable(); + $table->string('vinnare')->nullable(); + }); + // dokmotforslag + Schema::create('dokmotforslag', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->integer('nummer'); + $table->string('rubrik')->nullable(); + $table->text('forslag')->nullable(); + $table->string('partier')->nullable(); + $table->string('typ')->nullable(); + $table->integer('utskottsforslag_punkt')->nullable(); + $table->string('id', 50)->nullable(); + }); + // dokaktivitet + Schema::create('dokaktivitet', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('kod')->nullable(); + $table->string('namn')->nullable(); + $table->dateTime('datum')->nullable(); + $table->string('status')->nullable(); + $table->string('ordning')->nullable(); + $table->string('process')->nullable(); + }); + // dokintressent + Schema::create('dokintressent', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('intressent_id'); + $table->string('namn')->nullable(); + $table->string('partibet')->nullable(); + $table->integer('ordning')->nullable(); + $table->string('roll')->nullable(); + }); + // dokforslag + Schema::create('dokforslag', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->integer('nummer')->nullable(); + $table->string('beteckning')->nullable(); + $table->string('lydelse', 500)->nullable(); + $table->string('lydelse2')->nullable(); + $table->string('utskottet')->nullable(); + $table->string('kammaren')->nullable(); + $table->string('behandlas_i')->nullable(); + $table->string('behandlas_i_punkt')->nullable(); + $table->string('kammarbeslutstyp')->nullable(); + $table->string('intressent')->nullable(); + $table->string('avsnitt')->nullable(); + $table->string('grundforfattning')->nullable(); + $table->string('andringsforfattning')->nullable(); + }); + // dokuppgift + Schema::create('dokuppgift', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('kod')->nullable(); + $table->string('namn')->nullable(); + $table->text('text')->nullable(); + $table->string('dok_id')->nullable(); + $table->dateTime('systemdatum')->nullable(); + }); + // dokbilaga + Schema::create('dokbilaga', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('dok_id')->nullable(); + $table->string('titel')->nullable(); + $table->string('subtitel')->nullable(); + $table->string('filnamn')->nullable(); + $table->integer('filstorlek')->nullable(); + $table->string('filtyp')->nullable(); + $table->string('fil_url')->nullable(); + }); + // dokreferens + Schema::create('dokreferens', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('referenstyp')->nullable(); + $table->string('uppgift')->nullable(); + $table->string('ref_dok_id')->nullable(); + $table->string('ref_dok_typ')->nullable(); + $table->string('ref_dok_rm')->nullable(); + $table->string('ref_dok_bet')->nullable(); + $table->string('ref_dok_titel')->nullable(); + $table->string('ref_dok_subtitel')->nullable(); + $table->string('ref_dok_subtyp')->nullable(); + $table->string('ref_dok_dokumentnamn')->nullable(); + }); + // debatt + Schema::create('debatt', function (Blueprint $table) { + $table->integer('hangar_id'); + $table->string('video_id')->nullable(); + $table->string('video_url')->nullable(); + $table->integer('startpos')->nullable(); + $table->string('tumnagel')->nullable(); + $table->string('tumnagel_stor')->nullable(); + $table->string('anf_video_id')->nullable(); + $table->integer('anf_hangar_id')->nullable(); + $table->integer('anf_sekunder')->nullable(); + $table->string('anf_klockslag')->nullable(); + $table->dateTime('datumtid')->nullable(); + $table->dateTime('anf_datum')->nullable(); + $table->string('anf_typ')->nullable(); + $table->string('anf_text')->nullable(); + $table->string('anf_beteckning')->nullable(); + $table->string('anf_nummer')->nullable(); + $table->string('talare')->nullable(); + $table->string('intressent_id')->nullable(); + $table->string('parti')->nullable(); + $table->string('anf_rm')->nullable(); + }); + // votering + Schema::create('votering', function (Blueprint $table) { + $table->string('rm'); + $table->string('beteckning')->nullable(); + $table->integer('hangar_id'); + $table->string('votering_id')->nullable(); + $table->integer('punkt')->nullable(); + $table->string('namn')->nullable(); + $table->string('intressent_id')->nullable(); + $table->string('parti')->nullable(); + $table->string('valkrets')->nullable(); + $table->integer('valkretsnummer')->nullable(); + $table->string('iort')->nullable(); + $table->string('rost')->nullable(); + $table->string('avser')->nullable(); + $table->string('votering')->nullable(); + $table->integer('banknummer')->nullable(); + $table->string('fornamn')->nullable(); + $table->string('efternamn')->nullable(); + $table->string('kon')->nullable(); + $table->integer('fodd')->nullable(); + $table->dateTime('datum')->nullable(); + }); + // anforande + Schema::create('anforande', function (Blueprint $table) { + $table->integer('dok_hangar_id'); + $table->string('dok_id', 50); + $table->string('dok_titel')->nullable(); + $table->string('dok_rm', 20)->nullable(); + $table->integer('dok_nummer')->nullable(); + $table->dateTime('dok_datum')->nullable(); + $table->string('avsnittsrubrik')->nullable(); + $table->string('underrubrik')->nullable(); + $table->string('kammaraktivitet', 250)->nullable(); + $table->string('anforande_id', 50); + $table->integer('anforande_nummer')->nullable(); + $table->string('talare', 250)->nullable(); + $table->string('parti', 50)->nullable(); + $table->text('anforandetext')->nullable(); + $table->string('intressent_id', 50)->nullable(); + $table->string('rel_dok_id', 50)->nullable(); + $table->char('replik', 1)->nullable(); + $table->dateTime('systemdatum')->nullable(); + }); + // person + Schema::create('person', function (Blueprint $table) { + $table->string('intressent_id', 20)->primary(); + $table->smallInteger('född_år')->nullable(); + $table->string('kön', 6)->nullable(); + $table->string('efternamn', 50)->nullable(); + $table->string('tilltalsnamn', 50)->nullable(); + $table->string('sorteringsnamn', 80)->nullable(); + $table->string('iort', 40)->nullable(); + $table->string('parti', 40)->nullable(); + $table->string('valkrets', 50)->nullable(); + $table->string('status', 100)->nullable(); + }); + // personuppdrag + Schema::create('personuppdrag', function (Blueprint $table) { + $table->string('organ_kod', 20); + $table->string('roll_kod', 40); + $table->integer('ordningsnummer'); + $table->string('status', 20)->nullable(); + $table->string('typ', 20)->nullable(); + $table->dateTime('from')->nullable(); + $table->dateTime('tom')->nullable(); + $table->string('uppgift', 500)->nullable(); + $table->string('intressent_id', 50); + }); + // personuppgift + Schema::create('personuppgift', function (Blueprint $table) { + $table->string('uppgift_kod', 50); + $table->text('uppgift')->nullable(); + $table->string('uppgift_typ', 50)->nullable(); + $table->string('intressent_id', 50); + }); + // planering + Schema::create('planering', function (Blueprint $table) { + $table->integer('nyckel'); + $table->string('id', 50); + $table->string('rm', 12)->nullable(); + $table->string('typ', 40)->nullable(); + $table->char('dokserie_id', 2)->nullable(); + $table->string('subtyp', 40)->nullable(); + $table->string('bet', 40)->nullable(); + $table->string('tempbet', 40)->nullable(); + $table->string('intressent', 80)->nullable(); + $table->integer('nummer')->nullable(); + $table->integer('slutnummer')->nullable(); + $table->dateTime('datum')->nullable(); + $table->dateTime('publicerad')->nullable(); + $table->string('status', 40)->nullable(); + $table->string('titel', 300)->nullable(); + $table->string('subtitel', 255)->nullable(); + $table->text('html')->nullable(); + $table->text('toc')->nullable(); + $table->string('refcss', 66)->nullable(); + $table->string('url', 100)->nullable(); + $table->dateTime('uppdaterad')->nullable(); + $table->integer('storlek')->nullable(); + $table->string('source', 20)->nullable(); + $table->dateTime('wn_expires')->nullable(); + $table->string('wn_cachekey', 50)->nullable(); + $table->string('wn_status', 20)->nullable(); + $table->string('wn_checksum', 40)->nullable(); + $table->integer('wn_nid')->nullable(); + $table->string('wn_RawUrl', 255)->nullable(); + $table->string('wn_SourceID', 80)->nullable(); + $table->dateTime('timestamp')->nullable(); + $table->string('rel_id', 50)->nullable(); + $table->string('klockslag', 10)->nullable(); + $table->string('grupp', 20)->nullable(); + $table->string('format', 20)->nullable(); + $table->string('intressent_id', 13)->nullable(); + $table->string('mottagare_id', 13)->nullable(); + $table->string('mottagare', 80)->nullable(); + $table->integer('hangar_id')->nullable(); + $table->string('plats', 150)->nullable(); + $table->dateTime('slutdatum')->nullable(); + $table->tinyInteger('webbtvlive')->nullable(); + }); + // organ + Schema::create('organ', function (Blueprint $table) { + $table->integer('id')->primary(); + $table->string('kod', 50)->nullable(); + $table->string('namn', 100)->nullable(); + $table->string('typ', 50)->nullable(); + $table->string('status', 12)->nullable(); + $table->integer('sortering')->nullable(); + $table->string('namn_en', 100)->nullable(); + $table->string('domän', 50)->nullable(); + $table->string('beskrivning', 1000)->nullable(); + }); + // roll + Schema::create('roll', function (Blueprint $table) { + $table->integer('pk')->primary(); + $table->string('kod', 50)->nullable(); + $table->string('namn', 100)->nullable(); + $table->integer('sort')->nullable(); + }); + // riksmote + Schema::create('riksmote', function (Blueprint $table) { + $table->integer('pk')->primary(); + $table->string('riksmote', 20)->nullable(); + $table->string('id', 3)->nullable(); + $table->dateTime('start')->nullable(); + $table->dateTime('slut')->nullable(); + $table->string('mandatperiod', 20)->nullable(); + }); + } + + public function down() + { + Schema::dropIfExists('dokument'); + Schema::dropIfExists('dokutskottsforslag'); + Schema::dropIfExists('dokmotforslag'); + Schema::dropIfExists('dokaktivitet'); + Schema::dropIfExists('dokintressent'); + Schema::dropIfExists('dokforslag'); + Schema::dropIfExists('dokuppgift'); + Schema::dropIfExists('dokbilaga'); + Schema::dropIfExists('dokreferens'); + Schema::dropIfExists('debatt'); + Schema::dropIfExists('votering'); + Schema::dropIfExists('anforande'); + Schema::dropIfExists('person'); + Schema::dropIfExists('personuppdrag'); + Schema::dropIfExists('personuppgift'); + Schema::dropIfExists('planering'); + Schema::dropIfExists('organ'); + Schema::dropIfExists('roll'); + Schema::dropIfExists('riksmote'); + } +}; diff --git a/package-lock.json b/package-lock.json index 8dbff16..31e5c37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,9 @@ "tailwindcss": "^4.0.7", "vite": "^7.0.4" }, + "devDependencies": { + "prettier-plugin-blade": "^2.1.21" + }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", @@ -2056,6 +2059,36 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-blade": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-2.1.21.tgz", + "integrity": "sha512-+BPBPvla/Ppr0MVrqMAO+FTwxpXUYo8zhQPIGC7psNuMbB24y84cGrJ4Uc02GHTQN0q8txeG4Y4MxyJWgOujyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "prettier": ">=3" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", diff --git a/package.json b/package.json index 688bea8..9f855a4 100644 --- a/package.json +++ b/package.json @@ -19,5 +19,8 @@ "@rollup/rollup-linux-x64-gnu": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", "lightningcss-linux-x64-gnu": "^1.29.1" + }, + "devDependencies": { + "prettier-plugin-blade": "^2.1.21" } } diff --git a/resources/views/livewire/home-page.blade.php b/resources/views/livewire/home-page.blade.php index f12b767..2a646ed 100644 --- a/resources/views/livewire/home-page.blade.php +++ b/resources/views/livewire/home-page.blade.php @@ -6,30 +6,117 @@ Riksdagen App
- Utforska information om riksdagsledamöter, deras röster och uppdrag i Sveriges riksdag + Utforska information om riksdagsledamöter, deras röster och + uppdrag i Sveriges riksdag
-+ Sök bland motioner som lämnats till riksdagen av + ledamöterna +
+- Sök och läs propositioner och andra dokument från riksdagen + Sök och läs propositioner och andra dokument från + riksdagen
Se röststatistik och analyser för partier och ledamöter
@@ -72,33 +192,31 @@- Se kommande voteringar och riksdagsmöten -
-Utforska partier och deras ståndpunkter i olika frågor
@@ -109,14 +227,30 @@Sök i alla riksdagens dokument och voteringar
@@ -125,7 +259,6 @@+ Sök bland alla motioner som lämnats till riksdagen +
++ {{ $motion->undertitel }} +
+ @endif ++ {{ $motion->rm }}:{{ $motion->beteckning }} +
++ {{ \Carbon\Carbon::parse($motion->datum)->format('Y-m-d') }} +
++ {{ $motion->organ ?: '-' }} +
++ {!! strip_tags($motion->summary, '') !!} +
+ @endif ++ Försök ändra dina sökkriterier. +
+ ++ Använd sökfälten ovan för att hitta motioner. +
++ {{ $motion->undertitel }} +
+ @endif ++ Motionen med ID {{ $motionId }} kunde inte hittas. +
+ + Tillbaka till sökning + +