Skip to content

NapepGo  #3

Description

@timothybala8-stack
<title>Rider Management Dashboard</title> <style> /* Reset & Base Styles */ * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
    body {
        background-color: #f4f6f9;
        color: #333;
        padding: 24px;
        line-height: 1.5;
    }

    .dashboard-container {
        max-width: 1100px;
        margin: 0 auto;
    }

    /* Header Info Bar */
    .info-bar {
        background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
        color: #ffffff;
        padding: 16px 20px;
        border-radius: 8px;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        align-items: center;
        gap: 12px;
        margin-bottom: 24px;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    }

    /* Cards & Layout */
    .card {
        background: #ffffff;
        border-radius: 8px;
        padding: 24px;
        margin-bottom: 24px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
    }

    .card h2 {
        font-size: 1.25rem;
        margin-bottom: 16px;
        color: #1a202c;
    }

    /* Form Styles */
    .form-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
        gap: 16px;
        margin-bottom: 20px;
    }

    .form-group {
        display: flex;
        flex-direction: column;
    }

    .form-group label {
        font-size: 0.875rem;
        font-weight: 600;
        margin-bottom: 6px;
        color: #4a5568;
    }

    .form-group input, 
    .form-group select, 
    .filter-group input, 
    .filter-group select {
        padding: 10px 12px;
        border: 1px solid #cbd5e0;
        border-radius: 6px;
        font-size: 0.95rem;
        outline: none;
        transition: border-color 0.2s;
    }

    .form-group input:focus, 
    .form-group select:focus, 
    .filter-group input:focus, 
    .filter-group select:focus {
        border-color: #3182ce;
        box-shadow: 0 0 0 3px rgba(49, 130, 206, 0.15);
    }

    /* Buttons */
    .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 6px;
        font-weight: 600;
        cursor: pointer;
        transition: background-color 0.2s, transform 0.1s;
    }

    .btn:active {
        transform: scale(0.98);
    }

    .btn-primary {
        background-color: #2b6cb0;
        color: white;
    }

    .btn-primary:hover {
        background-color: #2c5282;
    }

    .btn-action {
        padding: 6px 12px;
        font-size: 0.825rem;
        margin-right: 4px;
    }

    .btn-toggle {
        background-color: #edf2f7;
        color: #2d3748;
    }

    .btn-toggle:hover {
        background-color: #e2e8f0;
    }

    .btn-delete {
        background-color: #fed7d7;
        color: #9b2c2c;
    }

    .btn-delete:hover {
        background-color: #feb2b2;
    }

    .btn-logout {
        background-color: #e53e3e;
        color: white;
        padding: 6px 12px;
        font-size: 0.8rem;
        border-radius: 4px;
    }

    .btn-logout:hover {
        background-color: #c53030;
    }

    /* Table Controls & Filtering */
    .table-header-controls {
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex-wrap: wrap;
        gap: 16px;
        margin-bottom: 16px;
    }

    .filter-group {
        display: flex;
        gap: 10px;
    }

    /* Table Design */
    .table-wrapper {
        overflow-x: auto;
    }

    table {
        width: 100%;
        border-collapse: collapse;
        text-align: left;
    }

    th {
        background-color: #f7fafc;
        color: #4a5568;
        font-weight: 600;
        font-size: 0.85rem;
        text-transform: uppercase;
        letter-spacing: 0.05em;
        padding: 12px 16px;
        border-bottom: 2px solid #e2e8f0;
    }

    td {
        padding: 14px 16px;
        border-bottom: 1px solid #e2e8f0;
        font-size: 0.95rem;
    }

    tr:hover {
        background-color: #f8fafc;
    }

    /* Status Badges */
    .badge {
        display: inline-block;
        padding: 4px 10px;
        border-radius: 12px;
        font-size: 0.825rem;
        font-weight: 600;
    }

    .badge-paid {
        background-color: #c6f6d5;
        color: #22543d;
    }

    .badge-pending {
        background-color: #feebc8;
        color: #744210;
    }

    .empty-msg {
        text-align: center;
        color: #a0aec0;
        padding: 24px;
    }

    /* Login Overlay Modal */
    .auth-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(15, 23, 42, 0.85);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
    }

    .auth-card {
        background: #ffffff;
        padding: 32px;
        border-radius: 10px;
        width: 100%;
        max-width: 400px;
        box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
    }

    .auth-card h2 {
        margin-bottom: 8px;
        color: #1e3c72;
    }

    .auth-card p {
        color: #64748b;
        font-size: 0.9rem;
        margin-bottom: 20px;
    }

    .error-msg {
        color: #e53e3e;
        font-size: 0.85rem;
        margin-top: 10px;
        text-align: center;
    }
</style>
<!-- Login Modal / Overlay -->
<div id="authOverlay" class="auth-overlay">
    <div class="auth-card">
        <h2>Admin Login</h2>
        <p>Sign in to manage rider tax collections.</p>
        <form id="loginForm">
            <div class="form-group">
                <label for="loginEmail">Email Address</label>
                <input type="email" id="loginEmail" value="timothybala8@gmail.com" required>
            </div>
            <div class="form-group" style="margin-top: 15px;">
                <label for="loginPassword">Password</label>
                <input type="password" id="loginPassword" placeholder="Enter password" required>
            </div>
            <button type="submit" class="btn btn-primary" style="width: 100%; margin-top: 20px;">Login</button>
            <p id="authError" class="error-msg"></p>
        </form>
    </div>
</div>

<div class="dashboard-container">
    <!-- Info Header -->
    <header class="info-bar">
        <div class="info-item"><strong>Admin:</strong> <span id="adminEmailDisplay">Not Signed In</span></div>
        <div class="info-item"><strong>Tax Collection:</strong> Opay 6103229012 (Timothy Bala Williams)</div>
        <div class="info-item">
            <strong style="margin-right: 10px;">Fee: ₦5,000/Month</strong>
            <button id="logoutBtn" class="btn btn-logout" style="display: none;">Logout</button>
        </div>
    </header>

    <!-- Main Dashboard Content -->
    <div id="mainDashboard" style="display: none;">
        <!-- Admin Form Card -->
        <section class="card">
            <h2>Add New Rider</h2>
            <form id="addRiderForm">
                <div class="form-grid">
                    <div class="form-group">
                        <label for="riderName">Rider Name</label>
                        <input type="text" id="riderName" placeholder="e.g. Usman Ibrahim" required>
                    </div>
                    <div class="form-group">
                        <label for="riderPhone">Phone / WhatsApp</label>
                        <input type="tel" id="riderPhone" placeholder="e.g. 08012345678" required>
                    </div>
                    <div class="form-group">
                        <label for="membershipId">Membership ID</label>
                        <input type="text" id="membershipId" placeholder="e.g. NPG-2026-001" required>
                    </div>
                    <div class="form-group">
                        <label for="taxStatus">Tax Status</label>
                        <select id="taxStatus">
                            <option value="Paid">Paid</option>
                            <option value="Pending">Pending</option>
                        </select>
                    </div>
                </div>
                <button type="submit" class="btn btn-primary">Register Rider</button>
            </form>
        </section>

        <!-- Table & Filter Controls -->
        <section class="card">
            <div class="table-header-controls">
                <h2>Registered Riders</h2>
                <div class="filter-group">
                    <input type="text" id="searchInput" placeholder="Search name or ID...">
                    <select id="statusFilter">
                        <option value="All">All Statuses</option>
                        <option value="Paid">Paid</option>
                        <option value="Pending">Pending</option>
                    </select>
                </div>
            </div>

            <div class="table-wrapper">
                <table id="ridersTable">
                    <thead>
                        <tr>
                            <th>Rider Name</th>
                            <th>Phone/WhatsApp</th>
                            <th>Membership ID</th>
                            <th>Tax Status</th>
                            <th>Actions</th>
                        </tr>
                    </thead>
                    <tbody id="tableBody">
                        <!-- Dynamic JavaScript Rows -->
                    </tbody>
                </table>
            </div>
        </section>
    </div>
</div>

<!-- Firebase SDKs and Application Logic -->
<script type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-app.js";
    import { 
        getDatabase, 
        ref, 
        onValue, 
        push, 
        update, 
        remove 
    } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-database.js";
    import { 
        getAuth, 
        signInWithEmailAndPassword, 
        signOut, 
        onAuthStateChanged 
    } from "https://www.gstatic.com/firebasejs/10.8.0/firebase-auth.js";

    // Your Firebase Configuration (Replace placeholders with your real Firebase keys)
    const firebaseConfig = {
        apiKey: "YOUR_API_KEY",
        authDomain: "napep-go.firebaseapp.com",
        databaseURL: "https://napep-go-default-rtdb.firebaseio.com",
        projectId: "napep-go",
        storageBucket: "napep-go.appspot.com",
        messagingSenderId: "YOUR_SENDER_ID",
        appId: "YOUR_APP_ID"
    };

    // Initialize Firebase
    const app = initializeApp(firebaseConfig);
    const db = getDatabase(app);
    const auth = getAuth(app);
    const ridersRef = ref(db, 'riders');

    // Local Data State & Listener Handle
    let riders = [];
    let unsubscribeRiders = null;

    // DOM Elements
    const authOverlay = document.getElementById('authOverlay');
    const loginForm = document.getElementById('loginForm');
    const loginEmail = document.getElementById('loginEmail');
    const loginPassword = document.getElementById('loginPassword');
    const authError = document.getElementById('authError');
    const mainDashboard = document.getElementById('mainDashboard');
    const adminEmailDisplay = document.getElementById('adminEmailDisplay');
    const logoutBtn = document.getElementById('logoutBtn');

    const form = document.getElementById('addRiderForm');
    const tableBody = document.getElementById('tableBody');
    const searchInput = document.getElementById('searchInput');
    const statusFilter = document.getElementById('statusFilter');

    // Event Listeners
    form.addEventListener('submit', handleAddRider);
    searchInput.addEventListener('input', renderTable);
    statusFilter.addEventListener('change', renderTable);
    logoutBtn.addEventListener('click', handleLogout);
    loginForm.addEventListener('submit', handleLogin);

    // Authentication State Observer
    onAuthStateChanged(auth, (user) => {
        if (user) {
            authOverlay.style.display = 'none';
            mainDashboard.style.display = 'block';
            logoutBtn.style.display = 'inline-block';
            adminEmailDisplay.textContent = user.email;

            startRidersListener();
        } else {
            authOverlay.style.display = 'flex';
            mainDashboard.style.display = 'none';
            logoutBtn.style.display = 'none';
            adminEmailDisplay.textContent = 'Not Signed In';

            if (unsubscribeRiders) unsubscribeRiders();
        }
    });

    // Login Handler
    function handleLogin(e) {
        e.preventDefault();
        authError.textContent = '';

        signInWithEmailAndPassword(auth, loginEmail.value.trim(), loginPassword.value)
            .then(() => {
                loginPassword.value = '';
            })
            .catch((error) => {
                authError.textContent = error.message;
            });
    }

    // Logout Handler
    function handleLogout() {
        signOut(auth).catch((error) => {
            console.error("Sign out error:", error);
        });
    }

    // Real-Time Database Sync
    function startRidersListener() {
        unsubscribeRiders = onValue(ridersRef, (snapshot) => {
            const data = snapshot.val();
            riders = [];

            if (data) {
                Object.keys(data).forEach(key => {
                    riders.push({
                        id: key,
                        ...data[key]
                    });
                });
            }
            renderTable();
        });
    }

    // Render Table
    function renderTable() {
        const searchTerm = searchInput.value.toLowerCase();
        const filterValue = statusFilter.value;

        tableBody.innerHTML = '';

        const filteredRiders = riders.filter(rider => {
            const matchesSearch = (rider.name && rider.name.toLowerCase().includes(searchTerm)) || 
                                  (rider.memberId && rider.memberId.toLowerCase().includes(searchTerm));
            const matchesStatus = filterValue === 'All' || rider.status === filterValue;
            return matchesSearch && matchesStatus;
        });

        if (filteredRiders.length === 0) {
            tableBody.innerHTML = `<tr><td colspan="5" class="empty-msg">No riders found in database.</td></tr>`;
            return;
        }

        filteredRiders.forEach(rider => {
            const tr = document.createElement('tr');
            const badgeClass = rider.status === 'Paid' ? 'badge-paid' : 'badge-pending';

            tr.innerHTML = `
                <td><strong>${escapeHtml(rider.name)}</strong></td>
                <td>${escapeHtml(rider.phone)}</td>
                <td><code>${escapeHtml(rider.memberId)}</code></td>
                <td><span class="badge ${badgeClass}">${rider.status}</span></td>
                <td>
                    <button class="btn btn-action btn-toggle" data-id="${rider.id}" data-status="${rider.status}">Toggle Status</button>
                    <button class="btn btn-action btn-delete" data-id="${rider.id}">Delete</button>
                </td>
            `;
            tableBody.appendChild(tr);
        });

        document.querySelectorAll('.btn-toggle').forEach(btn => {
            btn.addEventListener('click', (e) => {
                toggleTaxStatus(e.target.getAttribute('data-id'), e.target.getAttribute('data-status'));
            });
        });

        document.querySelectorAll('.btn-delete').forEach(btn => {
            btn.addEventListener('click', (e) => {
                deleteRider(e.target.getAttribute('data-id'));
            });
        });
    }

    // Database Operations
    function handleAddRider(e) {
        e.preventDefault();

        const newRider = {
            name: document.getElementById('riderName').value.trim(),
            phone: document.getElementById('riderPhone').value.trim(),
            memberId: document.getElementById('membershipId').value.trim(),
            status: document.getElementById('taxStatus').value,
            createdAt: Date.now()
        };

        push(ridersRef, newRider)
            .then(() => form.reset())
            .catch(error => alert("Failed to save rider: " + error.message));
    }

    function toggleTaxStatus(id, currentStatus) {
        const newStatus = currentStatus === 'Paid' ? 'Pending' : 'Paid';
        update(ref(db, `riders/${id}`), { status: newStatus });
    }

    function deleteRider(id) {
        if (confirm('Are you sure you want to remove this rider from the database?')) {
            remove(ref(db, `riders/${id}`));
        }
    }

    // Helper to prevent HTML Injection
    function escapeHtml(str) {
        return (str || '').replace(/[&<>"']/g, match => ({
            '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
        }[match]));
    }
</script>
Run these code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions