14-04-2026
This commit is contained in:
parent
f58c709945
commit
0f82fea88a
72 changed files with 7414 additions and 148 deletions
|
|
@ -63,8 +63,12 @@ return [
|
|||
'no_participants' => 'Noch keine Teilnehmer.',
|
||||
'no_participants_with_points' => 'Noch keine Teilnehmer mit Punkten.',
|
||||
'anonymous_consultant' => 'Anonymer Berater',
|
||||
'ranking_all_active' => 'Alle Aktiven',
|
||||
'vip_view_notice' => 'VIP-Ansicht: Klarnamen aller Teilnehmer werden angezeigt.',
|
||||
'vip_terms_accepted' => 'Teilnahmebedingungen akzeptiert',
|
||||
'vip_terms_pending' => 'Teilnahmebedingungen noch nicht akzeptiert',
|
||||
'ranking_anonymous_hint' => 'Namen erscheinen erst, wenn die Teilnahme am Incentive bestätigt wurde.',
|
||||
'ranking_extended_hint' => 'Die Liste zeigt die Plätze 1–30. Die besten :n qualifizierten Berater (hervorgehoben) gewinnen; die Plätze danach zeigen, wer noch nachlegen kann.',
|
||||
'ranking_extended_hint' => 'Die Liste zeigt alle Berater mit mehr als 0 Punkten. Die besten :n qualifizierten Berater (hervorgehoben) gewinnen; die Plätze danach zeigen, wer noch nachlegen kann.',
|
||||
'calculation_details' => 'Berechnungsdetails',
|
||||
'close' => 'Schliessen',
|
||||
|
||||
|
|
@ -136,6 +140,14 @@ return [
|
|||
'you_participate' => 'Du nimmst teil!',
|
||||
'your_rank' => 'Dein aktueller Rang',
|
||||
'participate_intro' => 'Bist du bereit für die Challenge? Melde dich einmalig an, um im offiziellen Ranking gelistet zu werden.',
|
||||
'dash_notice_unregistered_title' => 'Noch nicht angemeldet',
|
||||
'dash_notice_unregistered_body' => 'Du nimmst am Incentive noch nicht offiziell teil. Ohne Bestätigung werden deine Punkte nicht gewertet und du erscheinst nicht in der Rangliste.',
|
||||
'dash_notice_unconfirmed_title' => 'Teilnahme noch nicht bestätigt',
|
||||
'dash_notice_unconfirmed_body' => 'Deine Punkte laufen bereits mit – aber ohne Bestätigung der Teilnahmebedingungen wirst du in der Rangliste anonym angezeigt und kannst nicht gewinnen.',
|
||||
'dash_notice_btn' => 'Jetzt Teilnahme bestätigen',
|
||||
'dash_modal_title' => 'Teilnahme bestätigen',
|
||||
'dash_modal_intro' => 'Bitte lies die Informationen und Teilnahmebedingungen sorgfältig durch und bestätige anschließend deine Teilnahme.',
|
||||
'dash_modal_cancel' => 'Schließen',
|
||||
'pending_confirmation_banner' => 'Deine Punkte werden bereits im Qualifikationszeitraum mitgerechnet. Bitte bestätige die Teilnahme, damit dein Name in der Rangliste sichtbar wird und du alle Funktionen nutzen kannst.',
|
||||
'details_requires_confirmation' => 'Die Detailansicht ist erst nach Bestätigung der Teilnahme verfügbar.',
|
||||
'participate_abo_hint' => 'Es liegt mindestens ein für die Wertung relevantes Abo vor (aktives Berater-Abo oder Kundenabo im Qualifikationszeitraum). Mit dem Teilnehmen werden die Punkte dafür direkt nach den aktuellen Regeln übernommen.',
|
||||
|
|
|
|||
|
|
@ -86,4 +86,6 @@ return [
|
|||
'my_abo' => 'Mein Abo',
|
||||
'my_subscriptions' => 'Meine Abos',
|
||||
'team_customers' => 'Team Kunden',
|
||||
'payment_monitor' => 'Payment Monitor',
|
||||
'payment_monitor_management' => 'Payment Monitor GF',
|
||||
];
|
||||
|
|
|
|||
6
resources/lang/de/pagination.php
Normal file
6
resources/lang/de/pagination.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'previous' => '« Zurück',
|
||||
'next' => 'Weiter »',
|
||||
];
|
||||
|
|
@ -161,12 +161,32 @@ return [
|
|||
'payment_not_found' => 'Zahlung nicht gefunden',
|
||||
'payment_not_found_description' => 'Die Zahlung mit der Referenz :reference konnte nicht gefunden werden. Bitte kontaktieren Sie uns, falls Sie bereits bezahlt haben.',
|
||||
'payment_canceled' => 'Zahlung abgebrochen',
|
||||
'payment_canceled_description' => 'Der Zahlungsvorgang wurde abgebrochen. Ihre Bestellung wurde nicht ausgeführt.',
|
||||
'payment_error' => 'Zahlungsfehler',
|
||||
'payment_error_description' => 'Bei der Zahlungsabwicklung ist ein Fehler aufgetreten. Ihre Bestellung konnte nicht abgeschlossen werden.',
|
||||
'payment_canceled_description' => 'Sie haben den Zahlungsvorgang abgebrochen. Ihre Bestellung wurde nicht ausgeführt und es wurde nichts belastet.',
|
||||
'payment_canceled_hint' => 'Sie können jederzeit einen neuen Zahlungsversuch starten.',
|
||||
'payment_error' => 'Zahlung fehlgeschlagen',
|
||||
'payment_error_description' => 'Die Zahlung konnte leider nicht abgeschlossen werden.',
|
||||
'payment_error_hint' => 'Bitte prüfen Sie Ihre Zahlungsdaten und versuchen Sie es erneut — oder wählen Sie eine andere Zahlungsart.',
|
||||
'payment_error_retry' => 'Erneut versuchen',
|
||||
'payment_error_code' => 'Fehlercode',
|
||||
'payment_error_what_to_do' => 'Was kann ich tun?',
|
||||
'payment_unknown_status' => 'Unbekannter Zahlungsstatus',
|
||||
'payment_unknown_status_description' => 'Der Zahlungsstatus konnte nicht ermittelt werden. Bitte kontaktieren Sie uns für weitere Informationen.',
|
||||
'contact_support_if_needed' => 'Bei Fragen wenden Sie sich bitte an unseren Kundenservice.',
|
||||
'contact_support_if_needed' => 'Bei weiteren Fragen wenden Sie sich bitte an unseren Kundenservice.',
|
||||
'try_again' => 'Erneut versuchen',
|
||||
'choose_different_payment' => 'Andere Zahlungsart wählen',
|
||||
'nothing_was_charged' => 'Es wurde nichts von Ihrem Konto abgebucht.',
|
||||
'payment_error_reasons' => [
|
||||
'card_expired' => 'Ihre Karte ist abgelaufen. Bitte verwenden Sie eine gültige Karte oder wählen Sie eine andere Zahlungsart.',
|
||||
'card_blocked' => 'Ihre Karte ist gesperrt. Bitte wenden Sie sich an Ihre Bank oder wählen Sie eine andere Zahlungsart.',
|
||||
'card_invalid' => 'Die Kartendaten sind ungültig. Bitte überprüfen Sie Ihre Eingabe.',
|
||||
'card_declined' => 'Ihre Bank hat die Zahlung abgelehnt. Bitte wenden Sie sich an Ihre Bank oder wählen Sie eine andere Zahlungsart.',
|
||||
'insufficient_funds' => 'Das Kartenlimit wurde überschritten. Bitte wenden Sie sich an Ihre Bank oder wählen Sie eine andere Zahlungsart.',
|
||||
'cvv_invalid' => 'Die Prüfziffer (CVV) ist nicht korrekt. Bitte überprüfen Sie die 3-stellige Zahl auf der Rückseite Ihrer Karte.',
|
||||
'3ds_failed' => 'Die 3D-Secure-Authentifizierung ist fehlgeschlagen. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsart.',
|
||||
'timeout' => 'Die Verbindung zur Bank ist unterbrochen (Timeout). Bitte versuchen Sie es in wenigen Minuten erneut.',
|
||||
'fraud' => 'Die Zahlung wurde aus Sicherheitsgründen abgelehnt. Bitte wenden Sie sich an Ihre Bank.',
|
||||
'general' => 'Bitte überprüfen Sie Ihre Zahlungsdaten und versuchen Sie es erneut. Falls das Problem weiterhin besteht, wählen Sie eine andere Zahlungsart.',
|
||||
],
|
||||
|
||||
// DHL Packstation/Paketbox
|
||||
'packstation_delivery' => 'Lieferung an Packstation/Paketbox',
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@ return [
|
|||
'no_participants' => 'No participants yet.',
|
||||
'no_participants_with_points' => 'No participants with points yet.',
|
||||
'anonymous_consultant' => 'Anonymous consultant',
|
||||
'ranking_all_active' => 'All Active',
|
||||
'vip_view_notice' => 'VIP view: Real names of all participants are shown.',
|
||||
'vip_terms_accepted' => 'Terms accepted',
|
||||
'vip_terms_pending' => 'Terms not yet accepted',
|
||||
'ranking_anonymous_hint' => 'Names appear only after participation in the incentive has been confirmed.',
|
||||
'ranking_extended_hint' => 'The list shows ranks 1–30. The best :n qualified consultants (highlighted) win; the ranks below show who can still push ahead.',
|
||||
'ranking_extended_hint' => 'The list shows all consultants with more than 0 points. The best :n qualified consultants (highlighted) win; the ranks below show who can still push ahead.',
|
||||
'calculation_details' => 'Calculation Details',
|
||||
'close' => 'Close',
|
||||
|
||||
|
|
@ -136,6 +140,14 @@ return [
|
|||
'you_participate' => 'You are participating!',
|
||||
'your_rank' => 'Your current rank',
|
||||
'participate_intro' => 'Ready for the challenge? Register once to be listed in the official ranking.',
|
||||
'dash_notice_unregistered_title' => 'Not yet registered',
|
||||
'dash_notice_unregistered_body' => 'You are not yet officially participating. Without confirmation, your points won\'t count and you won\'t appear in the ranking.',
|
||||
'dash_notice_unconfirmed_title' => 'Participation not yet confirmed',
|
||||
'dash_notice_unconfirmed_body' => 'Your points are already tracked – but without accepting the terms you will appear anonymously in the ranking and cannot win.',
|
||||
'dash_notice_btn' => 'Confirm participation now',
|
||||
'dash_modal_title' => 'Confirm participation',
|
||||
'dash_modal_intro' => 'Please read the information and terms carefully, then confirm your participation.',
|
||||
'dash_modal_cancel' => 'Close',
|
||||
'pending_confirmation_banner' => 'Your points are already counted for the qualification period. Please confirm participation so your name appears in the ranking and you can use all features.',
|
||||
'details_requires_confirmation' => 'The detail view is available only after you confirm participation.',
|
||||
'participate_abo_hint' => 'You already have at least one subscription that counts (active consultant subscription or a customer subscription started in the qualification period). When you join, points for it are applied immediately according to the current rules.',
|
||||
|
|
|
|||
|
|
@ -86,4 +86,6 @@ return [
|
|||
'my_abo' => 'My Abo',
|
||||
'my_subscriptions' => 'My Subscriptions',
|
||||
'team_customers' => 'Team Customers',
|
||||
'payment_monitor' => 'Payment Monitor',
|
||||
'payment_monitor_management' => 'Payment Monitor (Mgmt)',
|
||||
];
|
||||
|
|
|
|||
6
resources/lang/en/pagination.php
Normal file
6
resources/lang/en/pagination.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'previous' => '« Previous',
|
||||
'next' => 'Next »',
|
||||
];
|
||||
|
|
@ -154,12 +154,32 @@ return [
|
|||
'payment_not_found' => 'Payment not found',
|
||||
'payment_not_found_description' => 'The payment with reference :reference could not be found. Please contact us if you have already paid.',
|
||||
'payment_canceled' => 'Payment canceled',
|
||||
'payment_canceled_description' => 'The payment process was canceled. Your order was not processed.',
|
||||
'payment_error' => 'Payment error',
|
||||
'payment_error_description' => 'An error occurred during payment processing. Your order could not be completed.',
|
||||
'payment_canceled_description' => 'You have canceled the payment process. Your order was not processed and nothing has been charged.',
|
||||
'payment_canceled_hint' => 'You can start a new payment attempt at any time.',
|
||||
'payment_error' => 'Payment failed',
|
||||
'payment_error_description' => 'Unfortunately, the payment could not be completed.',
|
||||
'payment_error_hint' => 'Please check your payment details and try again — or choose a different payment method.',
|
||||
'payment_error_retry' => 'Try again',
|
||||
'payment_error_code' => 'Error code',
|
||||
'payment_error_what_to_do' => 'What can I do?',
|
||||
'payment_unknown_status' => 'Unknown payment status',
|
||||
'payment_unknown_status_description' => 'The payment status could not be determined. Please contact us for more information.',
|
||||
'contact_support_if_needed' => 'If you have any questions, please contact our customer service.',
|
||||
'contact_support_if_needed' => 'If you have further questions, please contact our customer service.',
|
||||
'try_again' => 'Try again',
|
||||
'choose_different_payment' => 'Choose a different payment method',
|
||||
'nothing_was_charged' => 'Nothing has been charged to your account.',
|
||||
'payment_error_reasons' => [
|
||||
'card_expired' => 'Your card has expired. Please use a valid card or choose a different payment method.',
|
||||
'card_blocked' => 'Your card is blocked. Please contact your bank or choose a different payment method.',
|
||||
'card_invalid' => 'The card details are invalid. Please check your input.',
|
||||
'card_declined' => 'Your bank declined the payment. Please contact your bank or choose a different payment method.',
|
||||
'insufficient_funds' => 'The card limit has been exceeded. Please contact your bank or choose a different payment method.',
|
||||
'cvv_invalid' => 'The security code (CVV) is incorrect. Please check the 3-digit code on the back of your card.',
|
||||
'3ds_failed' => '3D Secure authentication failed. Please try again or choose a different payment method.',
|
||||
'timeout' => 'The connection to the bank was interrupted (timeout). Please try again in a few minutes.',
|
||||
'fraud' => 'The payment was declined for security reasons. Please contact your bank.',
|
||||
'general' => 'Please check your payment details and try again. If the problem persists, please choose a different payment method.',
|
||||
],
|
||||
|
||||
// DHL Packstation/Parcel Box
|
||||
'packstation_delivery' => 'Delivery to Packstation/Parcel Box',
|
||||
|
|
|
|||
|
|
@ -63,8 +63,12 @@ return [
|
|||
'no_participants' => 'Aun no hay participantes.',
|
||||
'no_participants_with_points' => 'Aun no hay participantes con puntos.',
|
||||
'anonymous_consultant' => 'Consultor anonimo',
|
||||
'ranking_all_active' => 'Todos los activos',
|
||||
'vip_view_notice' => 'Vista VIP: Se muestran los nombres reales de todos los participantes.',
|
||||
'vip_terms_accepted' => 'Condiciones aceptadas',
|
||||
'vip_terms_pending' => 'Condiciones aun no aceptadas',
|
||||
'ranking_anonymous_hint' => 'Los nombres solo se muestran despues de confirmar la participacion en el incentivo.',
|
||||
'ranking_extended_hint' => 'La lista muestra los puestos 1–30. Los mejores :n consultores calificados (marcados) ganan; los puestos siguientes muestran quien aun puede reforzar.',
|
||||
'ranking_extended_hint' => 'La lista muestra todos los consultores con mas de 0 puntos. Los mejores :n consultores calificados (marcados) ganan; los puestos siguientes muestran quien aun puede reforzar.',
|
||||
'calculation_details' => 'Detalles del calculo',
|
||||
'close' => 'Cerrar',
|
||||
|
||||
|
|
@ -136,6 +140,14 @@ return [
|
|||
'you_participate' => 'Estas participando!',
|
||||
'your_rank' => 'Tu puesto actual',
|
||||
'participate_intro' => 'Listo para el desafio? Registrate una vez para aparecer en el ranking oficial.',
|
||||
'dash_notice_unregistered_title' => 'Aun no registrado',
|
||||
'dash_notice_unregistered_body' => 'Aun no participas oficialmente. Sin confirmacion, tus puntos no contaran y no aparecereas en el ranking.',
|
||||
'dash_notice_unconfirmed_title' => 'Participacion aun no confirmada',
|
||||
'dash_notice_unconfirmed_body' => 'Tus puntos ya se estan registrando, pero sin aceptar las condiciones aparecereas de forma anonima en el ranking y no podras ganar.',
|
||||
'dash_notice_btn' => 'Confirmar participacion ahora',
|
||||
'dash_modal_title' => 'Confirmar participacion',
|
||||
'dash_modal_intro' => 'Por favor lee la informacion y las condiciones con atencion y confirma tu participacion.',
|
||||
'dash_modal_cancel' => 'Cerrar',
|
||||
'pending_confirmation_banner' => 'Tus puntos ya cuentan en el periodo de calificacion. Confirma la participacion para que tu nombre sea visible en el ranking y puedas usar todas las funciones.',
|
||||
'details_requires_confirmation' => 'La vista detallada solo esta disponible despues de confirmar la participacion.',
|
||||
'participate_abo_hint' => 'Ya tienes al menos una suscripcion relevante (suscripcion de consultor activa o suscripcion de cliente en el periodo de calificacion). Al participar, los puntos se aplican de inmediato segun las reglas vigentes.',
|
||||
|
|
|
|||
|
|
@ -86,4 +86,6 @@ return [
|
|||
'my_abo' => 'Mi Suscripción',
|
||||
'my_subscriptions' => 'Mis Suscripciones',
|
||||
'team_customers' => 'Clientes del Equipo',
|
||||
'payment_monitor' => 'Monitor de Pagos',
|
||||
'payment_monitor_management' => 'Monitor de Pagos (Dir.)',
|
||||
];
|
||||
|
|
|
|||
6
resources/lang/es/pagination.php
Normal file
6
resources/lang/es/pagination.php
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
'previous' => '« Anterior',
|
||||
'next' => 'Siguiente »',
|
||||
];
|
||||
|
|
@ -155,12 +155,32 @@ return [
|
|||
'payment_not_found' => 'Pago no encontrado',
|
||||
'payment_not_found_description' => 'No se pudo encontrar el pago con la referencia :reference. Por favor contáctenos si ya ha realizado el pago.',
|
||||
'payment_canceled' => 'Pago cancelado',
|
||||
'payment_canceled_description' => 'El proceso de pago fue cancelado. Su pedido no fue procesado.',
|
||||
'payment_error' => 'Error de pago',
|
||||
'payment_error_description' => 'Se produjo un error durante el procesamiento del pago. Su pedido no pudo completarse.',
|
||||
'payment_canceled_description' => 'Ha cancelado el proceso de pago. Su pedido no fue procesado y no se realizó ningún cargo.',
|
||||
'payment_canceled_hint' => 'Puede iniciar un nuevo intento de pago en cualquier momento.',
|
||||
'payment_error' => 'Pago fallido',
|
||||
'payment_error_description' => 'Lamentablemente, el pago no pudo completarse.',
|
||||
'payment_error_hint' => 'Por favor, revise sus datos de pago e inténtelo de nuevo — o elija otro método de pago.',
|
||||
'payment_error_retry' => 'Intentar de nuevo',
|
||||
'payment_error_code' => 'Código de error',
|
||||
'payment_error_what_to_do' => '¿Qué puedo hacer?',
|
||||
'payment_unknown_status' => 'Estado de pago desconocido',
|
||||
'payment_unknown_status_description' => 'No se pudo determinar el estado del pago. Por favor contáctenos para más información.',
|
||||
'contact_support_if_needed' => 'Si tiene alguna pregunta, por favor contacte a nuestro servicio de atención al cliente.',
|
||||
'contact_support_if_needed' => 'Si tiene más preguntas, por favor contacte a nuestro servicio de atención al cliente.',
|
||||
'try_again' => 'Intentar de nuevo',
|
||||
'choose_different_payment' => 'Elegir otro método de pago',
|
||||
'nothing_was_charged' => 'No se ha realizado ningún cargo en su cuenta.',
|
||||
'payment_error_reasons' => [
|
||||
'card_expired' => 'Su tarjeta ha caducado. Por favor use una tarjeta válida o elija otro método de pago.',
|
||||
'card_blocked' => 'Su tarjeta está bloqueada. Por favor contacte a su banco o elija otro método de pago.',
|
||||
'card_invalid' => 'Los datos de la tarjeta son inválidos. Por favor verifique su entrada.',
|
||||
'card_declined' => 'Su banco rechazó el pago. Por favor contacte a su banco o elija otro método de pago.',
|
||||
'insufficient_funds' => 'El límite de la tarjeta fue superado. Por favor contacte a su banco o elija otro método de pago.',
|
||||
'cvv_invalid' => 'El código de seguridad (CVV) es incorrecto. Por favor verifique los 3 dígitos en el reverso de su tarjeta.',
|
||||
'3ds_failed' => 'La autenticación 3D Secure falló. Por favor inténtelo de nuevo o elija otro método de pago.',
|
||||
'timeout' => 'La conexión con el banco se interrumpió (tiempo de espera). Por favor inténtelo de nuevo en unos minutos.',
|
||||
'fraud' => 'El pago fue rechazado por razones de seguridad. Por favor contacte a su banco.',
|
||||
'general' => 'Por favor verifique sus datos de pago e inténtelo de nuevo. Si el problema persiste, elija otro método de pago.',
|
||||
],
|
||||
|
||||
// DHL Packstation/Paketbox
|
||||
'packstation_delivery' => 'Entrega a Packstation/Paketbox',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<div class="timeline">
|
||||
@forelse($incident->activities as $activity)
|
||||
<div class="timeline-item {{ $activity->type === 'status_change' ? 'timeline-item-secondary' : '' }}">
|
||||
<div class="timeline-indicator bg-{{ match($activity->type) {
|
||||
'status_change' => 'info',
|
||||
'email' => 'primary',
|
||||
'call' => 'success',
|
||||
'ticket' => 'warning',
|
||||
'provider_response' => 'secondary',
|
||||
default => 'light border'
|
||||
} }}">
|
||||
<i class="ion {{ $activity->type_icon }}"></i>
|
||||
</div>
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<span class="badge badge-light">{{ $activity->type_label }}</span>
|
||||
<strong class="ml-1">{{ $activity->title }}</strong>
|
||||
</div>
|
||||
<small class="text-muted text-nowrap ml-2">
|
||||
{{ $activity->created_at->format('d.m.Y H:i') }}
|
||||
— {{ $activity->author }}
|
||||
</small>
|
||||
</div>
|
||||
@if($activity->content)
|
||||
<div class="mt-1 text-muted">{{ $activity->content }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-muted">Noch keine Aktivitäten.</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.timeline { position: relative; padding-left: 2.5rem; }
|
||||
.timeline::before { content: ''; position: absolute; left: 1rem; top: 0; bottom: 0; width: 2px; background: #e9ecef; }
|
||||
.timeline-item { position: relative; margin-bottom: 1.25rem; }
|
||||
.timeline-indicator {
|
||||
position: absolute; left: -2.5rem; width: 2rem; height: 2rem;
|
||||
border-radius: 50%; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.875rem; color: #fff;
|
||||
}
|
||||
.timeline-content { background: #f8f9fa; border-radius: 0.375rem; padding: 0.75rem 1rem; }
|
||||
</style>
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<div class="modal fade" id="createIncidentModal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Neuen Incident anlegen</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('admin.payment-dashboard.store') }}">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8">
|
||||
<label>Titel <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" class="form-control" value="{{ old('title') }}" required>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Erkannt am <span class="text-danger">*</span></label>
|
||||
<input type="datetime-local" name="detected_at" class="form-control"
|
||||
value="{{ old('detected_at', now()->format('Y-m-d\TH:i')) }}" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label>Anbieter <span class="text-danger">*</span></label>
|
||||
<select name="provider" class="custom-select" required>
|
||||
<option value="payone" {{ old('provider', 'payone') === 'payone' ? 'selected' : '' }}>PAYONE</option>
|
||||
{{-- <option value="stripe" {{ old('provider') === 'stripe' ? 'selected' : '' }}>Stripe</option> --}}
|
||||
<option value="paypal" {{ old('provider') === 'paypal' ? 'selected' : '' }}>PayPal</option>
|
||||
{{-- <option value="mollie" {{ old('provider') === 'mollie' ? 'selected' : '' }}>Mollie</option> --}}
|
||||
<option value="other" {{ old('provider') === 'other' ? 'selected' : '' }}>Sonstige</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Typ <span class="text-danger">*</span></label>
|
||||
<select name="type" class="custom-select" required>
|
||||
<option value="outage" {{ old('type') === 'outage' ? 'selected' : '' }}>Ausfall</option>
|
||||
<option value="ipn_error" {{ old('type') === 'ipn_error' ? 'selected' : '' }}>IPN-Fehler</option>
|
||||
<option value="payment_failure" {{ old('type', 'payment_failure') === 'payment_failure' ? 'selected' : '' }}>Zahlungsfehler</option>
|
||||
<option value="slow_response" {{ old('type') === 'slow_response' ? 'selected' : '' }}>Langsame Antwort</option>
|
||||
<option value="other" {{ old('type') === 'other' ? 'selected' : '' }}>Sonstiges</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Schwere <span class="text-danger">*</span></label>
|
||||
<select name="severity" class="custom-select" required>
|
||||
<option value="low" {{ old('severity') === 'low' ? 'selected' : '' }}>Niedrig</option>
|
||||
<option value="medium" {{ old('severity', 'medium') === 'medium' ? 'selected' : '' }}>Mittel</option>
|
||||
<option value="high" {{ old('severity') === 'high' ? 'selected' : '' }}>Hoch</option>
|
||||
<option value="critical" {{ old('severity') === 'critical' ? 'selected' : '' }}>Kritisch</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-4">
|
||||
<label>Betroffene Bestellungen</label>
|
||||
<input type="number" name="affected_orders" class="form-control" min="0"
|
||||
value="{{ old('affected_orders', 0) }}">
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Betroffener Umsatz (€)</label>
|
||||
<input type="number" name="affected_revenue" class="form-control" min="0" step="0.01"
|
||||
value="{{ old('affected_revenue', '0.00') }}">
|
||||
</div>
|
||||
<div class="form-group col-md-4">
|
||||
<label>Ticket-Nummer</label>
|
||||
<input type="text" name="ticket_number" class="form-control"
|
||||
value="{{ old('ticket_number') }}" placeholder="z.B. PAYONE-12345">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Beschreibung</label>
|
||||
<textarea name="description" class="form-control" rows="3"
|
||||
placeholder="Was ist passiert?">{{ old('description') }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Abbrechen</button>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="ion ion-md-alert"></i> Incident anlegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Schwere</th>
|
||||
<th>Titel</th>
|
||||
<th>Anbieter</th>
|
||||
<th>Typ</th>
|
||||
<th>Status</th>
|
||||
<th>Erkannt</th>
|
||||
<th>Dauer</th>
|
||||
@if(isset($showActions) && $showActions)
|
||||
<th></th>
|
||||
@endif
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($incidents as $incident)
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge badge-{{ $incident->severity_color }}">
|
||||
{{ $incident->severity_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ route('admin.payment-dashboard.show', $incident) }}">
|
||||
{{ $incident->title }}
|
||||
</a>
|
||||
@if($incident->ticket_number)
|
||||
<span class="text-muted small ml-1">#{{ $incident->ticket_number }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><span class="badge badge-secondary">{{ $incident->provider_label }}</span></td>
|
||||
<td>
|
||||
<i class="ion {{ $incident->type_icon }}"></i>
|
||||
{{ $incident->type_label }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-{{ $incident->status_color }}">
|
||||
{{ $incident->status_label }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-nowrap">{{ $incident->detected_at->format('d.m.Y H:i') }}</td>
|
||||
<td class="text-nowrap">{{ $incident->duration }}</td>
|
||||
@if(isset($showActions) && $showActions)
|
||||
<td>
|
||||
<div class="d-flex gap-1">
|
||||
<form method="POST" action="{{ route('admin.payment-dashboard.status.update', $incident) }}" class="d-inline">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<select name="status" class="custom-select custom-select-sm" onchange="this.form.submit()" style="width:auto">
|
||||
@foreach(['open' => 'Offen', 'in_progress' => 'In Bearb.', 'waiting_provider' => 'Wartet', 'resolved' => 'Gelöst', 'closed' => 'Geschl.'] as $value => $label)
|
||||
<option value="{{ $value }}" {{ $incident->status === $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
<a href="{{ route('admin.payment-dashboard.show', $incident) }}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="ion ion-md-open"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
@endif
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted py-3">Keine Incidents vorhanden.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<div class="row mb-4">
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card {{ $stats['critical_open'] > 0 ? 'border-danger' : '' }}">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Offene Incidents</div>
|
||||
<div class="display-4 font-weight-bold {{ $stats['open_incidents'] > 0 ? 'text-danger' : 'text-success' }}">
|
||||
{{ $stats['open_incidents'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">In Bearbeitung</div>
|
||||
<div class="display-4 font-weight-bold {{ $stats['in_progress'] > 0 ? 'text-warning' : 'text-muted' }}">
|
||||
{{ $stats['in_progress'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">PAYONE (30 Tage)</div>
|
||||
<div class="display-4 font-weight-bold text-info">
|
||||
{{ $stats['payone_incidents_30d'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Gelöst (Monat)</div>
|
||||
<div class="display-4 font-weight-bold text-success">
|
||||
{{ $stats['resolved_this_month'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Erfolgsrate Zahlung</div>
|
||||
<div class="display-4 font-weight-bold {{ $transactionStats['success_rate'] < 90 ? 'text-warning' : 'text-success' }}">
|
||||
{{ $transactionStats['success_rate'] }}%
|
||||
</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">letzte {{ $transactionStats['days'] }} Tage</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-2">
|
||||
<div class="card {{ $transactionStats['failed'] > 0 ? 'border-warning' : '' }}">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Fehlgeschlagen</div>
|
||||
<div class="display-4 font-weight-bold {{ $transactionStats['failed'] > 0 ? 'text-danger' : 'text-success' }}">
|
||||
{{ $transactionStats['failed'] }}
|
||||
</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">letzte {{ $transactionStats['days'] }} Tage</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
457
resources/views/admin/payment-dashboard/abandoned.blade.php
Normal file
457
resources/views/admin/payment-dashboard/abandoned.blade.php
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
<strong>Abbruch-Analyse</strong>
|
||||
<small class="text-muted ml-2">Nicht gestartete, abgebrochene und technisch fehlerhafte Zahlungen</small>
|
||||
</div>
|
||||
{{-- Zeitraum-Filter --}}
|
||||
<form method="GET" class="form-inline">
|
||||
<label class="mr-2 text-muted small">Zeitraum:</label>
|
||||
<select name="days" class="form-control form-control-sm mr-2" onchange="this.form.submit()">
|
||||
@foreach ([7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 60 => '60 Tage', 90 => '90 Tage', 0 => 'Alle'] as $value => $label)
|
||||
<option value="{{ $value }}" {{ $days == $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Stat-Karten --}}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-warning">
|
||||
<div class="card-body text-center py-3">
|
||||
<div class="text-muted small mb-1">Zahlung nie gestartet</div>
|
||||
<div class="h3 font-weight-bold mb-0 text-warning">{{ $abandonedStats['no_payment'] }}</div>
|
||||
<small class="text-muted">Orders mit txaction=prev ohne Payment</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-danger">
|
||||
<div class="card-body text-center py-3">
|
||||
<div class="text-muted small mb-1">Abgebrochen / Fehler</div>
|
||||
<div class="h3 font-weight-bold mb-0 text-danger">{{ $abandonedStats['cancelled'] }}</div>
|
||||
<small class="text-muted">cancel + error Payments</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card border-secondary">
|
||||
<div class="card-body text-center py-3">
|
||||
<div class="text-muted small mb-1">Kein PAYONE-Callback</div>
|
||||
<div class="h3 font-weight-bold mb-0 text-secondary">{{ $abandonedStats['no_callback'] }}</div>
|
||||
<small class="text-muted">Payments ohne Transaktion (>2h)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tabs --}}
|
||||
<ul class="nav nav-tabs mb-0" id="abandonedTabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="tab-no-payment" data-toggle="tab" href="#no-payment" role="tab">
|
||||
Nie gestartet
|
||||
@if ($abandonedStats['no_payment'] > 0)
|
||||
<span class="badge badge-warning ml-1">{{ $abandonedStats['no_payment'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab-cancelled" data-toggle="tab" href="#cancelled" role="tab">
|
||||
Abgebrochen / Fehler
|
||||
@if ($abandonedStats['cancelled'] > 0)
|
||||
<span class="badge badge-danger ml-1">{{ $abandonedStats['cancelled'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="tab-no-callback" data-toggle="tab" href="#no-callback" role="tab">
|
||||
Kein Callback
|
||||
@if ($abandonedStats['no_callback'] > 0)
|
||||
<span class="badge badge-secondary ml-1">{{ $abandonedStats['no_callback'] }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content border border-top-0 rounded-bottom bg-white px-3 pt-3 pb-2 mb-4">
|
||||
|
||||
{{-- Tab 1: Orders ohne Payment --}}
|
||||
<div class="tab-pane fade show active" id="no-payment" role="tabpanel">
|
||||
<p class="text-muted small mt-1 mb-3">
|
||||
Bestellungen, bei denen der Benutzer den Checkout-Prozess zwar abgeschlossen hat (txaction=prev),
|
||||
aber die Zahlung nie initiiert wurde. Mindestens 30 Minuten alt.
|
||||
</p>
|
||||
@if ($ordersWithoutPayment->isEmpty())
|
||||
<div class="alert alert-success">Keine offenen Bestellungen ohne Zahlung im gewählten Zeitraum.</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Order-ID</th>
|
||||
<th>Kunde / Berater</th>
|
||||
<th>Typ</th>
|
||||
<th>Betrag</th>
|
||||
<th>Erstellt</th>
|
||||
<th>Vor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($ordersWithoutPayment as $order)
|
||||
@php
|
||||
$isConsultant = $order->auth_user_id && $order->auth_user;
|
||||
if ($isConsultant) {
|
||||
$name = trim(
|
||||
($order->auth_user->firstname ?? '') .
|
||||
' ' .
|
||||
($order->auth_user->lastname ?? ''),
|
||||
);
|
||||
$email = $order->auth_user->email ?? '–';
|
||||
} else {
|
||||
$name = trim(
|
||||
($order->shopping_user->billing_firstname ?? '') .
|
||||
' ' .
|
||||
($order->shopping_user->billing_lastname ?? ''),
|
||||
);
|
||||
$email = $order->shopping_user->billing_email ?? '–';
|
||||
}
|
||||
@endphp
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ $order->auth_user_id ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
|
||||
target="_blank" class="text-monospace">
|
||||
#{{ $order->id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@if ($isConsultant)
|
||||
<span class="badge badge-primary badge-sm mr-1">Berater</span>
|
||||
@else
|
||||
<span class="badge badge-info badge-sm mr-1">Kunde</span>
|
||||
@endif
|
||||
<strong>{{ $name ?: '–' }}</strong>
|
||||
<br><small class="text-muted">{{ $email }}</small>
|
||||
</td>
|
||||
<td><small>{{ $order->payment_for ?? '–' }}</small></td>
|
||||
<td class="font-weight-bold">
|
||||
{{ $order->price_total ? number_format($order->price_total, 2, ',', '.') . ' €' : '–' }}
|
||||
</td>
|
||||
<td class="text-muted small">
|
||||
{{ $order->created_at ? $order->created_at->format('d.m.Y H:i') : '–' }}</td>
|
||||
<td class="text-muted small">
|
||||
{{ $order->created_at ? $order->created_at->diffForHumans() : '–' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ $ordersWithoutPayment->links() }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Tab 2: Abgebrochene / Fehler --}}
|
||||
<div class="tab-pane fade" id="cancelled" role="tabpanel">
|
||||
<p class="text-muted small mt-1 mb-3">
|
||||
Zahlungen, bei denen der Nutzer aktiv abgebrochen hat (<code>cancel</code>) oder bei denen PAYONE
|
||||
einen Fehler zurückgemeldet hat (<code>error</code>). Zeile anklicken für PAYONE-Fehlerdetails.
|
||||
</p>
|
||||
@if ($cancelledPayments->isEmpty())
|
||||
<div class="alert alert-success">Keine abgebrochenen Zahlungen im gewählten Zeitraum.</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width:1rem"></th>
|
||||
<th>Referenz</th>
|
||||
<th>Order-ID</th>
|
||||
<th>Kunde / Berater</th>
|
||||
<th>Betrag</th>
|
||||
<th>Status</th>
|
||||
<th>Zahlungsart</th>
|
||||
<th>Zeitpunkt</th>
|
||||
<th>Vor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($cancelledPayments as $payment)
|
||||
@php
|
||||
$order = $payment->shopping_order;
|
||||
$isConsultant = $order && $order->auth_user_id && $order->auth_user;
|
||||
if ($order && $isConsultant) {
|
||||
$name = trim(
|
||||
($order->auth_user->firstname ?? '') .
|
||||
' ' .
|
||||
($order->auth_user->lastname ?? ''),
|
||||
);
|
||||
$email = $order->auth_user->email ?? '–';
|
||||
} elseif ($order && $order->shopping_user) {
|
||||
$name = trim(
|
||||
($order->shopping_user->billing_firstname ?? '') .
|
||||
' ' .
|
||||
($order->shopping_user->billing_lastname ?? ''),
|
||||
);
|
||||
$email = $order->shopping_user->billing_email ?? '–';
|
||||
} else {
|
||||
$name = '–';
|
||||
$email = '–';
|
||||
}
|
||||
$hasTransactions = $payment->payment_transactions->isNotEmpty();
|
||||
$collapseId = 'cancelled-tx-' . $payment->id;
|
||||
@endphp
|
||||
{{-- Hauptzeile --}}
|
||||
<tr class="{{ $hasTransactions ? 'cursor-pointer' : '' }}"
|
||||
@if($hasTransactions) data-toggle="collapse" data-target="#{{ $collapseId }}" @endif>
|
||||
<td class="text-center text-muted">
|
||||
@if($hasTransactions)
|
||||
<i class="ion ion-md-chevron-forward toggle-icon" style="font-size:0.85rem"></i>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-monospace small">{{ $payment->reference }}</td>
|
||||
<td>
|
||||
@if ($order)
|
||||
<a href="{{ $isConsultant ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
|
||||
target="_blank" onclick="event.stopPropagation()">#{{ $order->id }}</a>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($order)
|
||||
@if ($isConsultant)
|
||||
<span class="badge badge-primary badge-sm mr-1">Berater</span>
|
||||
@else
|
||||
<span class="badge badge-info badge-sm mr-1">Kunde</span>
|
||||
@endif
|
||||
<strong>{{ $name ?: '–' }}</strong>
|
||||
<br><small class="text-muted">{{ $email }}</small>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="font-weight-bold">
|
||||
{{ $payment->amount ? number_format($payment->amount / 100, 2, ',', '.') . ' €' : '–' }}
|
||||
</td>
|
||||
<td>
|
||||
@if ($payment->status === 'cancel')
|
||||
<span class="badge badge-warning">Abgebrochen</span>
|
||||
@elseif($payment->status === 'error')
|
||||
<span class="badge badge-danger">Fehler</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">{{ $payment->status }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small">{{ $payment->payment_type ?? '–' }}</td>
|
||||
<td class="text-muted small">
|
||||
{{ $payment->created_at ? $payment->created_at->format('d.m.Y H:i') : '–' }}</td>
|
||||
<td class="text-muted small">
|
||||
{{ $payment->created_at ? $payment->created_at->diffForHumans() : '–' }}</td>
|
||||
</tr>
|
||||
|
||||
{{-- Aufklappbare Fehlerdetails --}}
|
||||
@if($hasTransactions)
|
||||
<tr class="collapse" id="{{ $collapseId }}">
|
||||
<td colspan="9" class="p-0">
|
||||
<div class="bg-light border-top border-bottom px-3 py-2">
|
||||
<small class="text-muted font-weight-bold d-block mb-2">
|
||||
PAYONE-Transaktionen ({{ $payment->payment_transactions->count() }})
|
||||
</small>
|
||||
@foreach($payment->payment_transactions as $tx)
|
||||
<div class="card card-body p-2 mb-2 {{ $tx->errorcode ? 'border-danger' : 'border-secondary' }}" style="font-size:0.78rem">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<span class="text-muted">TX-ID:</span>
|
||||
<strong>{{ $tx->txid ?? '–' }}</strong><br>
|
||||
<span class="text-muted">Action:</span>
|
||||
<code>{{ $tx->txaction ?? '–' }}</code><br>
|
||||
<span class="text-muted">Request:</span>
|
||||
<code>{{ $tx->request ?? '–' }}</code><br>
|
||||
<span class="text-muted">Status:</span>
|
||||
@if($tx->status === 'approved')
|
||||
<span class="badge badge-success">approved</span>
|
||||
@elseif($tx->status === 'error')
|
||||
<span class="badge badge-danger">error</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">{{ $tx->status ?? '–' }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@php
|
||||
$errorcode = $tx->errorcode
|
||||
?? ($tx->transmitted_data['errorcode'] ?? null);
|
||||
$failedcause = $tx->transmitted_data['failedcause'] ?? null;
|
||||
$errormessage = $tx->errormessage
|
||||
?? ($tx->transmitted_data['errormessage'] ?? null);
|
||||
$customermessage = $tx->customermessage
|
||||
?? ($tx->transmitted_data['customermessage'] ?? null);
|
||||
$description = $tx->error_description;
|
||||
@endphp
|
||||
@if($errorcode)
|
||||
<span class="text-danger font-weight-bold">
|
||||
<i class="ion ion-md-warning"></i>
|
||||
Fehlercode {{ $errorcode }}
|
||||
</span><br>
|
||||
@if($description)
|
||||
<span class="font-weight-bold text-dark">{{ $description }}</span><br>
|
||||
@endif
|
||||
@if($errormessage)
|
||||
<span class="text-muted">PAYONE-Meldung:</span>
|
||||
<span class="text-danger">{{ $errormessage }}</span><br>
|
||||
@endif
|
||||
@if($failedcause && $failedcause != '-'.$errorcode)
|
||||
<span class="text-muted">Ursache:</span>
|
||||
<code>{{ $failedcause }}</code><br>
|
||||
@endif
|
||||
@if($customermessage)
|
||||
<span class="text-muted">Kundennachricht:</span>
|
||||
<em>{{ $customermessage }}</em>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted font-italic">
|
||||
@if($tx->txaction === 'failed')
|
||||
Fehlercode nicht übermittelt
|
||||
<br><small>(txaction=failed ohne Fehlercode)</small>
|
||||
@elseif($tx->status === 'REDIRECT')
|
||||
Nutzer zu PAYONE weitergeleitet
|
||||
<br><small>(kein Fehler, Redirect)</small>
|
||||
@else
|
||||
Kein Fehlercode in diesem Callback
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<span class="text-muted">Modus:</span>
|
||||
@if($tx->mode === 'test')
|
||||
<span class="badge badge-warning">TEST</span>
|
||||
@elseif($tx->mode === 'live')
|
||||
<span class="badge badge-success">LIVE</span>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
<br>
|
||||
<span class="text-muted">Zeitpunkt:</span>
|
||||
{{ $tx->created_at ? $tx->created_at->format('d.m.Y H:i:s') : '–' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ $cancelledPayments->links() }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Tab 3: Kein Callback --}}
|
||||
<div class="tab-pane fade" id="no-callback" role="tabpanel">
|
||||
<p class="text-muted small mt-1 mb-3">
|
||||
Zahlungen, die gestartet wurden (PAYONE-Redirect), aber nach mehr als 2 Stunden
|
||||
weder einen Callback noch eine Nutzer-Rückkehr registriert haben.
|
||||
Dies kann auf technische Probleme (Timeout, fehlgeschlagene Weiterleitung) hinweisen.
|
||||
</p>
|
||||
@if ($pendingPayments->isEmpty())
|
||||
<div class="alert alert-success">Keine offenen Zahlungen ohne Callback im gewählten Zeitraum.</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Referenz</th>
|
||||
<th>Order-ID</th>
|
||||
<th>Kunde / Berater</th>
|
||||
<th>Betrag</th>
|
||||
<th>Zahlungsart</th>
|
||||
<th>Modus</th>
|
||||
<th>Gestartet</th>
|
||||
<th>Vor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($pendingPayments as $payment)
|
||||
@php
|
||||
$order = $payment->shopping_order;
|
||||
$isConsultant = $order && $order->auth_user_id && $order->auth_user;
|
||||
if ($order && $isConsultant) {
|
||||
$name = trim(
|
||||
($order->auth_user->firstname ?? '') .
|
||||
' ' .
|
||||
($order->auth_user->lastname ?? ''),
|
||||
);
|
||||
$email = $order->auth_user->email ?? '–';
|
||||
} elseif ($order && $order->shopping_user) {
|
||||
$name = trim(
|
||||
($order->shopping_user->billing_firstname ?? '') .
|
||||
' ' .
|
||||
($order->shopping_user->billing_lastname ?? ''),
|
||||
);
|
||||
$email = $order->shopping_user->billing_email ?? '–';
|
||||
} else {
|
||||
$name = '–';
|
||||
$email = '–';
|
||||
}
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="text-monospace small">{{ $payment->reference }}</td>
|
||||
<td>
|
||||
@if ($order)
|
||||
<a href="{{ $isConsultant ? route('admin_sales_users_detail', $order->id) : route('admin_sales_customers_detail', $order->id) }}"
|
||||
target="_blank">#{{ $order->id }}</a>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($order)
|
||||
@if ($isConsultant)
|
||||
<span class="badge badge-primary badge-sm mr-1">Berater</span>
|
||||
@else
|
||||
<span class="badge badge-info badge-sm mr-1">Kunde</span>
|
||||
@endif
|
||||
<strong>{{ $name ?: '–' }}</strong>
|
||||
<br><small class="text-muted">{{ $email }}</small>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="font-weight-bold">
|
||||
{{ $payment->amount ? number_format($payment->amount / 100, 2, ',', '.') . ' €' : '–' }}
|
||||
</td>
|
||||
<td class="small">{{ $payment->payment_type ?? '–' }}</td>
|
||||
<td>
|
||||
@if (($payment->mode ?? '') === 'test')
|
||||
<span class="badge badge-warning">TEST</span>
|
||||
@elseif(($payment->mode ?? '') === 'live')
|
||||
<span class="badge badge-success">LIVE</span>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-muted small">
|
||||
{{ $payment->created_at ? $payment->created_at->format('d.m.Y H:i') : '–' }}</td>
|
||||
<td class="text-muted small">
|
||||
{{ $payment->created_at ? $payment->created_at->diffForHumans() : '–' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ $pendingPayments->links() }}
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
302
resources/views/admin/payment-dashboard/funnel.blade.php
Normal file
302
resources/views/admin/payment-dashboard/funnel.blade.php
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 flex-wrap">
|
||||
<div class="mb-2">
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
<strong>Checkout-Funnel Tracking</strong>
|
||||
<small class="text-muted ml-2">Internes Tracking aller Checkout-Schritte</small>
|
||||
</div>
|
||||
<form method="GET" class="form-inline">
|
||||
<label class="mr-2 text-muted small">Zeitraum:</label>
|
||||
<select name="days" class="form-control form-control-sm" onchange="this.form.submit()">
|
||||
@foreach ([1 => 'Heute', 7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 60 => '60 Tage', 90 => '90 Tage', 0 => 'Alle'] as $value => $label)
|
||||
<option value="{{ $value }}" {{ $days == $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@php $topCount = $funnelSteps[0]['count'] > 0 ? $funnelSteps[0]['count'] : 1; @endphp
|
||||
|
||||
<div class="row">
|
||||
{{-- ── Funnel ──────────────────────────────────────────────────────── --}}
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><strong>Checkout-Funnel</strong></div>
|
||||
<div class="card-body">
|
||||
@foreach ($funnelSteps as $i => $step)
|
||||
@php $barWidth = $topCount > 0 ? round($step['count'] / $topCount * 100) : 0; @endphp
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<div>
|
||||
<span class="badge badge-secondary mr-1">{{ $i + 1 }}</span>
|
||||
<strong>{{ $step['label'] }}</strong>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<span class="font-weight-bold">{{ number_format($step['count'], 0, ',', '.') }}</span>
|
||||
@if ($step['conversion'] !== null)
|
||||
<span
|
||||
class="ml-2 small {{ $step['conversion'] >= 70 ? 'text-success' : ($step['conversion'] >= 40 ? 'text-warning' : 'text-danger') }}">
|
||||
↓ {{ $step['conversion'] }}%
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress" style="height:20px;">
|
||||
<div class="progress-bar {{ $i === 0 ? 'bg-primary' : ($i === count($funnelSteps) - 1 ? 'bg-success' : 'bg-info') }}"
|
||||
style="width:{{ max($barWidth, $step['count'] > 0 ? 2 : 0) }}%" role="progressbar">
|
||||
@if ($barWidth > 8)
|
||||
{{ $barWidth }}%
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@php
|
||||
$totalConversion =
|
||||
$funnelSteps[0]['count'] > 0
|
||||
? round(($funnelSteps[4]['count'] / $funnelSteps[0]['count']) * 100, 1)
|
||||
: 0;
|
||||
@endphp
|
||||
<div
|
||||
class="alert {{ $totalConversion >= 50 ? 'alert-success' : ($totalConversion >= 25 ? 'alert-warning' : 'alert-danger') }} mt-2 mb-0 py-2">
|
||||
<strong>Gesamt-Konversionsrate: {{ $totalConversion }}%</strong>
|
||||
<small class="text-muted ml-2">(Checkout aufgerufen → Zahlung bestätigt)</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Rechte Spalte ────────────────────────────────────────────────── --}}
|
||||
<div class="col-lg-4">
|
||||
|
||||
{{-- Rückkehr-Status --}}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><strong>Rückkehr von PAYONE</strong></div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
@forelse($returnStats as $status => $count)
|
||||
<tr>
|
||||
<td>
|
||||
@if ($status === 'success')
|
||||
<span class="badge badge-success">success</span>
|
||||
@elseif($status === 'cancel')
|
||||
<span class="badge badge-warning">cancel</span>
|
||||
@elseif($status === 'error')
|
||||
<span class="badge badge-danger">error</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">{{ $status ?? '?' }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-right font-weight-bold">{{ number_format($count, 0, ',', '.') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="2" class="text-muted text-center py-3 small">Noch keine Daten</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Quell-Kanal --}}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><strong>Quelle (Checkout-Aufrufe)</strong></div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm mb-0">
|
||||
<tbody>
|
||||
@forelse($sourceBreakdown as $key => $source)
|
||||
<tr>
|
||||
<td style="font-size:0.82rem">
|
||||
@if ($key === 'kundenshop')
|
||||
<i class="ion ion-md-cart text-success mr-1"></i>
|
||||
@elseif($key === 'salescenter')
|
||||
<i class="ion ion-md-briefcase text-primary mr-1"></i>
|
||||
@elseif($key === 'beraterzugang')
|
||||
<i class="ion ion-md-person text-info mr-1"></i>
|
||||
@elseif($key === 'testserver')
|
||||
<i class="ion ion-md-flask text-warning mr-1"></i>
|
||||
@else
|
||||
<i class="ion ion-md-help text-muted mr-1"></i>
|
||||
@endif
|
||||
{{ $source['label'] }}
|
||||
</td>
|
||||
<td class="text-right font-weight-bold">
|
||||
{{ number_format($source['count'], 0, ',', '.') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="2" class="text-muted text-center py-3 small">Noch keine Daten</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Ereignisse (gefiltert + paginiert) ─────────────────────────────────── --}}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center flex-wrap">
|
||||
<strong>Ereignisse</strong>
|
||||
{{-- Filter-Leiste --}}
|
||||
<form method="GET" class="form-inline mt-1 mt-md-0">
|
||||
<input type="hidden" name="days" value="{{ $days }}">
|
||||
<select name="event" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||||
<option value="">Alle Ereignisse</option>
|
||||
@foreach (\App\Models\CheckoutFunnelEvent::eventLabels() as $val => $label)
|
||||
<option value="{{ $val }}" {{ $filterEvent == $val ? 'selected' : '' }}>
|
||||
{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="return_status" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||||
<option value="">Alle Status</option>
|
||||
<option value="success" {{ $filterStatus == 'success' ? 'selected' : '' }}>success</option>
|
||||
<option value="cancel" {{ $filterStatus == 'cancel' ? 'selected' : '' }}>cancel</option>
|
||||
<option value="error" {{ $filterStatus == 'error' ? 'selected' : '' }}>error</option>
|
||||
</select>
|
||||
<select name="source" class="form-control form-control-sm mr-1" onchange="this.form.submit()">
|
||||
<option value="">Alle Quellen</option>
|
||||
@foreach (\App\Models\CheckoutFunnelEvent::sourceLabels() as $val => $label)
|
||||
<option value="{{ $val }}" {{ $filterSource == $val ? 'selected' : '' }}>
|
||||
{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@if ($filterEvent || $filterStatus || $filterSource)
|
||||
<a href="{{ route('admin.payment-dashboard.funnel', ['days' => $days]) }}"
|
||||
class="btn btn-sm btn-outline-secondary">✕ Reset</a>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Zeitpunkt</th>
|
||||
<th>Ereignis</th>
|
||||
<th>Quelle</th>
|
||||
<th>Domain</th>
|
||||
<th>Berater</th>
|
||||
<th>Order-ID</th>
|
||||
<th>Zahlungsart</th>
|
||||
<th>Betrag</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($recentEvents as $event)
|
||||
<tr>
|
||||
<td class="text-muted small text-nowrap">
|
||||
{{ $event->created_at->format('d.m. H:i:s') }}<br>
|
||||
<small>{{ $event->created_at->diffForHumans() }}</small>
|
||||
</td>
|
||||
<td>
|
||||
@php
|
||||
$badgeClass = match ($event->event) {
|
||||
'checkout_visited' => 'badge-secondary',
|
||||
'form_submitted' => 'badge-info',
|
||||
'payment_initiated' => 'badge-primary',
|
||||
'payment_returned' => match ($event->return_status) {
|
||||
'success' => 'badge-success',
|
||||
'cancel' => 'badge-warning',
|
||||
default => 'badge-danger',
|
||||
},
|
||||
'payment_confirmed' => 'badge-success',
|
||||
default => 'badge-light',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge {{ $badgeClass }}">{{ $event->event_label }}</span>
|
||||
@if ($event->metadata && isset($event->metadata['txaction']))
|
||||
<small class="text-muted ml-1">{{ $event->metadata['txaction'] }}</small>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small">
|
||||
@php $src = $event->source_type; @endphp
|
||||
@if ($src === 'kundenshop')
|
||||
<span class="badge badge-success" title="Kundenshop">Shop</span>
|
||||
@elseif($src === 'salescenter')
|
||||
<span class="badge badge-primary" title="Salescenter">SC</span>
|
||||
@elseif($src === 'beraterzugang')
|
||||
<span class="badge badge-info" title="Beraterzugang">BZ</span>
|
||||
@elseif($src === 'testserver')
|
||||
<span class="badge badge-warning" title="Testserver">TEST</span>
|
||||
@else
|
||||
<span class="badge badge-secondary">?</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-muted small">{{ $event->domain ?? '–' }}</td>
|
||||
<td class="small">
|
||||
@if ($event->consultant)
|
||||
{{ $event->consultant->firstname }} {{ $event->consultant->lastname }}
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small">
|
||||
@if ($event->shopping_order_id)
|
||||
<a href="{{ route('admin_sales_users_detail', $event->shopping_order_id) }}"
|
||||
target="_blank">
|
||||
#{{ $event->shopping_order_id }}
|
||||
</a>
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small">{{ $event->payment_method ?? '–' }}</td>
|
||||
<td class="small">
|
||||
{{ $event->amount_cents ? number_format($event->amount_cents / 100, 2, ',', '.') . ' €' : '–' }}
|
||||
</td>
|
||||
<td class="small">
|
||||
@if ($event->return_status)
|
||||
@if ($event->return_status === 'success')
|
||||
<span class="badge badge-success">{{ $event->return_status }}</span>
|
||||
@elseif($event->return_status === 'cancel')
|
||||
<span class="badge badge-warning">{{ $event->return_status }}</span>
|
||||
@else
|
||||
<span class="badge badge-danger">{{ $event->return_status }}</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">–</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="text-center text-muted py-4">
|
||||
Keine Ereignisse gefunden.
|
||||
@if ($filterEvent || $filterStatus || $filterSource)
|
||||
<a href="{{ route('admin.payment-dashboard.funnel', ['days' => $days]) }}">Filter
|
||||
zurücksetzen</a>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@if ($recentEvents->hasPages())
|
||||
<div class="card-footer">
|
||||
{{ $recentEvents->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Tracking-Hinweis --}}
|
||||
<div class="alert alert-info small py-2">
|
||||
<i class="ion ion-md-information-circle mr-1"></i>
|
||||
<strong>Hinweis:</strong> Das Tracking ist ab dem Aktivierungszeitpunkt aktiv. Ältere Checkouts sind nicht
|
||||
enthalten.
|
||||
Schritt 5 „PAYONE Callback" wird sowohl bei synchroner Bestätigung (<code>transactionApproved</code>)
|
||||
als auch bei asynchronem IPN-Callback (<code>txaction=paid</code> und <code>appointed</code>) erfasst.
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
143
resources/views/admin/payment-dashboard/index.blade.php
Normal file
143
resources/views/admin/payment-dashboard/index.blade.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
{{ session('success') }}
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h4 class="mb-0">
|
||||
<i class="ion ion-md-alert text-danger"></i> Payment Monitor
|
||||
</h4>
|
||||
<small class="text-muted">Entwickler-Ansicht — {{ now()->format('d.m.Y H:i') }}</small>
|
||||
</div>
|
||||
<div>
|
||||
<button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#createIncidentModal">
|
||||
<i class="ion ion-md-add"></i> Neuer Incident
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stat-Karten --}}
|
||||
@include('admin.payment-dashboard._partials.stats-cards')
|
||||
|
||||
{{-- Anbieter-Status --}}
|
||||
<div class="row mb-4">
|
||||
@foreach($providerStats as $key => $provider)
|
||||
@php $uptime = $uptimeStats[$key] ?? null; @endphp
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card {{ $provider['open_incidents'] > 0 ? 'border-danger' : '' }}">
|
||||
<div class="card-body py-2 px-3">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<strong>{{ $provider['label'] }}</strong>
|
||||
<div>
|
||||
@if($uptime && $uptime['last_check'])
|
||||
@if($uptime['last_check']->is_up)
|
||||
<span class="badge badge-success mr-1" title="Letzte Prüfung: {{ $uptime['last_check']->checked_at->diffForHumans() }}">
|
||||
<i class="ion ion-md-checkmark"></i> Online
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-danger mr-1" title="{{ $uptime['last_check']->error_message }}">
|
||||
<i class="ion ion-md-close"></i> Offline
|
||||
</span>
|
||||
@endif
|
||||
@endif
|
||||
@if($provider['open_incidents'] > 0)
|
||||
<span class="badge badge-warning">{{ $provider['open_incidents'] }} Incident</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted small mt-1">{{ $provider['total_30d'] }} Incidents (30 Tage)</div>
|
||||
@if($uptime && $uptime['checks_24h'] > 0)
|
||||
<div class="text-muted" style="font-size:0.7rem">
|
||||
Uptime 24h: {{ $uptime['uptime_24h'] }}%
|
||||
@if($uptime['failures_24h'] > 0)
|
||||
— <span class="text-danger">{{ $uptime['failures_24h'] }} Ausfälle</span>
|
||||
@endif
|
||||
</div>
|
||||
@elseif($uptime)
|
||||
<div class="text-muted" style="font-size:0.7rem">Noch keine Uptime-Daten</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- Tabs --}}
|
||||
<ul class="nav nav-tabs" id="dashboardTabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#tab-open">
|
||||
Offene Incidents
|
||||
@if($openIncidents->count() > 0)
|
||||
<span class="badge badge-danger ml-1">{{ $openIncidents->count() }}</span>
|
||||
@endif
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#tab-all">Alle Incidents</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.payment-dashboard.payments') }}">
|
||||
<i class="ion ion-md-card"></i> Zahlungen & Transaktionen
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.payment-dashboard.transactions') }}">
|
||||
<i class="ion ion-md-swap"></i> Rohe Transaktionen
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.payment-dashboard.funnel') }}">
|
||||
<i class="ion ion-md-funnel"></i> Funnel-Tracking
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.payment-dashboard.abandoned') }}">
|
||||
<i class="ion ion-md-alert"></i> Abbruch-Analyse
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.payment-dashboard.logs') }}">
|
||||
<i class="ion ion-md-list"></i> PAYONE Logs
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{{-- Tab: Offene Incidents --}}
|
||||
<div class="tab-pane fade show active pt-3" id="tab-open">
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
@include('admin.payment-dashboard._partials.incident-table', [
|
||||
'incidents' => $openIncidents,
|
||||
'showActions' => true,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tab: Alle Incidents --}}
|
||||
<div class="tab-pane fade pt-3" id="tab-all">
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
@include('admin.payment-dashboard._partials.incident-table', [
|
||||
'incidents' => $allIncidents,
|
||||
'showActions' => true,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
{{ $allIncidents->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@include('admin.payment-dashboard._partials.create-incident-modal')
|
||||
|
||||
@endsection
|
||||
86
resources/views/admin/payment-dashboard/logs.blade.php
Normal file
86
resources/views/admin/payment-dashboard/logs.blade.php
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
<strong>PAYONE Log-Viewer</strong>
|
||||
</div>
|
||||
@if(count($availableDates) > 1)
|
||||
<form method="GET" action="{{ route('admin.payment-dashboard.logs') }}">
|
||||
<select name="date" class="custom-select custom-select-sm" onchange="this.form.submit()">
|
||||
@foreach($availableDates as $date)
|
||||
<option value="{{ $date }}" {{ $selectedDate === $date ? 'selected' : '' }}>
|
||||
{{ \Carbon\Carbon::parse($date)->format('d.m.Y') }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(count($entries) === 0)
|
||||
<div class="alert alert-info">
|
||||
<i class="ion ion-md-information-circle"></i>
|
||||
Keine Log-Einträge für {{ \Carbon\Carbon::parse($selectedDate)->format('d.m.Y') }} gefunden.
|
||||
@if(count($availableDates) > 0)
|
||||
Verfügbare Daten: {{ implode(', ', array_map(fn($d) => \Carbon\Carbon::parse($d)->format('d.m.Y'), $availableDates)) }}
|
||||
@else
|
||||
Der Log-Kanal <code>payone</code> hat noch keine Einträge geschrieben.
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<div class="mb-2 text-muted small">
|
||||
<i class="ion ion-md-list"></i> {{ count($entries) }} Einträge (neueste zuerst)
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="mb-3">
|
||||
<input type="text" id="logFilter" class="form-control form-control-sm"
|
||||
placeholder="Filter: Stichwort oder Fehlercode (z.B. Error:2003)..."
|
||||
oninput="filterLogs(this.value)">
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div id="logEntries">
|
||||
@foreach($entries as $entry)
|
||||
@php
|
||||
$levelColor = match($entry['level']) {
|
||||
'error' => 'danger',
|
||||
'warning' => 'warning',
|
||||
'info' => 'info',
|
||||
'notice' => 'secondary',
|
||||
default => 'secondary',
|
||||
};
|
||||
@endphp
|
||||
<div class="log-entry border-bottom px-3 py-2 {{ $entry['level'] === 'error' ? 'bg-light' : '' }}"
|
||||
data-search="{{ strtolower($entry['timestamp'] . ' ' . $entry['level'] . ' ' . $entry['message']) }}">
|
||||
<div class="d-flex align-items-start">
|
||||
<span class="badge badge-{{ $levelColor }} mr-2 mt-1 flex-shrink-0">{{ strtoupper($entry['level']) }}</span>
|
||||
<div style="min-width:0">
|
||||
<div class="text-muted small">{{ $entry['timestamp'] }}</div>
|
||||
<div class="small" style="word-break:break-all; font-family:monospace">{{ $entry['message'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<script>
|
||||
function filterLogs(term) {
|
||||
const entries = document.querySelectorAll('.log-entry');
|
||||
const search = term.toLowerCase();
|
||||
entries.forEach(el => {
|
||||
el.style.display = el.dataset.search.includes(search) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@endsection
|
||||
111
resources/views/admin/payment-dashboard/management.blade.php
Normal file
111
resources/views/admin/payment-dashboard/management.blade.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h4 class="mb-0">
|
||||
<i class="ion ion-md-alert text-danger"></i> Payment Monitor
|
||||
</h4>
|
||||
<small class="text-muted">Stand: {{ now()->format('d.m.Y H:i') }}</small>
|
||||
</div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="ion ion-md-code-working"></i> Entwickler-Ansicht
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Ampel-Karten --}}
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
@php
|
||||
$level = $stats['critical_open'] > 0 ? 'danger' : ($stats['open_incidents'] > 0 ? 'warning' : 'success');
|
||||
$levelText = $stats['critical_open'] > 0 ? 'Kritische Störung!' : ($stats['open_incidents'] > 0 ? 'Offene Störungen' : 'Alles in Ordnung');
|
||||
@endphp
|
||||
<div class="card border-{{ $level }}">
|
||||
<div class="card-body text-center py-4">
|
||||
<i class="ion ion-md-{{ $level === 'success' ? 'checkmark-circle' : 'alert' }} text-{{ $level }}" style="font-size: 3rem"></i>
|
||||
<h5 class="mt-2 text-{{ $level }}">{{ $levelText }}</h5>
|
||||
<div class="display-4 font-weight-bold">{{ $stats['open_incidents'] }}</div>
|
||||
<div class="text-muted">Offene Störungen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card {{ $transactionStats['failed'] > 0 ? 'border-warning' : '' }}">
|
||||
<div class="card-body text-center py-4">
|
||||
<i class="ion ion-md-card text-{{ $transactionStats['success_rate'] >= 95 ? 'success' : ($transactionStats['success_rate'] >= 80 ? 'warning' : 'danger') }}" style="font-size: 3rem"></i>
|
||||
<h5 class="mt-2">Zahlungsquote</h5>
|
||||
<div class="display-4 font-weight-bold text-{{ $transactionStats['success_rate'] >= 95 ? 'success' : ($transactionStats['success_rate'] >= 80 ? 'warning' : 'danger') }}">
|
||||
{{ $transactionStats['success_rate'] }}%
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
{{ $transactionStats['failed'] }} fehlgeschlagen (30 Tage)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-4">
|
||||
<i class="ion ion-md-cash text-info" style="font-size: 3rem"></i>
|
||||
<h5 class="mt-2">Betroffener Umsatz</h5>
|
||||
<div class="display-4 font-weight-bold text-{{ $stats['total_affected_revenue'] > 0 ? 'warning' : 'success' }}">
|
||||
{{ number_format($stats['total_affected_revenue'], 0, ',', '.') }} €
|
||||
</div>
|
||||
<div class="text-muted">bei offenen Incidents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Anbieter-Status --}}
|
||||
<div class="card mb-4">
|
||||
<h6 class="card-header">Anbieter-Status</h6>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@foreach($providerStats as $key => $provider)
|
||||
<div class="col-sm-6 col-md-3 text-center mb-3">
|
||||
<div class="h5 mb-1">{{ $provider['label'] }}</div>
|
||||
@if($provider['open_incidents'] > 0)
|
||||
<span class="badge badge-danger badge-pill" style="font-size:1rem; padding: 0.5rem 1rem">
|
||||
<i class="ion ion-md-alert"></i> {{ $provider['open_incidents'] }} Störung(en)
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-success badge-pill" style="font-size:1rem; padding: 0.5rem 1rem">
|
||||
<i class="ion ion-md-checkmark"></i> OK
|
||||
</span>
|
||||
@endif
|
||||
<div class="text-muted small mt-1">{{ $provider['total_30d'] }} Incidents (30d)</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aktive Störungen --}}
|
||||
@if($openIncidents->count() > 0)
|
||||
<div class="card mb-4 border-danger">
|
||||
<h6 class="card-header bg-danger text-white">
|
||||
<i class="ion ion-md-alert"></i> Aktive Störungen ({{ $openIncidents->count() }})
|
||||
</h6>
|
||||
<div class="card-body p-0">
|
||||
@include('admin.payment-dashboard._partials.incident-table', [
|
||||
'incidents' => $openIncidents,
|
||||
'showActions' => false,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Letzte Vorfälle --}}
|
||||
<div class="card">
|
||||
<h6 class="card-header">Letzte Vorfälle</h6>
|
||||
<div class="card-body p-0">
|
||||
@include('admin.payment-dashboard._partials.incident-table', [
|
||||
'incidents' => $recentIncidents,
|
||||
'showActions' => false,
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
328
resources/views/admin/payment-dashboard/payments.blade.php
Normal file
328
resources/views/admin/payment-dashboard/payments.blade.php
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
<strong>Zahlungs-Übersicht</strong>
|
||||
<small class="text-muted ml-2">ShoppingPayments mit Transaktionen und Bestellung</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stat-Karten --}}
|
||||
<div class="row mb-4">
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Zahlungen gesamt</div>
|
||||
<div class="h4 font-weight-bold mb-0">{{ $paymentStats['total'] }}</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">{{ $paymentStats['days'] }} Tage</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Bezahlt</div>
|
||||
<div class="h4 font-weight-bold mb-0 text-success">{{ $paymentStats['paid'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card {{ $paymentStats['failed'] > 0 ? 'border-danger' : '' }}">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Mit Fehler</div>
|
||||
<div class="h4 font-weight-bold mb-0 {{ $paymentStats['failed'] > 0 ? 'text-danger' : 'text-muted' }}">
|
||||
{{ $paymentStats['failed'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Ausstehend</div>
|
||||
<div class="h4 font-weight-bold mb-0 text-warning">{{ $paymentStats['pending'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Volumen gesamt</div>
|
||||
<div class="h5 font-weight-bold mb-0">{{ number_format($paymentStats['total_amount'], 2, ',', '.') }} €</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-xl-2">
|
||||
<div class="card {{ $paymentStats['failed_amount'] > 0 ? 'border-danger' : '' }}">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="text-muted small">Fehlvolumen</div>
|
||||
<div class="h5 font-weight-bold mb-0 {{ $paymentStats['failed_amount'] > 0 ? 'text-danger' : 'text-muted' }}">
|
||||
{{ number_format($paymentStats['failed_amount'], 2, ',', '.') }} €
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body py-2">
|
||||
<form method="GET" action="{{ route('admin.payment-dashboard.payments') }}" class="form-inline flex-wrap">
|
||||
<label class="mr-2 small">Zeitraum:</label>
|
||||
<select name="days" class="custom-select custom-select-sm mr-3 mb-1" onchange="this.form.submit()">
|
||||
@foreach([1 => 'Heute', 7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 0 => 'Alle'] as $val => $label)
|
||||
<option value="{{ $val }}" {{ (int)$days === $val ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label class="mr-2 small">Status:</label>
|
||||
<select name="txaction" class="custom-select custom-select-sm mr-3 mb-1" onchange="this.form.submit()">
|
||||
<option value="">Alle</option>
|
||||
@foreach(['paid' => 'Bezahlt', 'appointed' => 'Vorgemerkt', 'pending' => 'Ausstehend', 'failed' => 'Fehlgeschlagen'] as $val => $label)
|
||||
<option value="{{ $val }}" {{ $statusFilter === $val ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label class="mr-2 small">Modus:</label>
|
||||
<select name="mode" class="custom-select custom-select-sm mr-3 mb-1" onchange="this.form.submit()">
|
||||
<option value="">Alle</option>
|
||||
<option value="live" {{ $modeFilter === 'live' ? 'selected' : '' }}>Live</option>
|
||||
<option value="test" {{ $modeFilter === 'test' ? 'selected' : '' }}>Test</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Zahlungs-Tabelle --}}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width:30px"></th>
|
||||
<th>Payment ID</th>
|
||||
<th>Bestellung</th>
|
||||
<th>Kunde</th>
|
||||
<th>Zahlart</th>
|
||||
<th>Betrag</th>
|
||||
<th>Status</th>
|
||||
<th>Modus</th>
|
||||
<th>Transaktionen</th>
|
||||
<th>Datum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($payments as $payment)
|
||||
@php
|
||||
$hasFailed = $payment->payment_transactions->where('txaction', 'failed')->count() > 0;
|
||||
$isPaid = $payment->txaction === 'paid';
|
||||
$rowClass = $hasFailed ? 'table-danger' : ($isPaid ? 'table-success' : '');
|
||||
@endphp
|
||||
<tr class="{{ $rowClass }}">
|
||||
<td class="text-center align-middle">
|
||||
@if($payment->payment_transactions->count() > 0)
|
||||
<button class="btn btn-sm btn-link p-0 text-muted"
|
||||
data-toggle="collapse"
|
||||
data-target="#tx-{{ $payment->id }}"
|
||||
title="{{ $payment->payment_transactions->count() }} Transaktionen">
|
||||
<i class="ion ion-md-arrow-dropdown"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<code class="small">{{ $payment->reference }}</code>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
@if($payment->shopping_order)
|
||||
@php
|
||||
$isCustomerOrder = in_array($payment->shopping_order->payment_for, [6, 7]);
|
||||
$orderRoute = $isCustomerOrder
|
||||
? route('admin_sales_customers_detail', $payment->shopping_order->id)
|
||||
: route('admin_sales_users_detail', $payment->shopping_order->id);
|
||||
@endphp
|
||||
<a href="{{ $orderRoute }}" class="font-weight-bold" target="_blank">
|
||||
#{{ $payment->shopping_order->id }}
|
||||
</a>
|
||||
@if($payment->shopping_order->paid)
|
||||
<i class="ion ion-md-checkmark-circle text-success ml-1" title="Bezahlt"></i>
|
||||
@endif
|
||||
<div class="text-muted" style="font-size:0.7rem">
|
||||
{{ number_format($payment->shopping_order->total ?? 0, 2, ',', '.') }} €
|
||||
</div>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle small">
|
||||
@if($payment->shopping_order?->auth_user_id && $payment->shopping_order->auth_user)
|
||||
{{-- Berater-Bestellung --}}
|
||||
<span class="badge badge-primary mb-1">Berater</span>
|
||||
<div>{{ $payment->shopping_order->auth_user->name }}</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">
|
||||
{{ $payment->shopping_order->auth_user->email }}
|
||||
</div>
|
||||
@elseif($payment->shopping_order?->shopping_user)
|
||||
{{-- Kunden-Bestellung --}}
|
||||
@php $su = $payment->shopping_order->shopping_user; @endphp
|
||||
<span class="badge badge-info mb-1">Kunde</span>
|
||||
<div>
|
||||
{{ trim($su->billing_firstname . ' ' . $su->billing_lastname) ?: '—' }}
|
||||
</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">
|
||||
{{ $su->billing_email }}
|
||||
</div>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle small">
|
||||
{{ $payment->getPaymentType() }}
|
||||
@if($payment->clearingtype)
|
||||
<span class="badge badge-secondary ml-1">{{ $payment->clearingtype }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle text-nowrap">
|
||||
<strong>{{ number_format($payment->amount / 100, 2, ',', '.') }}</strong>
|
||||
<small class="text-muted">{{ $payment->currency }}</small>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
@php
|
||||
$txColor = match($payment->txaction) {
|
||||
'paid' => 'success',
|
||||
'failed' => 'danger',
|
||||
'appointed' => 'info',
|
||||
'pending' => 'warning',
|
||||
default => 'secondary',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge badge-{{ $txColor }}">{{ $payment->txaction ?? '—' }}</span>
|
||||
@if($hasFailed && $isPaid)
|
||||
<span class="badge badge-warning ml-1" title="Hatte fehlerhafte Transaktionen">
|
||||
<i class="ion ion-md-warning"></i>
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
@if($payment->mode)
|
||||
<span class="badge badge-{{ $payment->mode === 'test' ? 'warning' : 'light' }}">
|
||||
{{ $payment->mode }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle text-center">
|
||||
@if($payment->payment_transactions->count() > 0)
|
||||
<span class="badge badge-{{ $hasFailed ? 'danger' : 'secondary' }}">
|
||||
{{ $payment->payment_transactions->count() }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-muted">0</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="align-middle text-nowrap small text-muted">
|
||||
{{ $payment->created_at->format('d.m.Y H:i') }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{-- Aufklappbare Transaktions-Sub-Tabelle --}}
|
||||
@if($payment->payment_transactions->count() > 0)
|
||||
<tr class="collapse" id="tx-{{ $payment->id }}">
|
||||
<td colspan="10" class="p-0">
|
||||
<div class="bg-light border-bottom px-3 py-2">
|
||||
<small class="font-weight-bold text-muted text-uppercase">
|
||||
Transaktionen zu Payment #{{ $payment->id }} / Referenz {{ $payment->reference }}
|
||||
</small>
|
||||
</div>
|
||||
<table class="table table-sm table-bordered mb-0" style="background:#fafafa">
|
||||
<thead>
|
||||
<tr class="bg-light">
|
||||
<th class="pl-4">TX-ID</th>
|
||||
<th>Aktion</th>
|
||||
<th>Status</th>
|
||||
<th>Fehlercode</th>
|
||||
<th>Fehlermeldung</th>
|
||||
<th>Kundennachricht</th>
|
||||
<th>Modus</th>
|
||||
<th>Datum</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($payment->payment_transactions as $tx)
|
||||
<tr class="{{ $tx->txaction === 'failed' ? 'table-danger' : ($tx->txaction === 'paid' ? 'table-success' : '') }}">
|
||||
<td class="pl-4 small">{{ $tx->txid ?? '—' }}</td>
|
||||
<td>
|
||||
@php
|
||||
$txaColor = match($tx->txaction) {
|
||||
'paid' => 'success', 'failed' => 'danger',
|
||||
'appointed' => 'info', 'pending' => 'warning',
|
||||
default => 'secondary',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge badge-{{ $txaColor }}">{{ $tx->txaction ?? '—' }}</span>
|
||||
</td>
|
||||
<td class="small">{{ $tx->status ?? '—' }}</td>
|
||||
<td>
|
||||
@if($tx->errorcode)
|
||||
<span class="text-danger font-weight-bold">{{ $tx->errorcode }}</span>
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small text-muted" style="max-width:200px">
|
||||
{{ \Illuminate\Support\Str::limit($tx->errormessage, 60) }}
|
||||
</td>
|
||||
<td class="small text-muted" style="max-width:150px">
|
||||
{{ \Illuminate\Support\Str::limit($tx->customermessage, 50) }}
|
||||
</td>
|
||||
<td>
|
||||
@if($tx->mode)
|
||||
<span class="badge badge-{{ $tx->mode === 'test' ? 'warning' : 'light' }}">{{ $tx->mode }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-nowrap small text-muted">{{ $tx->created_at->format('d.m.Y H:i') }}</td>
|
||||
<td>
|
||||
@if($tx->transmitted_data)
|
||||
<button class="btn btn-xs btn-outline-secondary"
|
||||
data-toggle="collapse"
|
||||
data-target="#raw-{{ $tx->id }}"
|
||||
title="Rohdaten">
|
||||
<i class="ion ion-md-code"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@if($tx->transmitted_data)
|
||||
<tr class="collapse" id="raw-{{ $tx->id }}">
|
||||
<td colspan="9" class="bg-white">
|
||||
<pre class="mb-0 small" style="max-height:150px; overflow-y:auto; font-size:0.75rem">{{ json_encode($tx->transmitted_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="10" class="text-center text-muted py-4">
|
||||
Keine Zahlungen im gewählten Zeitraum gefunden.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $payments->links() }}
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
165
resources/views/admin/payment-dashboard/show.blade.php
Normal file
165
resources/views/admin/payment-dashboard/show.blade.php
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
{{ session('success') }}
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<span class="badge badge-{{ $incident->severity_color }} badge-pill px-3 py-2" style="font-size:0.9rem">
|
||||
{{ $incident->severity_label }}
|
||||
</span>
|
||||
<span class="badge badge-{{ $incident->status_color }} badge-pill px-3 py-2 ml-1" style="font-size:0.9rem">
|
||||
{{ $incident->status_label }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{{-- Linke Spalte: Details + Timeline --}}
|
||||
<div class="col-lg-8">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
<i class="ion {{ $incident->type_icon }} mr-2 text-secondary"></i>
|
||||
<h5 class="mb-0">{{ $incident->title }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Anbieter</small>
|
||||
<span class="badge badge-secondary">{{ $incident->provider_label }}</span>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Typ</small>
|
||||
{{ $incident->type_label }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Erkannt am</small>
|
||||
{{ $incident->detected_at->format('d.m.Y H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Dauer</small>
|
||||
{{ $incident->duration }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Betroffene Bestellungen</small>
|
||||
{{ $incident->affected_orders }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<small class="text-muted d-block">Betroffener Umsatz</small>
|
||||
{{ number_format($incident->affected_revenue, 2, ',', '.') }} €
|
||||
</div>
|
||||
</div>
|
||||
@if($incident->ticket_number)
|
||||
<div class="mb-3">
|
||||
<small class="text-muted d-block">Ticket-Nummer</small>
|
||||
<code>{{ $incident->ticket_number }}</code>
|
||||
</div>
|
||||
@endif
|
||||
@if($incident->description)
|
||||
<div class="mb-3">
|
||||
<small class="text-muted d-block">Beschreibung</small>
|
||||
<p class="mb-0">{{ $incident->description }}</p>
|
||||
</div>
|
||||
@endif
|
||||
@if($incident->notes)
|
||||
<div>
|
||||
<small class="text-muted d-block">Interne Notizen</small>
|
||||
<p class="mb-0 text-muted">{{ $incident->notes }}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aktivitäten-Timeline --}}
|
||||
<div class="card">
|
||||
<h6 class="card-header">Kommunikationsverlauf ({{ $incident->activities->count() }})</h6>
|
||||
<div class="card-body">
|
||||
@include('admin.payment-dashboard._partials.activity-timeline')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Rechte Spalte: Aktionen --}}
|
||||
<div class="col-lg-4">
|
||||
{{-- Status ändern --}}
|
||||
<div class="card mb-4">
|
||||
<h6 class="card-header">Status ändern</h6>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.payment-dashboard.status.update', $incident) }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="form-group">
|
||||
<select name="status" class="custom-select">
|
||||
@foreach(['open' => 'Offen', 'in_progress' => 'In Bearbeitung', 'waiting_provider' => 'Wartet auf Anbieter', 'resolved' => 'Gelöst', 'closed' => 'Geschlossen'] as $value => $label)
|
||||
<option value="{{ $value }}" {{ $incident->status === $value ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block">Status speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Aktivität hinzufügen --}}
|
||||
<div class="card">
|
||||
<h6 class="card-header">Aktivität hinzufügen</h6>
|
||||
<div class="card-body">
|
||||
@if($errors->has('type') || $errors->has('title'))
|
||||
<div class="alert alert-danger small">
|
||||
@foreach($errors->all() as $error)
|
||||
<div>{{ $error }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('admin.payment-dashboard.activity.store', $incident) }}">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<label class="small">Typ</label>
|
||||
<select name="type" class="custom-select custom-select-sm">
|
||||
<option value="note">Notiz</option>
|
||||
<option value="email">E-Mail</option>
|
||||
<option value="call">Telefonat</option>
|
||||
<option value="ticket">Ticket</option>
|
||||
<option value="provider_response">Anbieter-Antwort</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="small">Titel</label>
|
||||
<input type="text" name="title" class="form-control form-control-sm"
|
||||
placeholder="Kurze Beschreibung" value="{{ old('title') }}" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="small">Details</label>
|
||||
<textarea name="content" class="form-control form-control-sm" rows="3"
|
||||
placeholder="Inhalt der Aktivität...">{{ old('content') }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-secondary btn-block btn-sm">
|
||||
<i class="ion ion-md-add"></i> Aktivität speichern
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($incident->resolved_at)
|
||||
<div class="mt-3 text-center text-muted small">
|
||||
<i class="ion ion-md-checkmark-circle text-success"></i>
|
||||
Gelöst am {{ $incident->resolved_at->format('d.m.Y H:i') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
183
resources/views/admin/payment-dashboard/transactions.blade.php
Normal file
183
resources/views/admin/payment-dashboard/transactions.blade.php
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
@extends('layouts.layout-2')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="btn btn-sm btn-outline-secondary mr-2">
|
||||
<i class="ion ion-md-arrow-back"></i> Zurück
|
||||
</a>
|
||||
<a href="{{ route('admin.payment-dashboard.payments') }}" class="btn btn-sm btn-outline-primary mr-2">
|
||||
<i class="ion ion-md-card"></i> Zahlungen & Transaktionen
|
||||
</a>
|
||||
<strong>Rohe Transaktionen</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Stat-Karten --}}
|
||||
<div class="row mb-4">
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Gesamt</div>
|
||||
<div class="display-4 font-weight-bold">{{ $transactionStats['total'] }}</div>
|
||||
<div class="text-muted" style="font-size:0.7rem">letzte {{ $transactionStats['days'] }} Tage</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Erfolgsrate</div>
|
||||
<div class="display-4 font-weight-bold text-{{ $transactionStats['success_rate'] >= 95 ? 'success' : ($transactionStats['success_rate'] >= 80 ? 'warning' : 'danger') }}">
|
||||
{{ $transactionStats['success_rate'] }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Bezahlt</div>
|
||||
<div class="display-4 font-weight-bold text-success">{{ $transactionStats['paid'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-xl-3">
|
||||
<div class="card {{ $transactionStats['failed'] > 0 ? 'border-danger' : '' }}">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-muted small">Fehlgeschlagen</div>
|
||||
<div class="display-4 font-weight-bold {{ $transactionStats['failed'] > 0 ? 'text-danger' : 'text-muted' }}">
|
||||
{{ $transactionStats['failed'] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($transactionStats['error_distribution']->count() > 0)
|
||||
<div class="card mb-4">
|
||||
<h6 class="card-header">Fehlercodes (letzte {{ $transactionStats['days'] }} Tage)</h6>
|
||||
<div class="card-body py-2">
|
||||
@foreach($transactionStats['error_distribution'] as $error)
|
||||
<span class="badge badge-danger mr-2 mb-1" style="font-size:0.85rem; padding: 0.4rem 0.7rem">
|
||||
Code {{ $error->errorcode }}: {{ $error->count }}×
|
||||
@if($error->errormessage) — {{ \Illuminate\Support\Str::limit($error->errormessage, 60) }} @endif
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Filter --}}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body py-2">
|
||||
<form method="GET" action="{{ route('admin.payment-dashboard.transactions') }}" class="form-inline">
|
||||
<label class="mr-2 small">Zeitraum:</label>
|
||||
<select name="days" class="custom-select custom-select-sm mr-3" onchange="this.form.submit()">
|
||||
@foreach([1 => 'Heute', 7 => '7 Tage', 14 => '14 Tage', 30 => '30 Tage', 0 => 'Alle'] as $val => $label)
|
||||
<option value="{{ $val }}" {{ (int)$days === $val ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label class="mr-2 small">Aktion:</label>
|
||||
<select name="txaction" class="custom-select custom-select-sm mr-3" onchange="this.form.submit()">
|
||||
<option value="">Alle</option>
|
||||
@foreach(['paid' => 'Bezahlt', 'appointed' => 'Vorgemerkt', 'pending' => 'Ausstehend', 'failed' => 'Fehlgeschlagen'] as $val => $label)
|
||||
<option value="{{ $val }}" {{ $txactionFilter == $val ? 'selected' : '' }}>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Transaktions-Tabelle --}}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Datum</th>
|
||||
<th>Aktion</th>
|
||||
<th>TX-ID</th>
|
||||
<th>Referenz</th>
|
||||
<th>Modus</th>
|
||||
<th>Fehlercode</th>
|
||||
<th>Fehlermeldung</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($transactions as $tx)
|
||||
<tr class="{{ $tx->txaction === 'failed' ? 'table-danger' : ($tx->txaction === 'paid' ? 'table-success' : '') }}">
|
||||
<td class="text-muted small">{{ $tx->id }}</td>
|
||||
<td class="text-nowrap small">{{ $tx->created_at->format('d.m.Y H:i') }}</td>
|
||||
<td>
|
||||
@php
|
||||
$actionColor = match($tx->txaction) {
|
||||
'paid' => 'success',
|
||||
'failed' => 'danger',
|
||||
'appointed' => 'info',
|
||||
'pending' => 'warning',
|
||||
default => 'secondary',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge badge-{{ $actionColor }}">{{ $tx->txaction ?? '—' }}</span>
|
||||
</td>
|
||||
<td class="small">{{ $tx->txid ?? '—' }}</td>
|
||||
<td class="small">
|
||||
@if($tx->shopping_payment)
|
||||
<code>{{ $tx->shopping_payment->reference }}</code>
|
||||
@else
|
||||
—
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($tx->mode)
|
||||
<span class="badge badge-{{ $tx->mode === 'test' ? 'warning' : 'secondary' }}">{{ $tx->mode }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($tx->errorcode)
|
||||
<span class="text-danger font-weight-bold">{{ $tx->errorcode }}</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="small text-muted" style="max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap">
|
||||
{{ $tx->errormessage }}
|
||||
@if($tx->customermessage)
|
||||
<span class="text-info">({{ \Illuminate\Support\Str::limit($tx->customermessage, 40) }})</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if($tx->transmitted_data)
|
||||
<button class="btn btn-sm btn-outline-secondary" type="button"
|
||||
data-toggle="collapse" data-target="#tx-data-{{ $tx->id }}">
|
||||
<i class="ion ion-md-code"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@if($tx->transmitted_data)
|
||||
<tr class="collapse" id="tx-data-{{ $tx->id }}">
|
||||
<td colspan="9" class="bg-light">
|
||||
<pre class="mb-0 small" style="max-height:200px; overflow-y:auto">{{ json_encode($tx->transmitted_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="9" class="text-center text-muted py-3">Keine Transaktionen gefunden.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
{{ $transactions->links() }}
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
@ -138,6 +138,148 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Umsätze nach Ländern - Jährlich -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Umsätze nach Ländern {{ session('revenue_filter_year') }}</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if(isset($revenue_summary['country_yearly']) && $revenue_summary['country_yearly']->count() > 0)
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Land</th>
|
||||
<th class="text-right">Netto</th>
|
||||
<th class="text-right">Steuer</th>
|
||||
<th class="text-right">Brutto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($revenue_summary['country_yearly'] as $item)
|
||||
<tr>
|
||||
<td>{{ $item->country_name }}</td>
|
||||
<td class="text-right">{{ number_format($item->total_net, 2, ',', '.') }} €</td>
|
||||
<td class="text-right">{{ number_format($item->total_tax, 2, ',', '.') }} €</td>
|
||||
<td class="text-right"><strong>{{ number_format($item->total_gross, 2, ',', '.') }} €</strong></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="p-3"><p class="text-muted mb-0">Keine Umsätze nach Ländern für {{ session('revenue_filter_year') }} gefunden</p></div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gutschriften nach Ländern - Jährlich -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Gutschriften nach Ländern {{ session('revenue_filter_year') }}</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if(isset($credit_summary['country_yearly']) && $credit_summary['country_yearly']->count() > 0)
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th>Land</th>
|
||||
<th class="text-right">Netto</th>
|
||||
<th class="text-right">Steuer</th>
|
||||
<th class="text-right">Brutto</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($credit_summary['country_yearly'] as $item)
|
||||
<tr>
|
||||
<td>{{ $item->country_name }}</td>
|
||||
<td class="text-right">{{ number_format($item->total_net, 2, ',', '.') }} €</td>
|
||||
<td class="text-right">{{ number_format($item->total_tax, 2, ',', '.') }} €</td>
|
||||
<td class="text-right"><strong>{{ number_format($item->total_gross, 2, ',', '.') }} €</strong></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<div class="p-3"><p class="text-muted mb-0">Keine Gutschriften nach Ländern für {{ session('revenue_filter_year') }} gefunden</p></div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Umsätze nach Ländern - Monatlich -->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Umsätze nach Ländern – monatliche Aufschlüsselung</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if(isset($revenue_summary['country_monthly']) && $revenue_summary['country_monthly']->count() > 0)
|
||||
@php $revenueByMonth = $revenue_summary['country_monthly']->groupBy('month'); @endphp
|
||||
@foreach($revenueByMonth as $month => $countries)
|
||||
<div class="px-3 pt-2 pb-1">
|
||||
<strong class="text-primary">{{ $countries->first()->month_label }}</strong>
|
||||
</div>
|
||||
<table class="table table-sm mb-1">
|
||||
<tbody>
|
||||
@foreach($countries as $item)
|
||||
<tr>
|
||||
<td class="pl-4">{{ $item->country_name }}</td>
|
||||
<td class="text-right text-muted"><small>{{ number_format($item->total_net, 2, ',', '.') }} €</small></td>
|
||||
<td class="text-right text-muted"><small>{{ number_format($item->total_tax, 2, ',', '.') }} €</small></td>
|
||||
<td class="text-right"><small><strong>{{ number_format($item->total_gross, 2, ',', '.') }} €</strong></small></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="p-3"><p class="text-muted mb-0">Keine monatlichen Umsätze nach Ländern gefunden</p></div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gutschriften nach Ländern - Monatlich -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">Gutschriften nach Ländern – monatliche Aufschlüsselung</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if(isset($credit_summary['country_monthly']) && $credit_summary['country_monthly']->count() > 0)
|
||||
@php $creditByMonth = $credit_summary['country_monthly']->groupBy('month'); @endphp
|
||||
@foreach($creditByMonth as $month => $countries)
|
||||
<div class="px-3 pt-2 pb-1">
|
||||
<strong class="text-primary">{{ $countries->first()->month_label }}</strong>
|
||||
</div>
|
||||
<table class="table table-sm mb-1">
|
||||
<tbody>
|
||||
@foreach($countries as $item)
|
||||
<tr>
|
||||
<td class="pl-4">{{ $item->country_name }}</td>
|
||||
<td class="text-right text-muted"><small>{{ number_format($item->total_net, 2, ',', '.') }} €</small></td>
|
||||
<td class="text-right text-muted"><small>{{ number_format($item->total_tax, 2, ',', '.') }} €</small></td>
|
||||
<td class="text-right"><small><strong>{{ number_format($item->total_gross, 2, ',', '.') }} €</strong></small></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endforeach
|
||||
@else
|
||||
<div class="p-3"><p class="text-muted mb-0">Keine monatlichen Gutschriften nach Ländern gefunden</p></div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
@if (isset($activeIncentive) && $activeIncentive)
|
||||
@php
|
||||
$hasConfirmedDash = $incentiveParticipant && $incentiveParticipant->accepted_terms_at !== null;
|
||||
@endphp
|
||||
<div class="d-flex col-xl-12 align-items-stretch">
|
||||
<div class="w-100 mb-4 inc-dash-widget">
|
||||
|
||||
|
|
@ -60,6 +63,33 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Notice: Teilnahme noch nicht bestätigt --}}
|
||||
@if (!$hasConfirmedDash)
|
||||
<div class="inc-dash-notice mb-3">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="ion ion-md-alert inc-dash-notice-icon mr-2 mt-1"></i>
|
||||
<div class="flex-grow-1">
|
||||
@if ($incentiveParticipant)
|
||||
<strong
|
||||
class="inc-dash-notice-title">{{ __('incentive.dash_notice_unconfirmed_title') }}</strong>
|
||||
<p class="mb-2 small">{{ __('incentive.dash_notice_unconfirmed_body') }}</p>
|
||||
@else
|
||||
<strong
|
||||
class="inc-dash-notice-title">{{ __('incentive.dash_notice_unregistered_title') }}</strong>
|
||||
<p class="mb-2 small">{{ __('incentive.dash_notice_unregistered_body') }}</p>
|
||||
@endif
|
||||
@if ($activeIncentive->isActive())
|
||||
<button type="button" class="btn inc-dash-btn-notice" data-toggle="modal"
|
||||
data-target="#incParticipateModal">
|
||||
<i class="ion ion-md-checkmark-circle mr-1"></i>
|
||||
{{ __('incentive.dash_notice_btn') }}
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Bilder-Leiste --}}
|
||||
@php
|
||||
$dashGallery = [];
|
||||
|
|
@ -99,6 +129,13 @@
|
|||
<i class="ion ion-md-list mr-1"></i>
|
||||
{{ __('incentive.dashboard_btn_ranking') }}
|
||||
</a>
|
||||
@if ($incentiveParticipant)
|
||||
<a href="{{ route('user_incentive_details', [$activeIncentive->slug]) }}"
|
||||
class="btn inc-dash-btn-secondary">
|
||||
<i class="ion ion-md-list mr-1"></i>
|
||||
{{ __('incentive.my_calculation') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
@ -106,7 +143,117 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Teilnahme-Modal --}}
|
||||
@if (!$hasConfirmedDash && $activeIncentive->isActive())
|
||||
<div class="modal fade" id="incParticipateModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="incParticipateModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="background: linear-gradient(135deg, #6b7758, #4a5340);">
|
||||
<h5 class="modal-title text-white" id="incParticipateModalLabel">
|
||||
<i class="ion ion-md-trophy mr-2" style="color: #d7d700;"></i>
|
||||
{{ __('incentive.dash_modal_title') }}
|
||||
</h5>
|
||||
<button type="button" class="close text-white" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<form id="incDashParticipateForm"
|
||||
action="{{ route('user_incentive_participate', [$activeIncentive->slug]) }}" method="POST">
|
||||
@csrf
|
||||
<div class="modal-body">
|
||||
<p class="mb-3 small">{{ __('incentive.dash_modal_intro') }}</p>
|
||||
|
||||
@if ($activeIncentive->getLang('description'))
|
||||
<div class="mb-3" style="line-height: 1.6;">
|
||||
{!! $activeIncentive->getLang('description') !!}
|
||||
</div>
|
||||
<hr>
|
||||
@endif
|
||||
|
||||
@if ($activeIncentive->getLang('terms'))
|
||||
<div class="card mb-4" style="border: 1px solid #e0e0d8;">
|
||||
<div class="card-header py-2 px-3" style="cursor: pointer; background: #f4f5f0;"
|
||||
data-toggle="collapse" data-target="#dashTermsCollapse">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="ion ion-md-document mr-2" style="color: #6b7758;"></i>
|
||||
<strong class="small">{{ __('incentive.terms') }}</strong>
|
||||
<i class="ion ion-md-chevron-down ml-auto text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="dashTermsCollapse" class="collapse">
|
||||
<div class="card-body small"
|
||||
style="max-height: 280px; overflow-y: auto; line-height: 1.6;">
|
||||
{!! $activeIncentive->getLang('terms') !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="dashAcceptTerms"
|
||||
name="accept_terms" value="1" required>
|
||||
<label class="custom-control-label" for="dashAcceptTerms">
|
||||
{{ __('incentive.accept_terms') }}
|
||||
@if ($activeIncentive->getLang('terms'))
|
||||
(<a href="#dashTermsCollapse" data-toggle="collapse"
|
||||
class="text-muted">{{ __('incentive.show_terms') }}</a>)
|
||||
@endif
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{{ __('incentive.dash_modal_cancel') }}
|
||||
</button>
|
||||
<button type="submit" class="btn inc-dash-btn-primary px-4">
|
||||
<i class="ion ion-md-checkmark mr-1"></i>
|
||||
{{ __('incentive.participate_now') }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<style>
|
||||
.inc-dash-notice {
|
||||
background: rgba(215, 185, 0, 0.10);
|
||||
border: 1px solid rgba(215, 185, 0, 0.35);
|
||||
border-radius: .6rem;
|
||||
padding: .9rem 1rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.inc-dash-notice-icon {
|
||||
color: #c8a000;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.inc-dash-notice-title {
|
||||
display: block;
|
||||
color: #444;
|
||||
margin-bottom: .2rem;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.inc-dash-btn-notice {
|
||||
background: linear-gradient(135deg, #6b7758, #4a5340);
|
||||
color: #fff !important;
|
||||
border: none;
|
||||
border-radius: 50px;
|
||||
padding: .4rem 1.2rem;
|
||||
font-weight: 700;
|
||||
font-size: .82rem;
|
||||
transition: transform .2s, box-shadow .2s;
|
||||
}
|
||||
|
||||
.inc-dash-btn-notice:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(107, 119, 88, 0.35);
|
||||
}
|
||||
|
||||
.inc-dash-widget {
|
||||
border-radius: .75rem;
|
||||
overflow: hidden;
|
||||
|
|
|
|||
114
resources/views/emails/payment-incident-alert.blade.php
Normal file
114
resources/views/emails/payment-incident-alert.blade.php
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>Payment Incident Alert – mivita.care</title>
|
||||
<style type="text/css">
|
||||
td, h1, h2, h3 { font-family: Helvetica, Verdana, Arial, sans-serif; font-weight: 400; }
|
||||
body { -webkit-font-smoothing: antialiased; width: 100%; height: 100%; color: #37302d; background: #ffffff; font-size: 15px; line-height: 26px; }
|
||||
table { border-collapse: separate !important; }
|
||||
.severity-critical { color: #dc3545; }
|
||||
.severity-high { color: #fd7e14; }
|
||||
.severity-medium { color: #ffc107; }
|
||||
.severity-low { color: #28a745; }
|
||||
.label { display: inline-block; padding: 2px 8px; border-radius: 3px; font-size: 12px; font-weight: bold; }
|
||||
.label-danger { background: #dc3545; color: #fff; }
|
||||
.label-warning { background: #ffc107; color: #212529; }
|
||||
.label-success { background: #28a745; color: #fff; }
|
||||
a { color: #919f7a; text-decoration: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="padding:0; margin:0; display:block; background:#f8f8f8;">
|
||||
|
||||
<table align="left" cellpadding="0" cellspacing="0" width="100%" height="100%">
|
||||
<tr>
|
||||
<td align="left" valign="top" bgcolor="#f8f8f8" width="100%">
|
||||
<br>
|
||||
<table style="margin: 0 auto;" cellpadding="0" cellspacing="0" width="700">
|
||||
<tr>
|
||||
<td align="center" style="padding-bottom: 20px;">
|
||||
<img src="https://my.mivita.care/images/logo_mivita.png" alt="mivita.care" width="200">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="10" cellspacing="0" border="0" width="100%" bgcolor="#ffffff">
|
||||
<tr>
|
||||
<td style="font-family: Helvetica, sans-serif; font-size: 18px; font-weight: bold; padding-bottom: 5px;">
|
||||
🚨 {{ $title }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table style="padding: 20px; border: 1px solid #eee; background-color: #fff8f8; line-height: 1.8em; width: 100%;" cellpadding="4" cellspacing="0">
|
||||
<tr>
|
||||
<td width="160"><strong>Titel</strong></td>
|
||||
<td>{{ $incident->title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Schwere</strong></td>
|
||||
<td>
|
||||
<span class="label label-{{ $incident->severity === 'critical' ? 'danger' : ($incident->severity === 'high' ? 'warning' : 'success') }}">
|
||||
{{ $incident->severity_label }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Anbieter</strong></td>
|
||||
<td>{{ $incident->provider_label }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Typ</strong></td>
|
||||
<td>{{ $incident->type_label }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Erkannt am</strong></td>
|
||||
<td>{{ $incident->detected_at->format('d.m.Y H:i') }} Uhr</td>
|
||||
</tr>
|
||||
@if($incident->description)
|
||||
<tr>
|
||||
<td><strong>Beschreibung</strong></td>
|
||||
<td>{{ $incident->description }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if($incident->affected_orders > 0)
|
||||
<tr>
|
||||
<td><strong>Betroffene Bestellungen</strong></td>
|
||||
<td>{{ $incident->affected_orders }}</td>
|
||||
</tr>
|
||||
@endif
|
||||
@if($incident->affected_revenue > 0)
|
||||
<tr>
|
||||
<td><strong>Betroffener Umsatz</strong></td>
|
||||
<td>{{ number_format($incident->affected_revenue, 2, ',', '.') }} €</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="text-align: center; padding: 20px 0;">
|
||||
<a href="{{ $dashboardUrl }}"
|
||||
style="display: inline-block; padding: 10px 24px; background-color: #dc3545; color: #ffffff; font-family: Helvetica, sans-serif; font-size: 14px; font-weight: bold; border-radius: 4px; text-decoration: none;">
|
||||
Incident im Dashboard öffnen
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color:#7B7B7E; font-size:13px; text-align: center; padding: 20px 0;">
|
||||
Diese Nachricht wurde automatisch von mivita.care generiert.<br>
|
||||
<a href="https://www.mivita.care" style="color: #7B7B7E; text-decoration: underline;">www.mivita.care</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -617,6 +617,25 @@
|
|||
<div>{{ __('navigation.tools') }}</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sidenav-item{{ Request::is('admin/payment-dashboard*') ? ' active' : '' }}">
|
||||
<a href="{{ route('admin.payment-dashboard.index') }}" class="sidenav-link">
|
||||
<i class="sidenav-icon ion ion-md-alert"></i>
|
||||
<div>{{ __('navigation.payment_monitor') }}</div>
|
||||
@php $openIncidentCount = Cache::remember('open_incident_count', 60, fn() => \App\Models\PaymentIncident::whereIn('status', ['open','in_progress','waiting_provider'])->count()); @endphp
|
||||
@if($openIncidentCount > 0)
|
||||
<div class="pl-1 ml-auto">
|
||||
<div class="badge badge-danger">{{ $openIncidentCount }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</a>
|
||||
</li>
|
||||
{{-- GF-Ansicht: vorerst auskommentiert --}}
|
||||
{{-- <li class="sidenav-item{{ Request::is('admin/payment-dashboard/management') ? ' active' : '' }}">
|
||||
<a href="{{ route('admin.payment-dashboard.management') }}" class="sidenav-link">
|
||||
<i class="sidenav-icon ion ion-md-stats"></i>
|
||||
<div>{{ __('navigation.payment_monitor_management') }}</div>
|
||||
</a>
|
||||
</li> --}}
|
||||
@endif
|
||||
@if (Auth::user()->isSySAdmin())
|
||||
<li class="sidenav-divider mb-1"></li>
|
||||
|
|
|
|||
|
|
@ -386,6 +386,49 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vip-ranking-notice {
|
||||
background: rgba(107, 119, 88, 0.08);
|
||||
border-bottom: 1px solid rgba(107, 119, 88, 0.15);
|
||||
padding: .6rem 1.5rem;
|
||||
font-size: .8rem;
|
||||
color: #6b7758;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vip-terms-accepted {
|
||||
color: #5a8a5a;
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vip-terms-pending {
|
||||
color: #c0392b;
|
||||
font-size: 1rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.inc-ranking-card .pagination {
|
||||
margin: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.inc-ranking-card .page-item .page-link {
|
||||
color: #6b7758;
|
||||
border-color: #e0e0d8;
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
.inc-ranking-card .page-item.active .page-link {
|
||||
background-color: #6b7758;
|
||||
border-color: #6b7758;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.inc-ranking-card .page-item.disabled .page-link {
|
||||
color: #bbb;
|
||||
border-color: #e0e0d8;
|
||||
}
|
||||
|
||||
.pending-banner {
|
||||
background: rgba(215, 215, 0, 0.12);
|
||||
border: 1px solid rgba(215, 215, 0, 0.3);
|
||||
|
|
@ -857,7 +900,7 @@
|
|||
<div class="d-flex align-items-center">
|
||||
<i class="ion ion-md-list mr-2" style="font-size: 1.2rem; color: #6b7758;"></i>
|
||||
<span class="ranking-title">{{ __('incentive.section_ranking') }}</span>
|
||||
<span class="badge-top ml-2">Top {{ $rankingDisplayLimit }}</span>
|
||||
<span class="badge-top ml-2">{{ __('incentive.ranking_all_active') }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="hint-text">{{ __('incentive.ranking_winners_hint', ['n' => $incentive->max_winners]) }}</span>
|
||||
|
|
@ -868,6 +911,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if ($isVipView)
|
||||
<div class="vip-ranking-notice">
|
||||
<i class="ion ion-md-eye mr-1"></i>
|
||||
{{ __('incentive.vip_view_notice') }}
|
||||
</div>
|
||||
@endif
|
||||
@if ($ranking->isEmpty())
|
||||
<div class="p-4 text-center text-muted">
|
||||
<i class="ion ion-md-people mb-2 d-block" style="font-size: 2.5rem; opacity: .4;"></i>
|
||||
|
|
@ -904,7 +953,16 @@
|
|||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($p->accepted_terms_at)
|
||||
@if ($p->accepted_terms_at || $isVipView)
|
||||
@if ($isVipView)
|
||||
@if ($p->accepted_terms_at)
|
||||
<i class="ion ion-md-checkmark-circle vip-terms-accepted ml-1"
|
||||
title="{{ __('incentive.vip_terms_accepted') }}"></i>
|
||||
@else
|
||||
<i class="ion ion-md-close-circle vip-terms-pending ml-1"
|
||||
title="{{ __('incentive.vip_terms_pending') }}"></i>
|
||||
@endif
|
||||
@endif
|
||||
@if ($p->user && $p->user->account)
|
||||
{{ $p->user->account->first_name }} {{ $p->user->account->last_name }}
|
||||
@else
|
||||
|
|
@ -947,6 +1005,11 @@
|
|||
</table>
|
||||
</div>
|
||||
@endif
|
||||
@if ($ranking->hasPages())
|
||||
<div class="p-3">
|
||||
{{ $ranking->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +1,148 @@
|
|||
@extends($user_shop ?'web.user.layouts.layout' : 'web.layouts.layout')
|
||||
@extends($user_shop ? 'web.user.layouts.layout' : 'web.layouts.layout')
|
||||
|
||||
@section('content')
|
||||
|
||||
@php
|
||||
$isCancel = ($error_type ?? 'error') === 'cancel';
|
||||
$errorcode = $errorcode ?? null;
|
||||
$errorDescription = $error_description ?? null;
|
||||
|
||||
// Fehlercode → konkreten Hinweistext ermitteln
|
||||
$errorReason = null;
|
||||
if (!$isCancel && $errorcode) {
|
||||
$code = (int) $errorcode;
|
||||
if (in_array($code, [33])) {
|
||||
$errorReason = __('payment.payment_error_reasons.card_expired');
|
||||
} elseif (in_array($code, [4, 34])) {
|
||||
$errorReason = __('payment.payment_error_reasons.card_blocked');
|
||||
} elseif (in_array($code, [12, 14, 105])) {
|
||||
$errorReason = __('payment.payment_error_reasons.card_invalid');
|
||||
} elseif (in_array($code, [5, 902, 4219])) {
|
||||
$errorReason = __('payment.payment_error_reasons.card_declined');
|
||||
} elseif (in_array($code, [130])) {
|
||||
$errorReason = __('payment.payment_error_reasons.insufficient_funds');
|
||||
} elseif (in_array($code, [120])) {
|
||||
$errorReason = __('payment.payment_error_reasons.cvv_invalid');
|
||||
} elseif (in_array($code, [900])) {
|
||||
$errorReason = __('payment.payment_error_reasons.3ds_failed');
|
||||
} elseif (in_array($code, [970, 135])) {
|
||||
$errorReason = __('payment.payment_error_reasons.timeout');
|
||||
} elseif (in_array($code, [4218])) {
|
||||
$errorReason = __('payment.payment_error_reasons.fraud');
|
||||
} else {
|
||||
$errorReason = __('payment.payment_error_reasons.general');
|
||||
}
|
||||
} elseif (!$isCancel) {
|
||||
$errorReason = __('payment.payment_error_reasons.general');
|
||||
}
|
||||
@endphp
|
||||
|
||||
<section class="page-header page-header-xlg parallax parallax-3"
|
||||
style="background-image:url('/assets/images/vision-min.jpg')">
|
||||
<div class="overlay dark-1"><!-- dark overlay [1 to 9 opacity] --></div>
|
||||
|
||||
<div class="container">
|
||||
</div>
|
||||
style="background-image:url('/assets/images/vision-min.jpg')">
|
||||
<div class="overlay dark-1"></div>
|
||||
<div class="container"></div>
|
||||
</section>
|
||||
<!-- /PAGE HEADER -->
|
||||
|
||||
<style>
|
||||
div.shop-item {
|
||||
margin-bottom:30px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
div.shop-item > .thumbnail, .thumbnail {
|
||||
border: none;
|
||||
}
|
||||
div.shop-item-summary {
|
||||
padding: 8px;
|
||||
}
|
||||
div.shop-item-summary h2 a {
|
||||
color: #9aa983;
|
||||
font-size: 1.2em;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
div.shop-item-buttons {
|
||||
padding: 0 8px 10px 8px;
|
||||
}
|
||||
div.shop-item-buttons .btn-xs{
|
||||
padding: 4px;
|
||||
}
|
||||
</style>
|
||||
<!-- -->
|
||||
|
||||
<!-- -->
|
||||
<section>
|
||||
<section class="py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-2 col-md-12"></div>
|
||||
<div class="col-md-12 col-lg-8">
|
||||
|
||||
<!-- CHECKOUT ERROR MESSAGE -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<div class="alert alert-danger">
|
||||
<h3><i class="fa fa-exclamation-triangle"></i> {{ $error_title ?? __('payment.payment_error') }}</h3>
|
||||
<p>{{ $error_message ?? __('payment.payment_error_description') }}</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
@if ($isCancel)
|
||||
{{-- ── ABGEBROCHEN ──────────────────────────────── --}}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body text-center py-4">
|
||||
<div style="font-size:3rem; color:#f0ad4e;" class="mb-3">
|
||||
<i class="fa fa-ban"></i>
|
||||
</div>
|
||||
<h3 class="mb-2">{{ $error_title }}</h3>
|
||||
<p class="text-muted mb-1">{{ $error_message }}</p>
|
||||
<p class="text-muted small mb-4">{{ __('payment.nothing_was_charged') }}</p>
|
||||
|
||||
<p>{{ __('payment.contact_support_if_needed') }}</p>
|
||||
|
||||
<p>
|
||||
<strong>{{ __('payment.your_mivita_team') }}</strong>
|
||||
</p>
|
||||
<div class="alert alert-info text-left py-2 mb-4" style="font-size:0.9rem;">
|
||||
<i class="fa fa-info-circle mr-1"></i>
|
||||
{{ __('payment.payment_canceled_hint') }}
|
||||
</div>
|
||||
|
||||
@if($user_shop)
|
||||
<div class="mt-4">
|
||||
<a href="{{ config('app.protocol') . $user_shop->slug . '.' . config('app.domain') . config('app.tld_care') }}" class="btn btn-primary">
|
||||
<i class="fa fa-arrow-left"></i> {{ __('payment.back_to_shop') }}
|
||||
</a>
|
||||
</div>
|
||||
@if (isset($checkout_url))
|
||||
<a href="{{ $checkout_url }}" class="btn btn-primary btn-lg btn-block mb-2">
|
||||
<i class="fa fa-refresh mr-1"></i>
|
||||
{{ __('payment.try_again') }}
|
||||
</a>
|
||||
@endif
|
||||
@if ($user_shop)
|
||||
<a href="{{ config('app.protocol') . $user_shop->slug . '.' . config('app.domain') . config('app.tld_care') }}"
|
||||
class="btn btn-default btn-block">
|
||||
<i class="fa fa-arrow-left mr-1"></i> {{ __('payment.back_to_shop') }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
{{-- ── FEHLER ───────────────────────────────────── --}}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body py-4">
|
||||
<div class="text-center mb-3">
|
||||
<div style="font-size:3rem; color:#d9534f;">
|
||||
<i class="fa fa-exclamation-circle"></i>
|
||||
</div>
|
||||
<h3 class="mt-2 mb-1">{{ $error_title }}</h3>
|
||||
<p class="text-muted mb-0">{{ $error_message }}</p>
|
||||
<small class="text-muted">{{ __('payment.nothing_was_charged') }}</small>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
{{-- Konkreter Hinweis basierend auf Fehlercode --}}
|
||||
<div class="alert alert-warning mb-3" style="font-size:0.9rem;">
|
||||
<strong><i
|
||||
class="fa fa-lightbulb-o mr-1"></i>{{ __('payment.payment_error_what_to_do') }}</strong><br>
|
||||
{{ $errorReason }}
|
||||
</div>
|
||||
|
||||
{{-- Fehlerbeschreibung + Code (für Transparenz) --}}
|
||||
@if ($errorcode || $errorDescription)
|
||||
<div class="panel panel-default mb-3" style="font-size:0.82rem;">
|
||||
<div class="panel-heading py-1 px-3" style="font-size:0.82rem;">
|
||||
<strong>{{ __('payment.payment_error_code') }}</strong>
|
||||
</div>
|
||||
<div class="panel-body py-2 px-3">
|
||||
@if ($errorcode)
|
||||
<span class="label label-danger mr-2">{{ $errorcode }}</span>
|
||||
@endif
|
||||
@if ($errorDescription)
|
||||
<span class="text-muted">{{ $errorDescription }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Aktionsbuttons --}}
|
||||
@if (isset($checkout_url))
|
||||
<a href="{{ $checkout_url }}" class="btn btn-primary btn-block btn-lg mb-2">
|
||||
<i class="fa fa-refresh mr-1"></i>
|
||||
{{ __('payment.payment_error_retry') }}
|
||||
</a>
|
||||
@endif
|
||||
@if ($user_shop)
|
||||
<a href="{{ config('app.protocol') . $user_shop->slug . '.' . config('app.domain') . config('app.tld_care') }}"
|
||||
class="btn btn-default btn-block mb-3">
|
||||
<i class="fa fa-arrow-left mr-1"></i> {{ __('payment.back_to_shop') }}
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<p class="text-muted text-center small mb-0">
|
||||
<i class="fa fa-envelope-o mr-1"></i>
|
||||
{{ __('payment.contact_support_if_needed') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- /CHECKOUT ERROR MESSAGE -->
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<!-- / -->
|
||||
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -210,8 +210,16 @@
|
|||
@if (\Session::has('errormessage'))
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="alert alert-danger">
|
||||
{{ \Session::get('customermessage') }}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h5 class="alert-heading mb-1">
|
||||
<i class="fa fa-exclamation-circle mr-1"></i>
|
||||
{{ __('payment.payment_error') }}
|
||||
</h5>
|
||||
@if(\Session::get('customermessage'))
|
||||
<p class="mb-1">{{ \Session::get('customermessage') }}</p>
|
||||
@endif
|
||||
<hr class="my-2">
|
||||
<p class="mb-0 small">{{ __('payment.payment_error_hint') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue