mirror of
https://github.com/HolgerHatGarKeineNode/einundzwanzig-nostr.git
synced 2025-12-13 05:26:47 +00:00
real-time-admin-dashboard added
This commit is contained in:
@@ -9,6 +9,9 @@
|
|||||||
"@nostr-dev-kit/ndk": "^2.10.0",
|
"@nostr-dev-kit/ndk": "^2.10.0",
|
||||||
"@tailwindcss/forms": "^0.5.8",
|
"@tailwindcss/forms": "^0.5.8",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
|
"chart.js": "^4.4.4",
|
||||||
|
"chartjs-adapter-date-fns": "^3.0.0",
|
||||||
|
"date-fns": "^4.1.0",
|
||||||
"flatpickr": "^4.6.13",
|
"flatpickr": "^4.6.13",
|
||||||
"laravel-echo": "^1.16.1",
|
"laravel-echo": "^1.16.1",
|
||||||
"laravel-vite-plugin": "^1.0",
|
"laravel-vite-plugin": "^1.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {Alpine, Livewire} from '../../vendor/livewire/livewire/dist/livewire.esm
|
|||||||
|
|
||||||
import nostrApp from "./nostrApp.js";
|
import nostrApp from "./nostrApp.js";
|
||||||
import nostrLogin from "./nostrLogin.js";
|
import nostrLogin from "./nostrLogin.js";
|
||||||
|
import electionAdminCharts from "./electionAdminCharts.js";
|
||||||
|
|
||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
@@ -18,5 +19,6 @@ Alpine.store('nostr', {
|
|||||||
|
|
||||||
Alpine.data('nostrApp', nostrApp);
|
Alpine.data('nostrApp', nostrApp);
|
||||||
Alpine.data('nostrLogin', nostrLogin);
|
Alpine.data('nostrLogin', nostrLogin);
|
||||||
|
Alpine.data('electionAdminCharts', electionAdminCharts);
|
||||||
|
|
||||||
Livewire.start();
|
Livewire.start();
|
||||||
|
|||||||
169
resources/js/electionAdminCharts.js
Normal file
169
resources/js/electionAdminCharts.js
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import {Chart} from "chart.js/auto";
|
||||||
|
|
||||||
|
export default (livewireComponent) => ({
|
||||||
|
plebs: livewireComponent.entangle('plebs', true),
|
||||||
|
electionConfig: livewireComponent.entangle('electionConfig', true),
|
||||||
|
votes: livewireComponent.entangle('votes', true),
|
||||||
|
charts: {}, // Store chart instances
|
||||||
|
|
||||||
|
hexToRGB(h) {
|
||||||
|
let r = 0;
|
||||||
|
let g = 0;
|
||||||
|
let b = 0;
|
||||||
|
if (h.length === 4) {
|
||||||
|
r = `0x${h[1]}${h[1]}`;
|
||||||
|
g = `0x${h[2]}${h[2]}`;
|
||||||
|
b = `0x${h[3]}${h[3]}`;
|
||||||
|
} else if (h.length === 7) {
|
||||||
|
r = `0x${h[1]}${h[2]}`;
|
||||||
|
g = `0x${h[3]}${h[4]}`;
|
||||||
|
b = `0x${h[5]}${h[6]}`;
|
||||||
|
}
|
||||||
|
return `${+r},${+g},${+b}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.createChart('chart_presidency', 'presidency');
|
||||||
|
this.createChart('chart_vice_president', 'vice_president');
|
||||||
|
this.createChart('chart_finances', 'finances');
|
||||||
|
this.createChart('chart_secretary', 'secretary');
|
||||||
|
this.createChart('chart_press_officer', 'press_officer');
|
||||||
|
this.createChart('chart_it_manager', 'it_manager');
|
||||||
|
|
||||||
|
this.$watch('votes', () => {
|
||||||
|
this.createChart('chart_presidency', 'presidency');
|
||||||
|
this.createChart('chart_vice_president', 'vice_president');
|
||||||
|
this.createChart('chart_finances', 'finances');
|
||||||
|
this.createChart('chart_secretary', 'secretary');
|
||||||
|
this.createChart('chart_press_officer', 'press_officer');
|
||||||
|
this.createChart('chart_it_manager', 'it_manager');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createChart(refName, type) {
|
||||||
|
const ctx = this.$refs[refName];
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
// Destroy old chart instance if it exists
|
||||||
|
if (this.charts[refName]) {
|
||||||
|
this.charts[refName].destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
const darkMode = localStorage.getItem('dark-mode') === 'true';
|
||||||
|
|
||||||
|
const textColor = {
|
||||||
|
light: '#9CA3AF',
|
||||||
|
dark: '#6B7280'
|
||||||
|
};
|
||||||
|
|
||||||
|
const gridColor = {
|
||||||
|
light: '#F3F4F6',
|
||||||
|
dark: `rgba(${this.hexToRGB('#374151')}, 0.6)`
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltipBodyColor = {
|
||||||
|
light: '#6B7280',
|
||||||
|
dark: '#9CA3AF'
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltipBgColor = {
|
||||||
|
light: '#ffffff',
|
||||||
|
dark: '#374151'
|
||||||
|
};
|
||||||
|
|
||||||
|
const tooltipBorderColor = {
|
||||||
|
light: '#E5E7EB',
|
||||||
|
dark: '#4B5563'
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = this.electionConfig.find(config => config.type === type);
|
||||||
|
const labels = config ? config.candidates.map(candidate => candidate.name) : [];
|
||||||
|
const labelsPubkeys = config ? config.candidates.map(candidate => candidate.pubkey) : [];
|
||||||
|
const data = this.votes.find(vote => vote.type === type);
|
||||||
|
const findVoteCountInDataByLabelsPubkey = data ? labelsPubkeys.map(pubkey => data.votes[pubkey]?.count ?? 0) : labelsPubkeys.map(() => 0);
|
||||||
|
console.log('findVoteCountInDataByLabelsPubkey', findVoteCountInDataByLabelsPubkey);
|
||||||
|
|
||||||
|
// Create new chart instance and store it
|
||||||
|
this.charts[refName] = new Chart(ctx, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: 'Stimmen',
|
||||||
|
data: findVoteCountInDataByLabelsPubkey,
|
||||||
|
backgroundColor: '#67BFFF',
|
||||||
|
hoverBackgroundColor: '#56B1F3',
|
||||||
|
barPercentage: 0.7,
|
||||||
|
categoryPercentage: 0.7,
|
||||||
|
borderRadius: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
layout: {
|
||||||
|
padding: {
|
||||||
|
top: 12,
|
||||||
|
bottom: 16,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
border: {display: false},
|
||||||
|
ticks: {
|
||||||
|
maxTicksLimit: 5,
|
||||||
|
color: darkMode ? textColor.dark : textColor.light,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
color: darkMode ? gridColor.dark : gridColor.light,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
border: {display: false},
|
||||||
|
grid: {display: false},
|
||||||
|
ticks: {
|
||||||
|
color: darkMode ? textColor.dark : textColor.light,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
legend: {display: false},
|
||||||
|
htmlLegend: {containerID: 'dashboard-card-01-legend'},
|
||||||
|
tooltip: {
|
||||||
|
bodyColor: darkMode ? tooltipBodyColor.dark : tooltipBodyColor.light,
|
||||||
|
backgroundColor: darkMode ? tooltipBgColor.dark : tooltipBgColor.light,
|
||||||
|
borderColor: darkMode ? tooltipBorderColor.dark : tooltipBorderColor.light,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
intersect: false,
|
||||||
|
mode: 'nearest',
|
||||||
|
},
|
||||||
|
animation: {duration: 200},
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('darkMode', (e) => {
|
||||||
|
const {mode} = e.detail;
|
||||||
|
if (mode === 'on') {
|
||||||
|
this.charts[refName].options.scales.x.ticks.color = textColor.dark;
|
||||||
|
this.charts[refName].options.scales.y.ticks.color = textColor.dark;
|
||||||
|
this.charts[refName].options.scales.y.grid.color = gridColor.dark;
|
||||||
|
this.charts[refName].options.plugins.tooltip.bodyColor = tooltipBodyColor.dark;
|
||||||
|
this.charts[refName].options.plugins.tooltip.backgroundColor = tooltipBgColor.dark;
|
||||||
|
this.charts[refName].options.plugins.tooltip.borderColor = tooltipBorderColor.dark;
|
||||||
|
} else {
|
||||||
|
this.charts[refName].options.scales.x.ticks.color = textColor.light;
|
||||||
|
this.charts[refName].options.scales.y.ticks.color = textColor.light;
|
||||||
|
this.charts[refName].options.scales.y.grid.color = gridColor.light;
|
||||||
|
this.charts[refName].options.plugins.tooltip.bodyColor = tooltipBodyColor.light;
|
||||||
|
this.charts[refName].options.plugins.tooltip.backgroundColor = tooltipBgColor.light;
|
||||||
|
this.charts[refName].options.plugins.tooltip.borderColor = tooltipBorderColor.light;
|
||||||
|
}
|
||||||
|
this.charts[refName].update('none');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
use swentel\nostr\Filter\Filter;
|
||||||
|
use swentel\nostr\Key\Key;
|
||||||
|
use swentel\nostr\Message\EventMessage;
|
||||||
|
use swentel\nostr\Message\RequestMessage;
|
||||||
|
use swentel\nostr\Relay\Relay;
|
||||||
|
use swentel\nostr\Relay\RelaySet;
|
||||||
|
use swentel\nostr\Request\Request;
|
||||||
|
use swentel\nostr\Subscription\Subscription;
|
||||||
|
use swentel\nostr\Event\Event as NostrEvent;
|
||||||
|
use swentel\nostr\Sign\Sign;
|
||||||
|
|
||||||
|
use function Livewire\Volt\computed;
|
||||||
|
use function Livewire\Volt\mount;
|
||||||
|
use function Livewire\Volt\state;
|
||||||
|
use function Livewire\Volt\with;
|
||||||
|
use function Livewire\Volt\updated;
|
||||||
|
use function Laravel\Folio\{middleware};
|
||||||
|
use function Laravel\Folio\name;
|
||||||
|
use function Livewire\Volt\{on};
|
||||||
|
|
||||||
|
name('association.election.admin');
|
||||||
|
|
||||||
|
state(['currentPubkey' => null]);
|
||||||
|
state(['votes' => null]);
|
||||||
|
state(['events' => null]);
|
||||||
|
state(['election' => fn() => $election]);
|
||||||
|
state(['ehrenMitgliederCount' => 0]);
|
||||||
|
state(['aktiveMitgliederCount' => 0]);
|
||||||
|
state(['signThisEvent' => '']);
|
||||||
|
state([
|
||||||
|
'plebs' => fn()
|
||||||
|
=> \App\Models\EinundzwanzigPleb::query()
|
||||||
|
->with([
|
||||||
|
'profile',
|
||||||
|
])
|
||||||
|
->whereIn('association_status', [3, 4])
|
||||||
|
->orderBy('association_status', 'desc')
|
||||||
|
->get()
|
||||||
|
->toArray(),
|
||||||
|
]);
|
||||||
|
state([
|
||||||
|
'electionConfig' => function () {
|
||||||
|
return collect(json_decode($this->election->candidates, true, 512, JSON_THROW_ON_ERROR))
|
||||||
|
->map(function ($c) {
|
||||||
|
$candidates = \App\Models\Profile::query()
|
||||||
|
->whereIn('pubkey', $c['c'])
|
||||||
|
->get()
|
||||||
|
->map(fn($p)
|
||||||
|
=> [
|
||||||
|
'pubkey' => $p->pubkey,
|
||||||
|
'name' => $p->name,
|
||||||
|
'picture' => $p->picture,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'type' => $c['type'],
|
||||||
|
'c' => $c['c'],
|
||||||
|
'candidates' => $candidates,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
mount(function () {
|
||||||
|
$plebsCollection = collect($this->plebs);
|
||||||
|
$this->ehrenMitgliederCount = $plebsCollection->where(
|
||||||
|
'association_status',
|
||||||
|
\App\Enums\AssociationStatus::HONORARY(),
|
||||||
|
)->count();
|
||||||
|
$this->aktiveMitgliederCount = $plebsCollection->where(
|
||||||
|
'association_status',
|
||||||
|
\App\Enums\AssociationStatus::ACTIVE(),
|
||||||
|
)->count();
|
||||||
|
$this->loadEvents();
|
||||||
|
$this->loadVotes();
|
||||||
|
});
|
||||||
|
|
||||||
|
on([
|
||||||
|
'nostrLoggedIn' => function ($pubkey) {
|
||||||
|
$this->currentPubkey = $pubkey;
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
on([
|
||||||
|
'echo:votes,.newVote' => function () {
|
||||||
|
$this->loadEvents();
|
||||||
|
$this->loadVotes();
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
$loadVotes = function () {
|
||||||
|
$votes = collect($this->events)
|
||||||
|
->map(function ($event) {
|
||||||
|
$votedFor = \App\Models\Profile::query()
|
||||||
|
->where('pubkey', str($event['content'])->before(',')->toString())
|
||||||
|
->first()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'created_at' => $event['created_at'],
|
||||||
|
'pubkey' => $event['pubkey'],
|
||||||
|
'forpubkey' => $votedFor['pubkey'],
|
||||||
|
'type' => str($event['content'])->after(',')->toString(),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->sortByDesc('created_at')
|
||||||
|
->unique(fn($event) => $event['pubkey'] . $event['type'])
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
$this->votes = collect($votes)
|
||||||
|
->groupBy('type')
|
||||||
|
->map(fn($votes)
|
||||||
|
=> [
|
||||||
|
'type' => $votes[0]['type'],
|
||||||
|
'votes' => collect($votes)
|
||||||
|
->groupBy('forpubkey')
|
||||||
|
->map(fn($group) => ['count' => $group->count()])
|
||||||
|
->toArray(),
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->toArray();
|
||||||
|
};
|
||||||
|
|
||||||
|
$loadEvents = function () {
|
||||||
|
$subscription = new Subscription();
|
||||||
|
$subscriptionId = $subscription->setId();
|
||||||
|
|
||||||
|
$filter1 = new Filter();
|
||||||
|
$filter1->setKinds([2121]); // You can add multiple kind numbers
|
||||||
|
$filters = [$filter1]; // You can add multiple filters.
|
||||||
|
|
||||||
|
$requestMessage = new RequestMessage($subscriptionId, $filters);
|
||||||
|
|
||||||
|
$relays = [
|
||||||
|
new Relay('ws://relay:7000'),
|
||||||
|
];
|
||||||
|
$relaySet = new RelaySet();
|
||||||
|
$relaySet->setRelays($relays);
|
||||||
|
|
||||||
|
$request = new Request($relaySet, $requestMessage);
|
||||||
|
$response = $request->send();
|
||||||
|
|
||||||
|
$this->events = collect($response['ws://relay:7000'])
|
||||||
|
->map(fn($event)
|
||||||
|
=> [
|
||||||
|
'id' => $event->event->id,
|
||||||
|
'kind' => $event->event->kind,
|
||||||
|
'content' => $event->event->content,
|
||||||
|
'pubkey' => $event->event->pubkey,
|
||||||
|
'tags' => $event->event->tags,
|
||||||
|
'created_at' => $event->event->created_at,
|
||||||
|
])
|
||||||
|
->toArray();
|
||||||
|
};
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<x-layouts.app title="{{ __('Wahl Manager') }}">
|
||||||
|
@volt
|
||||||
|
@php
|
||||||
|
$positions = [
|
||||||
|
'presidency' => ['icon' => 'fa-crown', 'title' => 'Präsidium'],
|
||||||
|
'vice_president' => ['icon' => 'fa-user-group-crown', 'title' => 'Vizepräsidium'],
|
||||||
|
'finances' => ['icon' => 'fa-bitcoin-sign', 'title' => 'Finanzen'],
|
||||||
|
'secretary' => ['icon' => 'fa-stapler', 'title' => 'Sekretär (Akurat)'],
|
||||||
|
'press_officer' => ['icon' => 'fa-newspaper', 'title' => 'Pressewart'],
|
||||||
|
'it_manager' => ['icon' => 'fa-server', 'title' => 'Technikwart'],
|
||||||
|
];
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="px-4 sm:px-6 lg:px-8 py-8 w-full max-w-9xl mx-auto" x-data="electionAdminCharts(@this)">
|
||||||
|
|
||||||
|
<!-- Dashboard actions -->
|
||||||
|
<div class="sm:flex sm:justify-between sm:items-center mb-8">
|
||||||
|
|
||||||
|
<!-- Left: Title -->
|
||||||
|
<div class="mb-4 sm:mb-0">
|
||||||
|
<h1 class="text-2xl md:text-3xl text-gray-800 dark:text-gray-100 font-bold">
|
||||||
|
Wahl des Vorstands {{ $election->year }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cards -->
|
||||||
|
<div class="grid grid-cols-12 gap-6">
|
||||||
|
|
||||||
|
@foreach($positions as $key => $position)
|
||||||
|
<div wire:key="pos_{{ $key }}" wire:ignore
|
||||||
|
class="flex flex-col col-span-full sm:col-span-6 bg-white dark:bg-gray-800 shadow-sm rounded-xl">
|
||||||
|
<header class="px-5 py-4 border-b border-gray-100 dark:border-gray-700/60">
|
||||||
|
<h2 class="font-semibold text-gray-800 dark:text-gray-100"><i
|
||||||
|
class="fa-sharp-duotone fa-solid {{ $position['icon'] }} w-5 h-5 fill-current text-white mr-4"></i>{{ $position['title'] }}
|
||||||
|
</h2>
|
||||||
|
</header>
|
||||||
|
<div x-ref="chart_{{ $key }}-legend" class="px-5 py-3">
|
||||||
|
<ul class="flex flex-wrap gap-x-4">
|
||||||
|
<li>
|
||||||
|
<button style="display: inline-flex; align-items: center;"><span
|
||||||
|
class="text-gray-800 dark:text-gray-100"
|
||||||
|
style="font-size: 1.88rem; line-height: 1.33; font-weight: 700; margin-right: 8px; pointer-events: none;">0 / {{ $ehrenMitgliederCount }}</span><span
|
||||||
|
class="text-gray-500 dark:text-gray-400"
|
||||||
|
style="font-size: 0.875rem; line-height: 1.5715;">Ehrenmitglieder</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button style="display: inline-flex; align-items: center;"><span
|
||||||
|
style="display: flex; align-items: center;"><span
|
||||||
|
class="text-gray-800 dark:text-gray-100"
|
||||||
|
style="font-size: 1.88rem; line-height: 1.33; font-weight: 700; margin-right: 8px; pointer-events: none;">0 / {{ $aktiveMitgliederCount }}</span><span
|
||||||
|
class="text-gray-500 dark:text-gray-400"
|
||||||
|
style="font-size: 0.875rem; line-height: 1.5715;">Aktive Mitglieder</span></span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="grow">
|
||||||
|
<!-- Change the height attribute to adjust the chart height -->
|
||||||
|
<canvas x-ref="chart_{{ $key }}" width="724" height="288"
|
||||||
|
style="display: block; box-sizing: border-box; height: 288px; width: 724px;"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endvolt
|
||||||
|
</x-layouts.app>
|
||||||
22
yarn.lock
22
yarn.lock
@@ -166,6 +166,11 @@
|
|||||||
"@jridgewell/resolve-uri" "^3.1.0"
|
"@jridgewell/resolve-uri" "^3.1.0"
|
||||||
"@jridgewell/sourcemap-codec" "^1.4.14"
|
"@jridgewell/sourcemap-codec" "^1.4.14"
|
||||||
|
|
||||||
|
"@kurkle/color@^0.3.0":
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@kurkle/color/-/color-0.3.2.tgz#5acd38242e8bde4f9986e7913c8fdf49d3aa199f"
|
||||||
|
integrity sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==
|
||||||
|
|
||||||
"@noble/ciphers@^0.5.1":
|
"@noble/ciphers@^0.5.1":
|
||||||
version "0.5.3"
|
version "0.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.3.tgz#48b536311587125e0d0c1535f73ec8375cd76b23"
|
resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-0.5.3.tgz#48b536311587125e0d0c1535f73ec8375cd76b23"
|
||||||
@@ -483,6 +488,18 @@ caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001663:
|
|||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4"
|
||||||
integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==
|
integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==
|
||||||
|
|
||||||
|
chart.js@^4.4.4:
|
||||||
|
version "4.4.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.4.tgz#b682d2e7249f7a0cbb1b1d31c840266ae9db64b7"
|
||||||
|
integrity sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==
|
||||||
|
dependencies:
|
||||||
|
"@kurkle/color" "^0.3.0"
|
||||||
|
|
||||||
|
chartjs-adapter-date-fns@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-3.0.0.tgz#c25f63c7f317c1f96f9a7c44bd45eeedb8a478e5"
|
||||||
|
integrity sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==
|
||||||
|
|
||||||
chokidar@^3.5.3:
|
chokidar@^3.5.3:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
|
||||||
@@ -542,6 +559,11 @@ data-uri-to-buffer@^4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
|
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
|
||||||
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
|
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
|
||||||
|
|
||||||
|
date-fns@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-4.1.0.tgz#64b3d83fff5aa80438f5b1a633c2e83b8a1c2d14"
|
||||||
|
integrity sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==
|
||||||
|
|
||||||
debug@^2.2.0:
|
debug@^2.2.0:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
|||||||
Reference in New Issue
Block a user