added sync button, improved UI
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
6
info.md
6
info.md
@@ -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')
|
||||||
|
]);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -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">
|
||||||
|
|||||||
81
resources/views/layouts/app.blade copy.php
Normal file
81
resources/views/layouts/app.blade copy.php
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user