2025-10-17 11:11:54 +02:00
<? php
2025-10-20 21:42:15 +02:00
use App\Models\Company ;
use App\Models\Transaction ;
use Illuminate\Support\Collection ;
use Illuminate\Support\Str ;
use Livewire\Attributes\Computed ;
2025-10-17 11:11:54 +02:00
use Livewire\Attributes\Layout ;
2025-10-31 13:14:59 +01:00
use Livewire\Attributes\Title ;
2025-10-17 11:11:54 +02:00
use Livewire\Volt\Component ;
2025-10-31 13:14:59 +01:00
new #[ Layout ( 'components.layouts.app' ), Title ( 'Unternehmensauskunft' )] class extends Component {
2025-10-17 11:11:54 +02:00
public string $search = '' ;
2025-10-20 21:42:15 +02:00
#[ Computed ]
public function overview () : array
{
$totalCompanies = Company :: count ();
$openAlertsQuery = Transaction :: query () -> where ( 'requires_review' , true );
$openAlertsCount = ( clone $openAlertsQuery ) -> count ();
$openAlertsVolume = ( clone $openAlertsQuery ) -> sum ( 'amount' );
$avgRisk = round (( clone $openAlertsQuery ) -> avg ( 'risk_score' ) ?? 0 );
$watchlistHits = ( clone $openAlertsQuery ) -> where ( 'signals' , 'like' , '%Sanktionsliste%' ) -> count ();
$totalTransactions = Transaction :: count ();
$falsePositives = Transaction :: where ( 'status' , Transaction :: STATUS_FALSE_POSITIVE ) -> count ();
$automationShare = 100 - ( int ) round (( $falsePositives / max ( $totalTransactions , 1 )) * 100 );
$automationShare = max ( 68 , min ( $automationShare , 94 ));
return [
'companies' => $totalCompanies ,
'open_alerts' => $openAlertsCount ,
'open_volume' => $openAlertsVolume ,
'avg_risk' => $avgRisk ,
'watchlist_hits' => $watchlistHits ,
'automation_share' => $automationShare ,
];
}
#[ Computed ]
public function companies () : Collection
{
$query = Company :: query ()
-> withCount ([
2025-11-07 13:52:36 +01:00
'transactions as alert_count' => fn ( $transactions ) => $transactions -> where ( 'requires_review' , true ),
2025-10-20 21:42:15 +02:00
])
-> withSum ([
2025-11-07 13:52:36 +01:00
'transactions as alert_volume' => fn ( $transactions ) => $transactions -> where ( 'requires_review' , true ),
2025-10-20 21:42:15 +02:00
], 'amount' )
-> withAvg ([
2025-11-07 13:52:36 +01:00
'transactions as alert_risk' => fn ( $transactions ) => $transactions -> where ( 'requires_review' , true ),
2025-10-20 21:42:15 +02:00
], 'risk_score' )
-> with ([
2025-11-07 13:52:36 +01:00
'transactions' => fn ( $transactions ) => $transactions -> where ( 'requires_review' , true ) -> latest ( 'executed_at' ) -> limit ( 1 ),
2025-10-20 21:42:15 +02:00
])
-> when (
$this -> search !== '' ,
function ( $builder ) {
$like = '%' . $this -> search . '%' ;
$builder -> where ( function ( $inner ) use ( $like ) {
$inner -> where ( 'name' , 'like' , $like )
-> orWhere ( 'legal_name' , 'like' , $like )
-> orWhere ( 'ticker' , 'like' , $like )
-> orWhere ( 'sector' , 'like' , $like );
});
}
)
-> orderByDesc ( 'alert_volume' )
-> orderBy ( 'name' )
-> limit ( 6 );
return $query -> get ();
}
2025-10-17 11:11:54 +02:00
}; ?>
2025-10-20 21:42:15 +02:00
@php
2025-11-07 13:52:36 +01:00
$overview = $this->overview;
/** @var \Illuminate\Support\Collection<int, \App\Models\Company> $companies */
2025-10-20 21:42:15 +02:00
$companies = $this->companies;
$formatCurrency = static fn (float $value): string => number_format($value, 2, ',', '.') . ' €';
$formatNumber = static fn ($value): string => number_format((float) $value, 0, ',', '.');
2025-11-07 13:52:36 +01:00
@endphp
2025-10-17 11:11:54 +02:00
2025-11-07 13:52:36 +01:00
<div class="flex flex-col gap-8">
<section class="rounded-3xl border border-slate-200/70 bg-white/95 p-8 shadow-sm backdrop-blur dark:border-slate-700/70 dark:bg-slate-900/90">
<div class="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
<div class="max-w-2xl space-y-2">
<flux:heading size="xl">{{ __('Unternehmensauskunft') }}</flux:heading>
<flux:text>
{{ __('Einfache Suche für Unternehmensdaten und historische Cases') }}
</flux:text>
</div>
<div class="w-full max-w-md">
<flux:input
wire:model.live.debounce.300ms="search"
type="search"
icon="magnifying-glass"
placeholder="{{ __('Rechtlicher Name, Ticker oder Branche durchsuchen …') }}" />
2025-10-20 21:42:15 +02:00
</div>
</div>
2025-11-07 13:52:36 +01:00
<!-- <div class="mt-8 grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
<div class="rounded-2xl border border-slate-200/70 bg-slate-50/90 p-5 text-sm dark:border-slate-700/70 dark:bg-slate-800/70">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-300">{{ __('Unternehmen im Screening') }}</p>
<p class="mt-2 text-2xl font-semibold text-slate-900 dark:text-white">{{ $formatNumber($overview['companies']) }}</p>
</div>
<div class="rounded-2xl border border-emerald-400/40 bg-emerald-500/10 p-5 text-sm shadow-sm shadow-emerald-900/10">
<p class="text-xs font-semibold uppercase tracking-wide text-emerald-800 dark:text-emerald-200">{{ __('Offene Alerts') }}</p>
<p class="mt-2 text-2xl font-semibold text-emerald-900 dark:text-emerald-100">{{ $formatNumber($overview['open_alerts']) }}</p>
<p class="text-xs text-emerald-700 dark:text-emerald-200">{{ __('Volumen') }} {{ $formatCurrency($overview['open_volume']) }}</p>
</div>
<div class="rounded-2xl border border-indigo-400/40 bg-indigo-500/10 p-5 text-sm shadow-sm shadow-indigo-900/10">
<p class="text-xs font-semibold uppercase tracking-wide text-indigo-800 dark:text-indigo-200">{{ __('Ø Risikoscore offene Fälle') }}</p>
<p class="mt-2 text-2xl font-semibold text-indigo-900 dark:text-indigo-100">{{ $overview['avg_risk'] }}</p>
<p class="text-xs text-indigo-700 dark:text-indigo-200">{{ __('Watchlist-Hits') }} {{ $formatNumber($overview['watchlist_hits']) }}</p>
</div>
<div class="rounded-2xl border border-zinc-200/70 bg-zinc-50/90 p-5 text-sm dark:border-zinc-700/70 dark:bg-zinc-800/70">
<p class="text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-300">{{ __('Automationsquote') }}</p>
<p class="mt-2 text-2xl font-semibold text-zinc-900 dark:text-white">{{ $overview['automation_share'] }}%</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Analyst:innen übernehmen nur komplexe Spezialfälle') }}</p>
</div>
</div> -->
</section>
<section class="grid gap-6">
<div class="rounded-3xl border border-zinc-200/70 bg-white/95 p-6 shadow-sm backdrop-blur dark:border-zinc-700/70 dark:bg-zinc-900/90">
<div class="flex flex-wrap items-end justify-between gap-4">
<div>
<h2 class="text-lg font-semibold text-zinc-900 dark:text-zinc-100">{{ __('Historische Cases') }}</h2>
</div>
<flux:badge variant="outline">
{{ $companies->count() }} {{ __('Treffer') }}
</flux:badge>
</div>
<div class="mt-6 space-y-4">
@forelse ($companies as $company)
2025-10-20 21:42:15 +02:00
@php
2025-11-07 13:52:36 +01:00
$latestAlert = optional($company->transactions->first());
$alertVolume = (float) ($company->alert_volume ?? 0);
$alertCount = (int) ($company->alert_count ?? 0);
$alertRisk = (int) round($company->alert_risk ?? 0);
$riskBadgeClasses = match (Str::lower($company->kyc_risk_level)) {
'high', 'hoch' => 'bg-rose-500/10 text-rose-600 dark:text-rose-300 border border-rose-500/20',
'low', 'niedrig' => 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-300 border border-emerald-500/20',
default => 'bg-amber-500/10 text-amber-600 dark:text-amber-300 border border-amber-500/20',
};
2025-10-20 21:42:15 +02:00
@endphp
2025-11-05 16:16:08 +01:00
<a href="{{ route('company.transactions', ['company' => $company->id]) }}" wire:navigate class="grid gap-4 rounded-2xl border border-zinc-200/70 bg-zinc-50/70 p-4 text-sm transition hover:-translate-y-0.5 hover:shadow-md dark:border-zinc-700/60 dark:bg-zinc-800/60 md:grid-cols-[1.4fr_1fr]">
2025-10-20 21:42:15 +02:00
<div class="space-y-2">
<div class="flex items-start justify-between gap-3">
<div>
<p class="text-base font-semibold text-zinc-900 dark:text-zinc-100">{{ $company->legal_name ?? $company->name }}</p>
<p class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ $company->ticker }} · {{ $company->sector }}</p>
</div>
2025-11-07 13:52:36 +01:00
<!-- <span class="inline-flex items-center gap-2 rounded-full px-3 py-1 text-xs font-semibold {{ $riskBadgeClasses }}">
2025-10-20 21:42:15 +02:00
{{ __('KYC-Risiko') }}: {{ Str::title($company->kyc_risk_level) }}
2025-11-07 13:52:36 +01:00
</span> -->
2025-10-20 21:42:15 +02:00
</div>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ $company->summary }}</p>
<div class="flex flex-wrap gap-3 text-xs text-zinc-500 dark:text-zinc-400">
2025-11-07 13:52:36 +01:00
<!-- <span class="inline-flex items-center gap-1 rounded-full bg-white/60 px-2 py-1 font-medium text-zinc-700 dark:bg-zinc-900/60 dark:text-zinc-200">
2025-10-20 21:42:15 +02:00
{{ __('Alerts offen') }}: {{ $formatNumber($alertCount) }}
2025-11-07 13:52:36 +01:00
</span> -->
<!-- <span class="inline-flex items-center gap-1 rounded-full bg-white/60 px-2 py-1 font-medium text-zinc-700 dark:bg-zinc-900/60 dark:text-zinc-200">
2025-10-20 21:42:15 +02:00
{{ __('Volumen') }}: {{ $formatCurrency($alertVolume) }}
2025-11-07 13:52:36 +01:00
</span> -->
<span class="inline-flex items-center gap-1 rounded-full bg-white/60 px-2 py-1 font-medium text-zinc-700 dark:bg-zinc-900/60 dark:text-zinc-200">
Automatisierungsscore: muss noch eingebaut werden
2025-10-20 21:42:15 +02:00
</span>
<span class="inline-flex items-center gap-1 rounded-full bg-white/60 px-2 py-1 font-medium text-zinc-700 dark:bg-zinc-900/60 dark:text-zinc-200">
{{ __('Ø Risikoscore') }}: {{ $alertRisk }}
</span>
</div>
</div>
<div class="space-y-3 rounded-2xl border border-zinc-200/70 bg-white/80 p-4 dark:border-zinc-700/60 dark:bg-zinc-900/70">
<p class="text-xs font-semibold uppercase tracking-wide text-zinc-400">{{ __('Neuester Alert') }}</p>
@if ($latestAlert)
2025-11-07 13:52:36 +01:00
<div class="space-y-1">
<p class="text-sm font-medium text-zinc-900 dark:text-zinc-100">{{ $latestAlert->counterparty }}</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ $latestAlert->executed_at?->format('d.m.Y H:i') }} · {{ $latestAlert->channel }}</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Grund') }}: {{ $latestAlert->flagged_reason }}</p>
</div>
2025-10-20 21:42:15 +02:00
@else
2025-11-07 13:52:36 +01:00
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Keine offenen Alerts – Monitoring im grünen Bereich.') }}</p>
2025-10-20 21:42:15 +02:00
@endif
<div class="flex flex-wrap gap-2 text-xs text-indigo-600 dark:text-indigo-300">
<span class="inline-flex items-center gap-1 rounded-full bg-indigo-500/10 px-2 py-1">
{{ __('Insights') }}
</span>
<span>{{ __('Generiere automatisch AIM-Summary und investigatives Casefile.') }}</span>
</div>
</div>
2025-11-05 16:16:08 +01:00
</a>
2025-11-07 13:52:36 +01:00
@empty
2025-10-20 21:42:15 +02:00
<div class="rounded-2xl border border-dashed border-zinc-300/70 bg-white/80 p-6 text-center text-sm text-zinc-500 dark:border-zinc-600/60 dark:bg-zinc-900/60 dark:text-zinc-300">
{{ __('Keine Unternehmen gefunden. Bitte Suchkriterien anpassen oder Seed-Daten neu laden.') }}
</div>
2025-11-07 13:52:36 +01:00
@endforelse
</div>
2025-10-20 21:42:15 +02:00
</div>
2025-11-07 13:52:36 +01:00
<!-- <aside class="flex flex-col gap-6">
<div class="rounded-3xl border border-zinc-200/70 bg-white/95 p-6 shadow-sm backdrop-blur dark:border-zinc-700/70 dark:bg-zinc-900/90">
<h3 class="text-base font-semibold text-zinc-900 dark:text-zinc-100">{{ __('Proof-of-Concept Checks') }}</h3>
<ul class="mt-4 space-y-3 text-sm text-zinc-600 dark:text-zinc-300">
<li class="flex gap-3">
<span class="mt-0.5 h-2 w-2 rounded-full bg-emerald-500"></span>
<div>
<p class="font-medium text-zinc-900 dark:text-zinc-100">{{ __('Adverse-Media Analyse in Echtzeit') }}</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('LLM-Agenten extrahieren Risikosignale aus Nachrichtenclustern und verknüpfen sie mit dem Unternehmensnetzwerk.') }}</p>
</div>
</li>
<li class="flex gap-3">
<span class="mt-0.5 h-2 w-2 rounded-full bg-indigo-500"></span>
<div>
<p class="font-medium text-zinc-900 dark:text-zinc-100">{{ __('Beneficial Ownership Mapping') }}</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Regelmäßiger Abgleich von UBO-Graphen gegen Sanktionslisten, PEP-Register und Hochrisikoländer.') }}</p>
</div>
</li>
<li class="flex gap-3">
<span class="mt-0.5 h-2 w-2 rounded-full bg-amber-500"></span>
<div>
<p class="font-medium text-zinc-900 dark:text-zinc-100">{{ __('Runbook-Automatisierung') }}</p>
<p class="text-xs text-zinc-500 dark:text-zinc-400">{{ __('Case-Workflows generieren Audit-Trails, Delegationen und Eskalationspfade automatisch.') }}</p>
</div>
</li>
</ul>
</div>
<div class="rounded-3xl border border-zinc-200/70 bg-white/95 p-6 shadow-sm backdrop-blur dark:border-zinc-700/70 dark:bg-zinc-900/90">
<h3 class="text-base font-semibold text-zinc-900 dark:text-zinc-100">{{ __('Sofortmaßnahmen (PoC)') }}</h3>
<ul class="mt-4 space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
<li class="flex items-center justify-between gap-2 rounded-2xl bg-zinc-50/80 px-3 py-2 dark:bg-zinc-800/60">
<span>{{ __('KYC Refresh initiiert') }}</span>
<span class="text-xs font-semibold text-emerald-600 dark:text-emerald-300">{{ __('automatisiert') }}</span>
</li>
<li class="flex items-center justify-between gap-2 rounded-2xl bg-zinc-50/80 px-3 py-2 dark:bg-zinc-800/60">
<span>{{ __('AI Investigations Summary') }}</span>
<span class="text-xs font-semibold text-indigo-600 dark:text-indigo-300">{{ __('bereit in 12s') }}</span>
</li>
<li class="flex items-center justify-between gap-2 rounded-2xl bg-zinc-50/80 px-3 py-2 dark:bg-zinc-800/60">
<span>{{ __('Case Handover an 2nd Line') }}</span>
<span class="text-xs font-semibold text-rose-600 dark:text-rose-300">{{ __('3 Eskalationen') }}</span>
</li>
</ul>
</div>
</aside> -->
</section>
</div>