added sync button, improved UI

This commit is contained in:
Kwesi Banson
2026-09-25 16:44:37 +00:00
parent b4fcf65293
commit ebf11d38ed
7 changed files with 277 additions and 67 deletions

View File

@@ -9,7 +9,6 @@ class DashboardController extends Controller
{ {
public function index(){ public function index(){
// dd('public insanity');
return view('dashboard.index'); return view('dashboard.index');
} }
@@ -50,8 +49,6 @@ class DashboardController extends Controller
'services' => $stats 'services' => $stats
]); ]);
} }
// app/Http/Controllers/DashboardController.php
// app/Http/Controllers/DashboardController.php
public function details(Request $request, $id) public function details(Request $request, $id)
{ {
$service = DB::table('subscription_services')->where('id', $id)->first(); $service = DB::table('subscription_services')->where('id', $id)->first();
@@ -77,7 +74,6 @@ class DashboardController extends Controller
return view('dashboard.details', compact('service', 'subscribers')); return view('dashboard.details', compact('service', 'subscribers'));
} }
// app/Http/Controllers/DashboardController.php
public function subscriberProfile(Request $request, $serviceId, $msisdn) public function subscriberProfile(Request $request, $serviceId, $msisdn)
{ {
$service = DB::table('subscription_services')->where('id', $serviceId)->first(); $service = DB::table('subscription_services')->where('id', $serviceId)->first();

View File

@@ -35,3 +35,9 @@
techappsuser@206.225.84.201:/home/techappsuser/DockerProjects/subscription_services/src/ techappsuser@206.225.84.201:/home/techappsuser/DockerProjects/subscription_services/src/
\App\Models\User::create([
'name' => 'Admin User',
'email' => 'kwesi@click-mobile.com',
'password' => Hash::make('franctic@233')
]);

View File

@@ -1,6 +1,5 @@
// public/js/dashboard.js // public/js/dashboard.js
$(document).ready(function() { $(document).ready(function() {
console.log(base_url);
function fetchDashboardStats() { function fetchDashboardStats() {
$.ajax({ $.ajax({
url: base_url + '/api/dashboard/stats', url: base_url + '/api/dashboard/stats',
@@ -83,4 +82,53 @@ $(document).ready(function() {
// Todo: Refresh data every 5 minutes (300000 ms) automatically without page reload // Todo: Refresh data every 5 minutes (300000 ms) automatically without page reload
// setInterval(fetchDashboardStats, 300000); // setInterval(fetchDashboardStats, 300000);
$('#syncTriggerBtn').on('click', function() {
let btn = $(this);
let icon = $('#syncIcon');
let text = $('#syncText');
// Disable button and add spinning effect
btn.prop('disabled', true);
icon.addClass('spinner-border spinner-border-sm');
text.text('Syncing...');
$.ajax({
url: base_url + '/api/subscriptions/sync',
method: 'POST',
data: {
_token: $('meta[name="csrf-token"]').attr('content')
},
success: function(response) {
// Use SweetAlert2 for a clean success alert
Swal.fire({
icon: 'success',
title: 'Synchronized!',
text: response.message,
timer: 2000,
showConfirmButton: false
});
// Refresh dashboard data instantly to reflect updated counts
fetchDashboardStats();
},
error: function(xhr) {
let errorMsg = xhr.responseJSON && xhr.responseJSON.message
? xhr.responseJSON.message
: 'An unexpected error occurred during synchronization.';
Swal.fire({
icon: 'error',
title: 'Sync Failed',
text: errorMsg
});
},
complete: function() {
// Restore button state
btn.prop('disabled', false);
icon.removeClass('spinner-border spinner-border-sm');
text.text('Sync Now');
}
});
});
}); });

View File

@@ -2,7 +2,15 @@
@section('content') @section('content')
<div class="container mt-4"> <div class="container mt-4">
<h2 class="mb-4">Subscriptions Overview</h2> <div class="d-flex justify-content-between align-items-center mb-4">
<h2>Subscriptions Overview</h2>
<!-- Trigger Button -->
<button id="syncTriggerBtn" class="btn btn-primary d-flex align-items-center gap-2 shadow-sm">
<i class="bi bi-arrow-repeat" id="syncIcon"></i>
<span id="syncText">Sync Now</span>
</button>
</div>
<!-- Grand Totals Section --> <!-- Grand Totals Section -->
<div class="row mb-5"> <div class="row mb-5">

View File

@@ -0,0 +1,81 @@
<!doctype html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Click Content Management</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<!-- Scripts -->
<link rel="stylesheet" href="{{ url('public/assets/libs/bootstrap/css/bootstrap5.3.2.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
@stack('styles')
<script>
var base_url = "{!! url('/') !!}";
</script>
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">CML</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a>
</li>
<!-- <li class="nav-item">
<a class="nav-link" href="">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">Project Statuses</a>
</li>
-->
</ul>
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
User Profile
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="#">My Profile</a></li>
<li><a class="dropdown-item" href="#">Settings</a></li>
<li>
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf
</form>
</li>
</ul>
</div>
</div>
</div>
</nav>
<main class="py-4">
@yield('content')
</main>
</div>
@stack('modals')
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="{{ url('public/assets/libs/bootstrap/js/bootstrap5.3.2.js') }}" defer></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
@stack('scripts')
</body>
</html>

View File

@@ -11,11 +11,56 @@
<!-- Fonts --> <!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net"> <link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet"> <link href="https://fonts.bunny.net/css?family=Nunito:wght@400;600;700&display=swap" rel="stylesheet">
<!-- Scripts --> <!-- Scripts & Styles -->
<link rel="stylesheet" href="{{ url('public/assets/libs/bootstrap/css/bootstrap5.3.2.css') }}"> <link rel="stylesheet" href="{{ url('public/assets/libs/bootstrap/css/bootstrap5.3.2.css') }}">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<!-- Lighter Sea/Sky Blue Theme Customizations -->
<style>
:root {
--bs-primary: #0096c7;
--bs-primary-rgb: 0, 150, 199;
}
body {
font-family: 'Nunito', sans-serif;
background-color: #f0f7ff; /* Very soft bright ice blue */
color: #333333;
}
.navbar-sea {
background: linear-gradient(135deg, #0077b6, #00b4d8); /* Brighter, lighter sea blue gradient */
box-shadow: 0 4px 12px rgba(0, 150, 199, 0.2);
}
.navbar-sea .navbar-brand,
.navbar-sea .nav-link {
color: #ffffff !important;
font-weight: 600;
}
.navbar-sea .nav-link:hover {
color: #caf0f8 !important;
}
.card {
border: none;
border-radius: 0.75rem;
box-shadow: 0 0.5rem 1rem rgba(0, 119, 182, 0.05);
transition: transform 0.2s ease-in-out;
}
.btn-primary {
background-color: #0096c7;
border-color: #0077b6;
}
.btn-primary:hover {
background-color: #0077b6;
border-color: #03045e;
}
.dropdown-menu {
border-radius: 0.5rem;
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.1);
border: none;
}
</style>
@stack('styles') @stack('styles')
<script> <script>
var base_url = "{!! url('/') !!}"; var base_url = "{!! url('/') !!}";
@@ -23,39 +68,37 @@
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<!-- Lighter Sea Blue Gradient Navbar -->
<nav class="navbar navbar-expand-lg navbar-sea py-3">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid px-4">
<div class="container-fluid"> <a class="navbar-brand d-flex align-items-center gap-2" href="{{ url('/') }}">
<a class="navbar-brand" href="#">CML</a> <i class="bi bi-layers-fill fs-4"></i>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span>CML Subscriptions</span>
<span class="navbar-toggler-icon"></span> </a>
<button class="navbar-toggler border-0 text-white" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon" style="filter: invert(1);"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" aria-current="page" href="/">Home</a> <a class="nav-link active" aria-current="page" href="/">
<i class="bi bi-house-door me-1"></i> Home
</a>
</li> </li>
<!-- <li class="nav-item">
<a class="nav-link" href="">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link" href="">Project Statuses</a>
</li>
-->
</ul> </ul>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn btn-light text-primary fw-bold dropdown-toggle px-3 py-2 rounded-pill shadow-sm" type="button" id="userDropdown" data-bs-toggle="dropdown" aria-expanded="false">
User Profile <i class="bi bi-person-circle me-1"></i> User Profile
</button> </button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="userDropdown"> <ul class="dropdown-menu dropdown-menu-end mt-2 p-2" aria-labelledby="userDropdown">
<li><a class="dropdown-item" href="#">My Profile</a></li> <li><a class="dropdown-item rounded py-2" href="#"><i class="bi bi-person me-2 text-muted"></i> My Profile</a></li>
<li><a class="dropdown-item" href="#">Settings</a></li> <li><a class="dropdown-item rounded py-2" href="#"><i class="bi bi-gear me-2 text-muted"></i> Settings</a></li>
<li><hr class="dropdown-divider"></li>
<li> <li>
<a class="dropdown-item" href="{{ route('logout') }}" <a class="dropdown-item rounded py-2 text-danger" href="{{ route('logout') }}"
onclick="event.preventDefault(); onclick="event.preventDefault();
document.getElementById('logout-form').submit();"> document.getElementById('logout-form').submit();">
{{ __('Logout') }} <i class="bi bi-box-arrow-right me-2"></i> {{ __('Logout') }}
</a> </a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none"> <form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@csrf @csrf
@@ -67,10 +110,11 @@
</div> </div>
</nav> </nav>
<main class="py-4"> <main class="py-4 px-2">
@yield('content') @yield('content')
</main> </main>
</div> </div>
@stack('modals') @stack('modals')
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="{{ url('public/assets/libs/bootstrap/js/bootstrap5.3.2.js') }}" defer></script> <script src="{{ url('public/assets/libs/bootstrap/js/bootstrap5.3.2.js') }}" defer></script>

View File

@@ -1,12 +1,19 @@
<?php <?php
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Artisan;
// Route::get('/', function () { // Route::get('/', function () {
// return view('welcome'); // return view('welcome');
// }); // });
Auth::routes(); Auth::routes([
'register' => false, // Disables the registration routes
'reset' => false, // Disables the password reset routes
'verify' => false, // Disables the email verification routes
]);
Route::get('/home', [App\Http\Controllers\DashboardController::class, 'index'])->name('home'); Route::get('/home', [App\Http\Controllers\DashboardController::class, 'index'])->name('home');
@@ -15,10 +22,29 @@ Route::get('/', [App\Http\Controllers\DashboardController::class, 'index'])->nam
// The main dashboard view Route::post('/api/subscriptions/sync', function () {
try {
// Run the artisan command programmatically
Artisan::call('subscriptions:sync');
return response()->json([
'status' => 'success',
'message' => 'Subscriptions synchronized successfully!'
]);
} catch (\Exception $e) {
return response()->json([
'status' => 'error',
'message' => 'Sync failed: ' . $e->getMessage()
], 500);
}
})->name('api.subscriptions.sync');
Route::middleware(['auth'])->group(function () {
Route::get('/', [App\Http\Controllers\DashboardController::class, 'index'])->name('dashboard.index'); Route::get('/', [App\Http\Controllers\DashboardController::class, 'index'])->name('dashboard.index');
// The AJAX endpoint for jQuery to consume
Route::get('/api/dashboard/stats', [App\Http\Controllers\DashboardController::class, 'getStats'])->name('api.dashboard.stats'); Route::get('/api/dashboard/stats', [App\Http\Controllers\DashboardController::class, 'getStats'])->name('api.dashboard.stats');
@@ -27,6 +53,7 @@ Route::post('/services', [App\Http\Controllers\SubscriptionServiceController::cl
Route::post('/services/test-connection', [App\Http\Controllers\SubscriptionServiceController::class, 'testConnection'])->name('services.test'); Route::post('/services/test-connection', [App\Http\Controllers\SubscriptionServiceController::class, 'testConnection'])->name('services.test');
// routes/web.php
Route::get('/services/{id}/details', [App\Http\Controllers\DashboardController::class, 'details'])->name('services.details'); Route::get('/services/{id}/details', [App\Http\Controllers\DashboardController::class, 'details'])->name('services.details');
Route::get('/services/{serviceId}/subscriber/{msisdn}', [App\Http\Controllers\DashboardController::class, 'subscriberProfile'])->name('services.subscriber.profile'); Route::get('/services/{serviceId}/subscriber/{msisdn}', [App\Http\Controllers\DashboardController::class, 'subscriberProfile'])->name('services.subscriber.profile');
});