buildtracker-pro / index.html
FFriZz's picture
<!DOCTYPE html>
6298011 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>BuildTracker Pro - Construction Hours Logger</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.net.min.js"></script>
<style>
:root {
--primary: #3b82f6;
--primary-dark: #2563eb;
--secondary: #10b981;
--secondary-dark: #059669;
--accent: #f59e0b;
--card: #ffffff;
--card-dark: #1f2937;
--text: #111827;
--text-dark: #f9fafb;
--muted: #6b7280;
--muted-dark: #9ca3af;
--border: #e5e7eb;
--border-dark: #374151;
--border-strong: #d1d5db;
--border-strong-dark: #4b5563;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-dark: 0 4px 6px rgba(0, 0, 0, 0.3);
}
body {
font-family: 'Inter', sans-serif;
background-color: #f9fafb;
color: var(--text);
transition: all 0.3s ease;
}
body.dark {
background-color: #111827;
color: var(--text-dark);
}
.card {
background-color: var(--card);
border-radius: 12px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
transition: all 0.3s ease;
}
.dark .card {
background-color: var(--card-dark);
box-shadow: var(--shadow-dark);
border-color: var(--border-dark);
}
.btn {
transition: all 0.2s ease;
border-radius: 8px;
font-weight: 500;
display: inline-flex;
align-items: center;
gap: 4px;
}
.btn-primary {
background-color: var(--primary);
color: white;
border: none;
}
.btn-primary:hover {
background-color: var(--primary-dark);
transform: translateY(-1px);
}
.btn-secondary {
background-color: var(--secondary);
color: white;
border: none;
}
.btn-secondary:hover {
background-color: var(--secondary-dark);
transform: translateY(-1px);
}
.btn-danger {
background-color: #ef4444;
color: white;
border: none;
}
.btn-danger:hover {
background-color: #dc2626;
transform: translateY(-1px);
}
.btn-ghost {
background-color: transparent;
color: var(--primary);
border: 1px solid var(--primary);
}
.btn-ghost:hover {
background-color: rgba(59, 130, 246, 0.1);
transform: translateY(-1px);
}
.dark .btn-ghost {
color: #60a5fa;
border-color: #60a5fa;
}
.dark .btn-ghost:hover {
background-color: rgba(96, 165, 250, 0.1);
}
.input-field {
border: 2px solid var(--border);
border-radius: 8px;
padding: 12px;
transition: all 0.2s ease;
background-color: white;
}
.input-field:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.dark .input-field {
background-color: #374151;
border-color: var(--border-dark);
color: white;
}
.dark .input-field:focus {
border-color: #60a5fa;
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
}
.day-card {
border: 2px solid var(--border-strong);
border-radius: 10px;
transition: all 0.2s ease;
}
.dark .day-card {
border-color: var(--border-strong-dark);
}
.day-card:hover {
border-color: var(--primary);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.dark .day-card:hover {
border-color: #60a5fa;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.vanta-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.glass-effect {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass-effect {
background: rgba(17, 24, 39, 0.85);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.logo-container {
background: linear-gradient(135deg, #3b82f6, #10b981);
border-radius: 20px;
padding: 4px;
display: inline-block;
}
.logo-inner {
background: white;
border-radius: 16px;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.dark .logo-inner {
background: #1f2937;
}
</style>
</head>
<body class="min-h-screen">
<!-- Vanta.js Background -->
<div id="vanta-bg" class="vanta-bg"></div>
<!-- Notification Toast -->
<div id="notification" class="fixed left-1/2 transform -translate-x-1/2 -top-16 transition-all duration-300 z-50 px-4 py-3 rounded-lg shadow-lg bg-white border border-gray-200 hidden"></div>
<!-- Theme Toggle -->
<div class="fixed top-4 right-4 flex items-center gap-2 z-50">
<div class="theme-toggle relative w-12 h-6 rounded-full bg-gray-200 cursor-pointer" id="themeToggle">
<div class="knob absolute top-1 left-1 w-4 h-4 rounded-full bg-white transition-transform"></div>
</div>
<span class="theme-toggle-label text-sm font-medium">Dark</span>
</div>
<!-- Auth Section -->
<div id="authShell" class="min-h-screen flex flex-col items-center justify-center p-4">
<div id="topLogo" class="mb-8">
<div class="logo-container">
<div class="logo-inner">
<img src="https://i.ibb.co/HLKfKpvC/DDFGTDGDsdfsdf-G.png" alt="BuildTracker Pro Logo" class="w-64 max-w-full">
</div>
</div>
</div>
<!-- Login Form -->
<div id="loginSection" class="w-full max-w-md">
<div class="card p-8 glass-effect">
<h1 class="text-2xl font-bold text-center mb-6 text-gray-800 dark:text-white">Sign In</h1>
<div class="space-y-4">
<div class="flex flex-col sm:flex-row gap-3">
<input type="text" id="firstName" placeholder="First name" class="input-field flex-1 min-w-0">
<input type="text" id="lastName" placeholder="Last name" class="input-field flex-1 min-w-0">
</div>
<input type="password" id="pinUnique" placeholder="PIN" class="input-field w-full">
<button id="loginButton" class="btn btn-primary w-full py-3 rounded-lg font-medium">Login</button>
<button id="createAccountButton" class="btn btn-ghost w-full py-2 text-sm">Create Account</button>
<p class="text-center text-sm text-gray-500 dark:text-gray-400">Access limited to registered users</p>
</div>
</div>
</div>
<!-- Create Account Form -->
<div id="createAccountSection" class="w-full max-w-md hidden">
<div class="card p-8 glass-effect">
<h1 class="text-2xl font-bold text-center mb-6 text-gray-800 dark:text-white">Create Account</h1>
<div class="space-y-4">
<div class="flex flex-col sm:flex-row gap-3">
<input type="text" id="createAccountFirstNameUnique" placeholder="First name" class="input-field flex-1 min-w-0">
<input type="text" id="createAccountLastNameUnique" placeholder="Last name" class="input-field flex-1 min-w-0">
</div>
<input type="password" id="createAccountPinUnique" placeholder="PIN (4+ digits)" class="input-field w-full">
<input type="password" id="createAccountRePinUnique" placeholder="Re-enter PIN" class="input-field w-full">
<button id="submitButton" class="btn btn-primary w-full py-3 rounded-lg font-medium">Submit</button>
<button id="backButton" class="btn btn-danger w-full py-2 text-sm">Back</button>
</div>
</div>
</div>
</div>
<!-- Main App Content -->
<div id="mainContent" class="hidden container mx-auto p-4 max-w-6xl">
<!-- Impersonation Banner -->
<div id="impersonationBanner" class="sticky top-0 z-40 bg-yellow-50 border-b border-yellow-300 text-yellow-800 p-3 hidden dark:bg-yellow-900 dark:border-yellow-700 dark:text-yellow-200">
<div class="container mx-auto flex justify-between items-center">
<span>Impersonating <strong id="impersonatingName"></strong></span>
<button id="stopImpersonateBtn" class="btn btn-danger px-3 py-1 text-sm">Stop</button>
</div>
</div>
<!-- Header -->
<div class="text-center my-8">
<div class="logo-container inline-block">
<div class="logo-inner">
<img src="https://i.ibb.co/HLKfKpvC/DDFGTDGDsdfsdf-G.png" alt="BuildTracker Pro Logo" class="w-64 mx-auto">
</div>
</div>
</div>
<!-- Period Controls -->
<div class="flex flex-col items-center mb-8">
<div class="card p-4 w-full max-w-2xl flex items-center justify-between glass-effect">
<select id="payPeriodSelect" class="input-field flex-1"></select>
<button id="hamburgerBtn" class="ml-3 p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700">
<i data-feather="menu"></i>
</button>
</div>
<div id="periodActionsRow" class="flex flex-wrap justify-center gap-3 mt-4">
<button id="menuEditPeriod" class="btn btn-ghost flex items-center gap-2">
<i data-feather="edit"></i> Edit
</button>
<button id="menuAddPeriod" class="btn btn-ghost flex items-center gap-2">
<i data-feather="plus"></i> New
</button>
<button id="menuRemovePeriods" class="btn btn-ghost flex items-center gap-2">
<i data-feather="trash-2"></i> Remove
</button>
<button id="menuDaysManager" class="btn btn-ghost flex items-center gap-2">
<i data-feather="calendar"></i> Days
</button>
<button id="openAdminBtn" class="btn btn-ghost flex items-center gap-2 hidden">
<i data-feather="shield"></i> Admin
</button>
</div>
</div>
<!-- Period Editor -->
<div id="dateEditContainer" class="card p-6 mb-8 max-w-2xl mx-auto glass-effect hidden">
<h4 class="text-lg font-semibold text-center mb-3 text-gray-800 dark:text-white">Pay Period Date Select</h4>
<p class="text-sm text-gray-500 text-center mb-6">Select the start and end dates for the pay period</p>
<div class="flex flex-wrap justify-center gap-6 mb-6">
<div>
<label class="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">Start</label>
<input type="date" id="startDateInput" class="input-field">
</div>
<div>
<label class="block text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">End</label>
<input type="date" id="endDateInput" class="input-field">
</div>
</div>
<div class="flex justify-center gap-3">
<button id="savePeriodBtn" class="btn btn-primary px-6 py-2">Save</button>
<button id="cancelPeriodBtn" class="btn btn-ghost px-6 py-2">Cancel</button>
</div>
</div>
<!-- Main Content Sections -->
<div id="dayTables" class="card p-6 mb-8 glass-effect">
<h3 class="text-xl font-bold mb-4 text-gray-800 dark:text-white">Daily Time Tracking</h3>
<!-- Day cards will be dynamically inserted here with visible borders -->
</div>
<div id="jobSummary" class="card p-6 mb-8 glass-effect">
<h3 class="text-xl font-bold mb-4 text-gray-800 dark:text-white">Job Summary</h3>
<!-- Job summary content will be dynamically inserted here -->
</div>
<div id="adminAggregate" class="card p-6 mb-8 glass-effect hidden">
<h3 class="text-xl font-bold mb-4 text-gray-800 dark:text-white">Admin Overview</h3>
<!-- Admin aggregate content will be dynamically inserted here -->
</div>
<!-- Action Buttons -->
<div id="mainActionRow" class="flex justify-center gap-4 mb-8 hidden">
<button id="generateInvoice" class="btn btn-primary px-6 py-3 flex items-center gap-2">
<i data-feather="file-text"></i> Generate Invoice
</button>
<button id="toggleLogs" class="btn btn-ghost px-6 py-3 flex items-center gap-2">
<i data-feather="list"></i> Show Logs
</button>
<button id="toggleMeta" class="btn btn-ghost px-6 py-3 flex items-center gap-2">
<i data-feather="info"></i> Show Meta Data
</button>
</div>
<!-- Log and Meta Panels -->
<div id="logPanel" class="card p-6 mb-8 max-h-48 overflow-auto glass-effect hidden">
<h4 class="text-lg font-semibold mb-3 text-gray-800 dark:text-white">Activity Log</h4>
<!-- Log content will be dynamically inserted here -->
</div>
<div id="metaPanel" class="card p-6 mb-8 max-h-56 overflow-auto glass-effect hidden whitespace-pre">
<h4 class="text-lg font-semibold mb-3 text-gray-800 dark:text-white">Meta Data</h4>
<!-- Meta data content will be dynamically inserted here -->
</div>
<!-- Footer -->
<div class="text-center mt-12 mb-6">
<button id="footerLogout" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hidden flex items-center gap-2 mx-auto">
<i data-feather="log-out"></i> Logout
</button>
</div>
</div>
<!-- Modals -->
<div id="descModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="card p-6 max-w-md w-full mx-4 glass-effect">
<h2 class="text-xl font-bold text-center mb-4 text-gray-800 dark:text-white">Description</h2>
<div id="descModalText" class="mb-4 break-words text-gray-700 dark:text-gray-300"></div>
<div class="text-center">
<button id="closeDescModal" class="btn btn-ghost px-4 py-2">Close</button>
</div>
</div>
</div>
<div id="removePeriodsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="card p-6 max-w-md w-full mx-4 glass-effect">
<h3 class="text-lg font-bold text-center mb-2 text-gray-800 dark:text-white">Select Pay Periods to Delete</h3>
<p class="text-sm text-gray-500 text-center mb-4">Check the box for any pay periods you want to remove</p>
<div id="removePeriodsList" class="space-y-2 mb-4"></div>
<div class="flex justify-center gap-3">
<button id="confirmRemovePeriods" class="btn btn-danger px-4 py-2">Delete</button>
<button id="cancelRemovePeriods" class="btn btn-ghost px-4 py-2">Cancel</button>
</div>
</div>
</div>
<div id="daysWorkedModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="card p-6 max-w-md w-full mx-4 glass-effect">
<h2 class="text-xl font-bold text-center mb-4 text-gray-800 dark:text-white">Days Worked Manager</h2>
<div id="daysWorkedList" class="mb-4"></div>
<div class="flex justify-center gap-3">
<button id="saveDaysWorkedModal" class="btn btn-primary px-4 py-2">Save</button>
<button id="closeDaysWorkedModal" class="btn btn-ghost px-4 py-2">Cancel</button>
</div>
</div>
</div>
<div id="adminModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="card p-6 w-full max-w-4xl glass-effect">
<h2 class="text-xl font-bold text-center mb-2 text-gray-800 dark:text-white">Admin Panel</h2>
<p class="text-sm text-gray-500 text-center mb-4">Manage users, roles, and data</p>
<div class="flex justify-center gap-3 mb-4">
<button id="adminCreateUser" class="btn btn-primary px-4 py-2 flex items-center gap-2">
<i data-feather="user-plus"></i> New User
</button>
<button id="adminRefresh" class="btn btn-ghost px-4 py-2 flex items-center gap-2">
<i data-feather="refresh-cw"></i> Refresh
</button>
<button id="adminClose" class="btn btn-danger px-4 py-2 flex items-center gap-2">
<i data-feather="x"></i> Close
</button>
</div>
<div id="adminUsers" class="overflow-auto max-h-96"></div>
</div>
</div>
<!-- Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.25/jspdf.plugin.autotable.min.js"></script>
<script>
// Initialize Vanta.js background
let vantaEffect;
document.addEventListener('DOMContentLoaded', function() {
// Initialize Vanta.js
vantaEffect = VANTA.NET({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: 0x3b82f6,
backgroundColor: 0xf9fafb,
points: 12.00,
maxDistance: 22.00,
spacing: 18.00
});
// Initialize feather icons
feather.replace();
// Set light theme by default
document.documentElement.classList.remove('dark');
document.querySelector('.theme-toggle-label').textContent = 'Light';
// Theme toggle functionality
document.getElementById('themeToggle').addEventListener('click', function() {
const html = document.documentElement;
const isDark = html.classList.toggle('dark');
document.querySelector('.theme-toggle-label').textContent = isDark ? 'Dark' : 'Light';
// Update Vanta.js background for theme
if (vantaEffect) {
vantaEffect.destroy();
}
vantaEffect = VANTA.NET({
el: "#vanta-bg",
mouseControls: true,
touchControls: true,
gyroControls: false,
minHeight: 200.00,
minWidth: 200.00,
scale: 1.00,
scaleMobile: 1.00,
color: isDark ? 0x60a5fa : 0x3b82f6,
backgroundColor: isDark ? 0x111827 : 0xf9fafb,
points: 12.00,
maxDistance: 22.00,
spacing: 18.00
});
// Update knob position
const knob = document.querySelector('.theme-toggle .knob');
knob.style.transform = isDark ? 'translateX(24px)' : 'translateX(0)';
});
});
// Notification function
function showNotification(message, type = 'info') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `fixed left-1/2 transform -translate-x-1/2 top-4 transition-all duration-300 z-50 px-4 py-3 rounded-lg shadow-lg ${
type === 'error' ? 'bg-red-50 border-red-500 text-red-700' :
type === 'success' ? 'bg-green-50 border-green-500 text-green-700' :
'bg-blue-50 border-blue-500 text-blue-700'
}`;
setTimeout(() => {
notification.classList.add('hidden');
}, 3000);
}
// Example day card structure (for reference)
function createDayCard(dayData) {
return `
<div class="day-card p-4 mb-4">
<div class="flex justify-between items-center mb-3">
<h4 class="font-semibold text-lg">${dayData.date}</h4>
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-sm font-medium dark:bg-blue-900 dark:text-blue-200">
${dayData.hours} hours
</span>
</div>
<div class="space-y-2">
${dayData.jobs.map(job => `
<div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg dark:bg-gray-800">
<span class="font-medium">${job.name}</span>
<span class="text-gray-600 dark:text-gray-400">${job.hours}h</span>
</div>
`).join('')}
</div>
</div>
`;
}
</script>
<!-- App scripts would be loaded here -->
<script src="./js/globals.js"></script>
<script src="./js/helpers.js"></script>
<script src="./js/periods.js"></script>
<script src="./js/data.js"></script>
<script src="./js/admin_aggregate.js"></script>
<script src="./js/render.js"></script>
<script src="./js/job_actions.js"></script>
<script src="./js/period_controls.js"></script>
<script src="./js/invoice.js"></script>
<script src="./js/auth_admin.js"></script>
<script src="./js/init.js"></script>
</body>
</html>