Mass UI Updates

This commit is contained in:
2025-12-15 14:54:29 -06:00
parent cf1ffac9ee
commit 13bb6542bc
11 changed files with 3060 additions and 121 deletions
+853
View File
@@ -0,0 +1,853 @@
================================================================================
LIQUID GLASS DESIGN SYSTEM PLAYBOOK
Apple-Inspired Glassmorphism
================================================================================
OVERVIEW
--------
This playbook documents the liquid glass (glassmorphism) design system used
throughout the Job-Pages application. This design creates a premium, Apple-like
aesthetic using frosted glass effects, subtle transparency, and smooth transitions.
Last Updated: 2024
Version: 2.0
================================================================================
CORE PRINCIPLES
================================================================================
1. TRANSPARENCY & BLUR
- Use backdrop-blur for frosted glass effects
- Semi-transparent backgrounds (white/40, white/60, white/70, white/80)
- Subtle borders with white/20 to white/40 opacity
- Gradient background provides depth: from-gray-50 via-blue-50/30 to-indigo-50/40
2. DEPTH & SHADOWS
- Layered shadows (shadow-sm, shadow-lg, shadow-xl, shadow-2xl)
- Colored shadows for interactive elements (shadow-indigo-500/30, shadow-indigo-500/50)
- Hover states increase shadow intensity
- Shadow-lg for buttons, shadow-xl for cards, shadow-2xl for modals/dropdowns
3. SMOOTH TRANSITIONS
- All interactive elements use transition-all or transition-colors
- Scale effects on hover (hover:scale-105 for avatars, hover:scale-[1.02] for cards)
- Opacity changes for hover states
- Duration-300 for smooth animations
4. ROUNDED CORNERS
- Standard: rounded-xl (12px) - buttons, inputs
- Large cards: rounded-2xl (16px) - main containers, cards
- Modals: rounded-3xl (24px) - modal content
- Full rounded: rounded-full (for avatars)
================================================================================
BACKGROUND SYSTEM
================================================================================
MAIN PAGE BACKGROUND
--------------------
Class: bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40
Purpose: Creates a subtle gradient that provides depth and visual interest
Usage: Applied to <body> element on all pages
Example:
<body class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40">
This gradient creates the foundation for the glassmorphism effect, allowing
transparent elements to show through with a beautiful backdrop.
================================================================================
NAVIGATION BAR
================================================================================
STANDARD NAVIGATION
-------------------
Classes:
- backdrop-blur-xl
- bg-white/70
- border-b border-white/20
- shadow-sm
- sticky top-0 z-40
Purpose: Creates a frosted glass navigation that stays at the top
Container:
- max-w-7xl mx-auto
- px-4 sm:px-6 lg:px-8
- py-4 sm:py-4 (consistent padding on all screen sizes)
- px-2 (additional horizontal padding)
Example:
<nav class="backdrop-blur-xl bg-white/70 border-b border-white/20 shadow-sm sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row justify-between items-center h-16 sm:h-auto py-4 sm:py-4 gap-4 sm:gap-0 px-2">
<!-- Navigation content -->
</div>
</div>
</nav>
NAVIGATION LINKS
----------------
Classes:
- text-indigo-600
- hover:text-indigo-700
- font-medium
- text-sm sm:text-base
- transition-colors
Example:
<a href="dashboard.html" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base transition-colors">← Dashboard</a>
================================================================================
CARDS & CONTAINERS
================================================================================
STANDARD CARD
-------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- p-6
Purpose: Main content cards with glassmorphism effect
Example:
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
CARD WITH HOVER EFFECT
----------------------
Additional classes:
- hover:bg-white/70
- hover:shadow-2xl
- hover:border-white/50
- hover:scale-[1.02]
- transition-all duration-300
- cursor-pointer
Example:
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6
hover:bg-white/70 hover:shadow-2xl hover:border-white/50 hover:scale-[1.02]
transition-all duration-300 cursor-pointer">
SEARCH/FILTER CONTAINER
-----------------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- p-6
- mb-6
Example:
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 mb-6">
MODAL/CONTAINER
---------------
Classes:
- backdrop-blur-2xl
- bg-white/80
- rounded-3xl
- shadow-2xl
- border border-white/30
Backdrop:
- backdrop-blur-sm
- bg-black/20
Example:
<div class="fixed inset-0 backdrop-blur-sm bg-black/20 z-50">
<div class="backdrop-blur-2xl bg-white/80 rounded-3xl shadow-2xl border border-white/30">
<!-- Modal content -->
</div>
</div>
QUICK INFO CARD (Smaller card)
-------------------------------
Classes:
- backdrop-blur-xl
- bg-white/40
- rounded-2xl
- p-4
- border border-white/30
- shadow-lg
Example:
<div class="backdrop-blur-xl bg-white/40 rounded-2xl p-4 border border-white/30 shadow-lg">
================================================================================
BUTTONS
================================================================================
PRIMARY BUTTON (ACTIVE STATE)
------------------------------
Classes:
- backdrop-blur-sm
- bg-indigo-600/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-indigo-700/90
- hover:shadow-xl
- transition-all
Example:
<button class="backdrop-blur-sm bg-indigo-600/90 text-white rounded-xl shadow-lg
hover:bg-indigo-700/90 hover:shadow-xl transition-all">
SECONDARY BUTTON (INACTIVE STATE)
----------------------------------
Classes:
- backdrop-blur-sm
- text-gray-700
- rounded-xl
- hover:bg-white/40
- transition-all
Example:
<button class="backdrop-blur-sm text-gray-700 rounded-xl hover:bg-white/40 transition-all">
BUTTON GROUP CONTAINER (View Toggle)
-------------------------------------
Classes:
- inline-flex
- rounded-2xl
- border border-white/30
- backdrop-blur-xl
- bg-white/60
- p-1
- shadow-lg
Example:
<div class="inline-flex rounded-2xl border border-white/30 backdrop-blur-xl bg-white/60 p-1 shadow-lg">
<!-- Toggle buttons inside -->
</div>
VIEW TOGGLE BUTTONS
-------------------
Active Button:
- backdrop-blur-sm
- bg-indigo-600/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-indigo-700/90
- transition-all
Inactive Button:
- backdrop-blur-sm
- text-gray-700
- rounded-xl
- hover:bg-white/40
- transition-all
FORM BUTTONS
------------
Save Button:
- backdrop-blur-sm
- bg-indigo-600/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-indigo-700/90
- hover:shadow-xl
- transition-all
Cancel Button:
- backdrop-blur-sm
- bg-white/60
- border border-white/40
- text-gray-700
- rounded-xl
- shadow-lg
- hover:bg-white/80
- hover:shadow-xl
- transition-all
MAP BUTTONS (Apple Maps / Google Maps)
---------------------------------------
Apple Maps:
- backdrop-blur-sm
- bg-gray-900/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-gray-800/90
- hover:shadow-xl
Google Maps:
- backdrop-blur-sm
- bg-blue-600/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-blue-700/90
- hover:shadow-xl
================================================================================
FORM ELEMENTS
================================================================================
INPUT FIELDS
------------
Classes:
- border border-white/40
- rounded-xl
- backdrop-blur-sm
- bg-white/40
- focus:ring-2
- focus:ring-indigo-500/50
- focus:border-indigo-300/50
- transition-all
- shadow-sm
- px-3 py-2 (or px-4 py-3 for larger inputs)
Example:
<input class="w-full px-3 py-2 border border-white/40 rounded-xl backdrop-blur-sm bg-white/40
focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-300/50
transition-all shadow-sm">
SEARCH INPUT
------------
Classes:
- Same as input fields
- Larger padding: px-4 py-3
- text-base
Example:
<input class="w-full px-4 py-3 border border-white/40 rounded-xl backdrop-blur-sm bg-white/40
focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-300/50
outline-none text-base transition-all shadow-sm">
SELECT DROPDOWNS
----------------
Same classes as input fields
Example:
<select class="w-full px-3 py-2 border border-white/40 rounded-xl backdrop-blur-sm bg-white/40
focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-300/50
transition-all shadow-sm">
================================================================================
AVATARS & ICONS
================================================================================
AVATAR BUTTON
-------------
Classes:
- rounded-full
- bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500
- w-10 h-10
- shadow-lg shadow-indigo-500/30
- hover:scale-105
- hover:shadow-indigo-500/50
- transition-all duration-300
- flex items-center justify-center
- text-white
Purpose: User avatar with gradient background and smooth hover effect
Example:
<button class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500
shadow-lg shadow-indigo-500/30 hover:scale-105 hover:shadow-indigo-500/50
transition-all duration-300 flex items-center justify-center text-white">
<span class="text-xs font-semibold">IC</span>
</button>
AVATAR INITIALS
---------------
Classes:
- text-xs
- font-semibold
- text-white
================================================================================
DROPDOWNS & MENUS
================================================================================
DROPDOWN MENU
-------------
Classes:
- backdrop-blur-xl
- bg-white/80
- rounded-2xl
- shadow-2xl
- border border-white/30
- py-2
- overflow-hidden
- absolute right-0 mt-2
- z-50
- w-48
Purpose: User dropdown menu with glassmorphism
Example:
<div class="hidden absolute right-0 mt-2 w-48 backdrop-blur-xl bg-white/80 rounded-2xl
shadow-2xl border border-white/30 py-2 z-50 overflow-hidden">
DROPDOWN HEADER
---------------
Classes:
- px-4 py-3
- border-b border-white/20
Example:
<div class="px-4 py-3 border-b border-white/20">
<p class="text-xs text-gray-500 font-medium">Signed in as</p>
<p class="text-sm font-semibold text-gray-900 truncate mt-0.5">user@example.com</p>
</div>
DROPDOWN ITEM
-------------
Classes:
- px-4 py-2.5
- text-sm
- text-gray-700
- hover:bg-white/40
- transition-colors
- font-medium
Example:
<a class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 transition-colors font-medium">
Preferences
</a>
DESTRUCTIVE DROPDOWN ITEM
--------------------------
Classes:
- w-full text-left
- px-4 py-2.5
- text-sm
- text-red-600
- hover:bg-red-50/50
- transition-colors
- font-medium
Example:
<button class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50/50
transition-colors font-medium">
Logout
</button>
================================================================================
TABLES
================================================================================
TABLE CONTAINER
---------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- overflow-hidden
Example:
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 overflow-hidden">
TABLE HEADER
------------
Classes:
- backdrop-blur-sm
- bg-white/40
Example:
<thead class="backdrop-blur-sm bg-white/40">
TABLE BODY
----------
Classes:
- bg-white/30
- divide-y divide-white/20
Example:
<tbody class="bg-white/30 divide-y divide-white/20">
TABLE CELLS
-----------
Standard cells:
- px-3 sm:px-6
- py-4
- text-sm
- text-gray-900
Header cells:
- px-3 sm:px-6
- py-2 sm:py-3
- text-xs
- font-medium
- text-gray-500
- uppercase
- tracking-wider
================================================================================
MODALS & OVERLAYS
================================================================================
MODAL BACKDROP
--------------
Classes:
- fixed inset-0
- backdrop-blur-sm
- bg-black/20
- overflow-y-auto
- z-50
Example:
<div class="fixed inset-0 backdrop-blur-sm bg-black/20 overflow-y-auto z-50">
MODAL CONTENT
-------------
Classes:
- relative top-0 sm:top-10
- mx-auto
- p-4 sm:p-6
- w-full sm:max-w-7xl (or sm:max-w-4xl for smaller modals)
- backdrop-blur-2xl
- bg-white/80
- rounded-none sm:rounded-3xl
- shadow-2xl
- border border-white/30
- mb-0 sm:mb-10
- min-h-screen sm:min-h-[600px]
Example:
<div class="relative top-0 sm:top-10 mx-auto p-4 sm:p-6 w-full sm:max-w-7xl
backdrop-blur-2xl bg-white/80 rounded-none sm:rounded-3xl shadow-2xl
border border-white/30 mb-0 sm:mb-10 min-h-screen sm:min-h-[600px]">
MODAL CARDS (Inside modal)
---------------------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- p-6
- mb-6
Example:
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 mb-6">
================================================================================
PREFERENCES PAGE
================================================================================
PREFERENCES CARD
----------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- p-6
PREFERENCE SECTION DIVIDER
---------------------------
Classes:
- border-b border-white/20
- py-4
RADIO BUTTON OPTIONS
--------------------
Container:
- flex flex-col sm:flex-row
- gap-4
Option Label:
- flex items-center
- p-4
- border-2 border-white/30
- backdrop-blur-sm
- bg-white/40
- rounded-2xl
- cursor-pointer
- hover:border-indigo-500/50
- hover:bg-white/60
- transition-all
- shadow-sm
Selected State (via JavaScript):
- border-indigo-500
- bg-indigo-50/50
- shadow-md
Example:
<label class="view-option flex items-center p-4 border-2 border-white/30 backdrop-blur-sm
bg-white/40 rounded-2xl cursor-pointer hover:border-indigo-500/50
hover:bg-white/60 transition-all shadow-sm" data-value="table">
<input type="radio" name="defaultView" value="table" class="sr-only">
<!-- Content -->
</label>
================================================================================
OPACITY LEVELS REFERENCE
================================================================================
BACKGROUND OPACITIES:
- /20 = Very subtle borders and dividers (border-white/20)
- /30 = Standard borders and dividers (border-white/30)
- /40 = Input backgrounds, table rows, option backgrounds (bg-white/40)
- /50 = Hover states, focus rings, selected states (hover:bg-white/50, bg-indigo-50/50)
- /60 = Standard card backgrounds (bg-white/60)
- /70 = Navigation bar (bg-white/70)
- /80 = Dropdowns, modals (bg-white/80)
- /90 = Button backgrounds active state (bg-indigo-600/90, bg-gray-900/90)
BLUR LEVELS:
- backdrop-blur-sm = Subtle blur (4px) - inputs, buttons, table headers
- backdrop-blur-xl = Standard blur (24px) - cards, navigation, dropdowns
- backdrop-blur-2xl = Strong blur (40px) - modals, overlays
================================================================================
SHADOW LEVELS REFERENCE
================================================================================
- shadow-sm = Small shadow (1px) - subtle elevation
- shadow-lg = Large shadow (10px) - buttons, active states, containers
- shadow-xl = Extra large (20px) - cards, main containers
- shadow-2xl = 2X extra large (25px) - modals, dropdowns
COLORED SHADOWS:
- shadow-indigo-500/30 = Subtle colored shadow (avatar default)
- shadow-indigo-500/50 = More prominent colored shadow (avatar hover)
================================================================================
TRANSITION PATTERNS
================================================================================
STANDARD TRANSITION
-------------------
Classes: transition-all duration-300
SMOOTH HOVER
------------
Classes: transition-all duration-300 hover:scale-105
BUTTON TRANSITION
-----------------
Classes: transition-all (includes color, shadow, scale changes)
COLOR TRANSITION
----------------
Classes: transition-colors (for hover state color changes only)
================================================================================
COLOR PALETTE
================================================================================
PRIMARY COLORS:
- indigo-600/90 = Primary button background
- indigo-700/90 = Primary button hover
- indigo-500/30 = Shadow color (subtle)
- indigo-500/50 = Focus ring, stronger shadow, selected states
- indigo-300/50 = Focus border color
GRADIENT COLORS (Avatar):
- from-indigo-500 = Start of gradient
- via-purple-500 = Middle of gradient
- to-pink-500 = End of gradient
TEXT COLORS:
- text-gray-500 = Secondary text, labels
- text-gray-600 = Body text, descriptions
- text-gray-700 = Standard text (inactive buttons, dropdown items)
- text-gray-900 = Headings, important text, dropdown headers
- text-white = Text on colored backgrounds
- text-indigo-600 = Links, active states
- text-indigo-700 = Link hover state
- text-red-600 = Destructive actions (logout)
BORDER COLORS:
- border-white/20 = Subtle borders (navigation, dividers)
- border-white/30 = Standard borders (cards, containers)
- border-white/40 = Input borders
- border-indigo-300/50 = Focus borders
- border-indigo-500 = Selected state borders
BACKGROUND GRADIENT:
- from-gray-50 = Light gray start
- via-blue-50/30 = Light blue middle (30% opacity)
- to-indigo-50/40 = Light indigo end (40% opacity)
================================================================================
COMMON PATTERNS
================================================================================
SEARCH/FILTER CONTAINER
-----------------------
Classes:
- backdrop-blur-xl
- bg-white/60
- rounded-2xl
- shadow-xl
- border border-white/30
- p-6
- mb-6
VIEW TOGGLE BUTTONS
-------------------
Container:
- inline-flex
- rounded-2xl
- border border-white/30
- backdrop-blur-xl
- bg-white/60
- p-1
- shadow-lg
Active Button:
- backdrop-blur-sm
- bg-indigo-600/90
- text-white
- rounded-xl
- shadow-lg
- hover:bg-indigo-700/90
- transition-all
Inactive Button:
- backdrop-blur-sm
- text-gray-700
- rounded-xl
- hover:bg-white/40
- transition-all
MESSAGE CONTAINER
-----------------
Success Message:
- bg-green-50
- border-green-200
- text-green-700
- border
- px-4 py-3
- rounded-xl
- text-sm
- backdrop-blur-sm
Error Message:
- bg-red-50
- border-red-200
- text-red-700
- border
- px-4 py-3
- rounded-xl
- text-sm
- backdrop-blur-sm
================================================================================
RESPONSIVE DESIGN
================================================================================
BREAKPOINTS:
- sm: 640px
- md: 768px
- lg: 1024px
MOBILE (< 640px):
- Single column layouts
- Full-width buttons
- Stacked navigation elements
- Full-screen modals (rounded-none)
- Smaller text sizes (text-sm)
- Reduced padding
TABLET (640px - 1024px):
- Two-column grids where applicable
- Horizontal navigation
- Constrained modals (rounded-3xl)
- Medium text sizes
DESKTOP (> 1024px):
- Multi-column layouts
- Max-width containers (max-w-7xl)
- Side-by-side elements
- Larger text and spacing
================================================================================
IMPLEMENTATION CHECKLIST
================================================================================
When creating a new component with liquid glass style:
[ ] 1. Apply appropriate backdrop-blur level (sm, xl, or 2xl)
[ ] 2. Set semi-transparent background (white/40 to white/80 based on context)
[ ] 3. Add rounded corners (rounded-xl for buttons/inputs, rounded-2xl for cards)
[ ] 4. Include shadow (shadow-lg for buttons, shadow-xl for cards, shadow-2xl for modals)
[ ] 5. Add subtle border (border-white/20 to border-white/40)
[ ] 6. Add transition-all or transition-colors for smooth interactions
[ ] 7. Include hover states with increased opacity/shadow/scale
[ ] 8. Test on gradient background to ensure glass effect is visible
[ ] 9. Ensure proper z-index layering (z-40 for nav, z-50 for modals/dropdowns)
[ ] 10. Verify responsive behavior on mobile, tablet, and desktop
================================================================================
ACCESSIBILITY NOTES
================================================================================
- Ensure sufficient contrast for text readability
- Focus states should be clearly visible (focus:ring-2 focus:ring-indigo-500/50)
- Interactive elements should have clear hover feedback
- Maintain WCAG AA contrast ratios (4.5:1 for normal text)
- Use semantic HTML elements
- Provide keyboard navigation support
- Screen reader friendly labels and ARIA attributes where needed
================================================================================
BROWSER COMPATIBILITY
================================================================================
backdrop-blur requires:
- Chrome 76+
- Firefox 103+
- Safari 9+
- Edge 79+
For older browsers, consider:
- Fallback solid backgrounds
- CSS filters as alternative
- Progressive enhancement approach
================================================================================
PERFORMANCE CONSIDERATIONS
================================================================================
- backdrop-blur can be performance-intensive
- Use sparingly on mobile devices
- Consider will-change: transform for animated elements
- Test on lower-end devices
- Limit the number of blurred layers
- Use backdrop-blur-sm for smaller elements when possible
================================================================================
CURRENT IMPLEMENTATION STATUS
================================================================================
PAGES IMPLEMENTED:
✓ Dashboard (dashboard.html)
✓ Jobs Page (jobs.html)
✓ Notes Page (notes.html)
✓ Preferences Page (preferences.html)
✓ Login Page (index.html) - standard design, not glassmorphism
COMPONENTS IMPLEMENTED:
✓ Navigation bar
✓ User avatar with dropdown
✓ Cards and containers
✓ Search/filter sections
✓ View toggle buttons
✓ Tables
✓ Modals
✓ Form inputs and selects
✓ Buttons (all states)
✓ Preferences page with radio options
================================================================================
END OF PLAYBOOK
================================================================================
+1187
View File
File diff suppressed because it is too large Load Diff
+72 -18
View File
@@ -7,23 +7,45 @@
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/pocketbase@latest/dist/pocketbase.umd.js"></script>
</head>
<body class="min-h-screen bg-gray-50">
<body class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40">
<!-- Navigation Header -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<nav class="backdrop-blur-xl bg-white/70 border-b border-white/20 shadow-sm sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row justify-between items-center h-16 sm:h-auto py-4 sm:py-0 gap-4 sm:gap-0">
<div class="text-lg sm:text-xl font-bold text-gray-800">Job Tracking</div>
<div class="flex flex-col sm:flex-row items-center gap-3 w-full sm:w-auto">
<div class="text-xs sm:text-sm text-gray-600" id="userEmail"></div>
<div class="flex flex-row justify-end items-center py-4 px-2">
<div class="flex items-center gap-3 relative">
<div class="relative">
<button
onclick="toggleUserDropdown()"
class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-medium text-sm hover:scale-105 focus:outline-none transition-all duration-300 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/50"
id="userAvatarBtn"
>
<span id="userInitials" class="text-xs font-semibold"></span>
</button>
<div
id="userDropdown"
class="hidden absolute right-0 mt-2 w-48 backdrop-blur-xl bg-white/80 rounded-2xl shadow-2xl border border-white/30 py-2 z-50 overflow-hidden"
>
<div class="px-4 py-3 border-b border-white/20">
<p class="text-xs text-gray-500 font-medium">Signed in as</p>
<p class="text-sm font-semibold text-gray-900 truncate mt-0.5" id="userEmail"></p>
</div>
<a
href="preferences"
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none transition-colors font-medium"
>
Preferences
</a>
<button
onclick="logout()"
class="w-full sm:w-auto bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition"
class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50/50 focus:outline-none transition-colors font-medium"
>
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
@@ -37,7 +59,7 @@
<!-- Dashboard Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<!-- Job Tracking Card -->
<div class="bg-white rounded-lg shadow border border-gray-200 p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 hover:bg-white/70 transition-all duration-300">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-indigo-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -47,11 +69,11 @@
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">Job Tracking</h3>
<p class="text-sm text-gray-600 mb-4">View and manage job information</p>
<a href="jobs.html" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm">View Jobs →</a>
<a href="jobs" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm">View Jobs →</a>
</div>
<!-- Reports Card -->
<div class="bg-white rounded-lg shadow border border-gray-200 p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 hover:bg-white/70 transition-all duration-300">
<div class="flex items-center justify-between mb-4">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center">
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -61,12 +83,12 @@
</div>
<h3 class="text-lg font-semibold text-gray-800 mb-2">Notes & Reports</h3>
<p class="text-sm text-gray-600 mb-4">Manage job notes and reports</p>
<a href="notes.html" class="text-green-600 hover:text-green-700 font-medium text-sm">View Notes →</a>
<a href="notes" class="text-green-600 hover:text-green-700 font-medium text-sm">View Notes →</a>
</div>
</div>
<!-- Quick Stats -->
<div class="bg-white rounded-lg shadow border border-gray-200 p-4 sm:p-6 mb-6 sm:mb-8">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-4 sm:p-6 mb-6 sm:mb-8">
<h2 class="text-base sm:text-lg font-semibold text-gray-800 mb-4">Quick Stats</h2>
<div class="grid grid-cols-2 sm:grid-cols-4 lg:grid-cols-7 gap-3 sm:gap-4">
<!-- Total Jobs -->
@@ -176,11 +198,11 @@
</div>
<!-- Quick Actions -->
<div class="bg-white rounded-lg shadow border border-gray-200 p-4 sm:p-6 mb-6 sm:mb-8">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-4 sm:p-6 mb-6 sm:mb-8">
<h2 class="text-base sm:text-lg font-semibold text-gray-800 mb-4">Quick Actions</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- Add New Job -->
<a href="jobs.html" class="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-indigo-300 transition-colors">
<a href="jobs" class="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-indigo-300 transition-colors">
<div class="w-10 h-10 bg-indigo-100 rounded-lg flex items-center justify-center mr-4 flex-shrink-0">
<svg class="w-5 h-5 text-indigo-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
@@ -193,7 +215,7 @@
</a>
<!-- View All Jobs -->
<a href="jobs.html" class="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-green-300 transition-colors">
<a href="jobs" class="flex items-center p-4 border border-gray-200 rounded-lg hover:bg-gray-50 hover:border-green-300 transition-colors">
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-4 flex-shrink-0">
<svg class="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path>
@@ -211,11 +233,15 @@
<script src="js/pocketbase.js"></script>
<script src="js/config.js"></script>
<script src="js/auth.js"></script>
<script src="js/preferences.js"></script>
<script src="js/jobs.js"></script>
<script>
// Check authentication
checkAuth();
// Initialize dark mode
initializeDarkMode();
// Load dashboard data
async function loadDashboard() {
try {
@@ -256,13 +282,41 @@
}
}
// Set user email
// Set user email and initials
const user = pb.authStore.model;
if (user) {
document.getElementById('userEmail').textContent = user.email || '';
document.getElementById('welcomeTitle').textContent = `Welcome, ${user.email?.split('@')[0] || 'User'}`;
const email = user.email || '';
document.getElementById('userEmail').textContent = email;
document.getElementById('welcomeTitle').textContent = `Welcome, ${email.split('@')[0] || 'User'}`;
// Set initials for avatar
const initials = email
.split('@')[0]
.split('.')
.map(part => part.charAt(0).toUpperCase())
.join('')
.substring(0, 2) || 'U';
document.getElementById('userInitials').textContent = initials;
}
// Toggle user dropdown
function toggleUserDropdown() {
const dropdown = document.getElementById('userDropdown');
dropdown.classList.toggle('hidden');
}
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('userDropdown');
const button = document.getElementById('userAvatarBtn');
if (dropdown && !dropdown.contains(e.target) && !button.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
// Make function globally accessible
window.toggleUserDropdown = toggleUserDropdown;
loadDashboard();
</script>
</body>
+3 -3
View File
@@ -24,7 +24,7 @@
id="email"
name="email"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition text-base"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition text-base"
placeholder="Enter your email"
>
</div>
@@ -36,7 +36,7 @@
id="password"
name="password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none transition text-base"
class="w-full px-4 py-3 border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition text-base"
placeholder="Enter your password"
>
</div>
@@ -68,7 +68,7 @@
try {
await login(email, password);
window.location.href = 'dashboard.html';
window.location.href = 'dashboard';
} catch (error) {
errorMessage.textContent = error.message || 'Login failed. Please try again.';
errorMessage.classList.remove('hidden');
+161 -59
View File
@@ -8,27 +8,66 @@
<script src="https://cdn.jsdelivr.net/npm/pocketbase@latest/dist/pocketbase.umd.js"></script>
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
<style>
/* Custom dropdown scrollbar styling */
[id$="_menu"]::-webkit-scrollbar {
width: 8px;
}
[id$="_menu"]::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
[id$="_menu"]::-webkit-scrollbar-thumb {
background: rgba(79, 70, 229, 0.3);
border-radius: 4px;
}
[id$="_menu"]::-webkit-scrollbar-thumb:hover {
background: rgba(79, 70, 229, 0.5);
}
</style>
</head>
<body class="min-h-screen bg-gray-50">
<body class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40">
<!-- Navigation Header -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<nav class="backdrop-blur-xl bg-white/70 border-b border-white/20 shadow-sm sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row justify-between items-center h-16 sm:h-auto py-4 sm:py-0 gap-4 sm:gap-0">
<div class="flex items-center gap-4 w-full sm:w-auto">
<a href="dashboard.html" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base">← Dashboard</a>
<div class="text-lg sm:text-xl font-bold text-gray-800">Job Tracking</div>
<div class="flex flex-row justify-between items-center py-4 px-2">
<div class="flex items-center gap-4">
<a href="dashboard" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base transition-colors">← Dashboard</a>
</div>
<div class="flex flex-col sm:flex-row items-center gap-3 w-full sm:w-auto">
<div class="text-xs sm:text-sm text-gray-600" id="userEmail"></div>
<div class="flex items-center gap-3 relative">
<div class="relative">
<button
onclick="toggleUserDropdown()"
class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-medium text-sm hover:scale-105 focus:outline-none transition-all duration-300 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/50"
id="userAvatarBtn"
>
<span id="userInitials" class="text-xs font-semibold"></span>
</button>
<div
id="userDropdown"
class="hidden absolute right-0 mt-2 w-48 backdrop-blur-xl bg-white/80 rounded-2xl shadow-2xl border border-white/30 py-2 z-50 overflow-hidden"
>
<div class="px-4 py-3 border-b border-white/20">
<p class="text-xs text-gray-500 font-medium">Signed in as</p>
<p class="text-sm font-semibold text-gray-900 truncate mt-0.5" id="userEmail"></p>
</div>
<a
href="preferences"
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none transition-colors font-medium"
>
Preferences
</a>
<button
onclick="logout()"
class="w-full sm:w-auto bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition"
class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50/50 focus:outline-none transition-colors font-medium"
>
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
@@ -37,14 +76,14 @@
<div id="messageContainer" class="mb-4"></div>
<!-- Search and Filter Section -->
<div class="bg-white rounded-lg shadow border border-gray-200 p-4 mb-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 mb-6">
<div class="flex flex-col gap-4">
<!-- Search Bar -->
<input
type="text"
id="searchInput"
placeholder="Search by job number, name, or client..."
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base"
class="w-full px-4 py-3 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm"
>
<!-- Filter Section -->
@@ -82,11 +121,11 @@
<!-- View Toggle -->
<div class="mb-4 flex justify-end">
<div class="inline-flex rounded-lg border border-gray-300 bg-white p-1">
<div class="inline-flex rounded-2xl border border-white/30 backdrop-blur-xl bg-white/60 p-1 shadow-lg">
<button
onclick="switchView('table')"
id="tableViewBtn"
class="px-4 py-2 rounded-md text-sm font-medium transition focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-indigo-600 text-white"
class="px-4 py-2 rounded-xl text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/50 backdrop-blur-sm bg-indigo-600/90 text-white shadow-lg hover:bg-indigo-700/90"
>
<svg class="w-5 h-5 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
@@ -96,7 +135,7 @@
<button
onclick="switchView('card')"
id="cardViewBtn"
class="px-4 py-2 rounded-md text-sm font-medium transition focus:outline-none focus:ring-2 focus:ring-indigo-500 text-gray-700 hover:bg-gray-50"
class="px-4 py-2 rounded-xl text-sm font-medium transition-all focus:outline-none focus:ring-2 focus:ring-indigo-500/50 text-gray-700 hover:bg-white/40 backdrop-blur-sm"
>
<svg class="w-5 h-5 inline-block mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
@@ -107,10 +146,10 @@
</div>
<!-- Jobs Table View -->
<div id="tableView" class="bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
<div id="tableView" class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<thead class="backdrop-blur-sm bg-white/40">
<tr>
<th class="px-3 sm:px-6 py-2 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Job Number</th>
<th class="px-3 sm:px-6 py-2 sm:py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Job Name</th>
@@ -122,7 +161,7 @@
<th class="px-3 sm:px-6 py-2 sm:py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="jobsTableBody" class="bg-white divide-y divide-gray-200">
<tbody id="jobsTableBody" class="bg-white/30 divide-y divide-white/20">
<tr id="loadingRow">
<td colspan="8" class="px-6 py-8 text-center text-gray-500">Loading...</td>
</tr>
@@ -143,10 +182,10 @@
</main>
<!-- Job Dashboard Modal -->
<div id="jobModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto z-50">
<div class="relative top-0 sm:top-10 mx-auto p-4 sm:p-6 border-0 sm:border w-full sm:max-w-7xl shadow-lg rounded-none sm:rounded-md bg-gray-50 mb-0 sm:mb-10 min-h-screen sm:min-h-[600px]">
<div id="jobModal" class="hidden fixed inset-0 backdrop-blur-sm bg-black/20 overflow-y-auto z-50">
<div class="relative top-0 sm:top-10 mx-auto p-4 sm:p-6 w-full sm:max-w-7xl shadow-2xl rounded-none sm:rounded-3xl backdrop-blur-2xl bg-white/80 border border-white/30 mb-0 sm:mb-10 min-h-screen sm:min-h-[600px]">
<!-- Dashboard Header -->
<div class="bg-white rounded-lg shadow-sm p-6 mb-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 mb-6">
<div class="flex flex-col lg:flex-row justify-between items-start gap-4 mb-4">
<div class="flex-1">
<h2 class="text-2xl font-bold text-gray-900 mb-2" id="dashboardJobName">Job Dashboard</h2>
@@ -163,7 +202,7 @@
</div>
<!-- Quick Info Card -->
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200 min-w-[250px]">
<div class="backdrop-blur-xl bg-white/40 rounded-2xl p-4 border border-white/30 min-w-[250px] shadow-lg">
<h3 class="text-sm font-semibold text-gray-900 mb-3">Quick Info</h3>
<div class="space-y-2">
<div class="flex justify-between">
@@ -215,28 +254,28 @@
<!-- Overview Tab Content -->
<div id="dashboard-tab-overview" class="dashboard-tab-content space-y-6 min-h-[500px]">
<!-- Basic Information Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Basic Information</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="Job_Number" class="block text-sm font-medium text-gray-700 mb-1">Job Number</label>
<input type="text" id="Job_Number" name="Job_Number" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Number" name="Job_Number" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Full_Name" class="block text-sm font-medium text-gray-700 mb-1">Job Full Name</label>
<input type="text" id="Job_Full_Name" name="Job_Full_Name" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Full_Name" name="Job_Full_Name" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Name" class="block text-sm font-medium text-gray-700 mb-1">Job Name</label>
<input type="text" id="Job_Name" name="Job_Name" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Name" name="Job_Name" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Address" class="block text-sm font-medium text-gray-700 mb-1">Job Address</label>
<input type="text" id="Job_Address" name="Job_Address" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Address" name="Job_Address" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Type" class="block text-sm font-medium text-gray-700 mb-1">Job Type</label>
<select id="Job_Type" name="Job_Type" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="Job_Type" name="Job_Type" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select type...</option>
<option value="FX">FX</option>
<option value="TM">TM</option>
@@ -248,7 +287,7 @@
</div>
<div>
<label for="Job_Status" class="block text-sm font-medium text-gray-700 mb-1">Job Status</label>
<select id="Job_Status" name="Job_Status" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="Job_Status" name="Job_Status" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select status...</option>
<option value="Estimating">Estimating</option>
<option value="Est Sent">Est Sent</option>
@@ -266,7 +305,7 @@
</div>
<div>
<label for="Job_Division" class="block text-sm font-medium text-gray-700 mb-1">Job Division</label>
<select id="Job_Division" name="Job_Division" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="Job_Division" name="Job_Division" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select division...</option>
<option value="C#">C#</option>
<option value="R#">R#</option>
@@ -280,12 +319,12 @@
<!-- Team & Client Tab Content -->
<div id="dashboard-tab-team" class="dashboard-tab-content hidden space-y-6 min-h-[500px]">
<!-- Team Assignment Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Team Assignment</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="Estimator" class="block text-sm font-medium text-gray-700 mb-1">Estimator</label>
<select id="Estimator" name="Estimator" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="Estimator" name="Estimator" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select estimator...</option>
<option value="Beth Cardoza">Beth Cardoza</option>
<option value="Steve Brewer">Steve Brewer</option>
@@ -294,7 +333,7 @@
</div>
<div>
<label for="Office_Rep" class="block text-sm font-medium text-gray-700 mb-1">Office Rep</label>
<select id="Office_Rep" name="Office_Rep" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="Office_Rep" name="Office_Rep" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select office rep...</option>
<option value="Regina McClain">Regina McClain</option>
<option value="Beth Cardoza">Beth Cardoza</option>
@@ -304,30 +343,30 @@
</div>
<div>
<label for="Project_Manager" class="block text-sm font-medium text-gray-700 mb-1">Project Manager</label>
<input type="text" id="Project_Manager" name="Project_Manager" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Project_Manager" name="Project_Manager" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
</div>
</div>
<!-- Client Information Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Client Information</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="Company_Client" class="block text-sm font-medium text-gray-700 mb-1">Company Client</label>
<input type="text" id="Company_Client" name="Company_Client" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Company_Client" name="Company_Client" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Contact_Person" class="block text-sm font-medium text-gray-700 mb-1">Contact Person</label>
<input type="text" id="Contact_Person" name="Contact_Person" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Contact_Person" name="Contact_Person" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Phone_Number" class="block text-sm font-medium text-gray-700 mb-1">Phone Number</label>
<input type="tel" id="Phone_Number" name="Phone_Number" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="tel" id="Phone_Number" name="Phone_Number" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Email" class="block text-sm font-medium text-gray-700 mb-1">Email</label>
<input type="email" id="Email" name="Email" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="email" id="Email" name="Email" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
</div>
</div>
@@ -336,63 +375,63 @@
<!-- Dates & Links Tab Content -->
<div id="dashboard-tab-dates" class="dashboard-tab-content hidden space-y-6 min-h-[500px]">
<!-- Dates & Scheduling Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Dates & Scheduling</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="Start_Date" class="block text-sm font-medium text-gray-700 mb-1">Start Date</label>
<input type="date" id="Start_Date" name="Start_Date" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="date" id="Start_Date" name="Start_Date" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Due_Date" class="block text-sm font-medium text-gray-700 mb-1">Due Date</label>
<input type="date" id="Due_Date" name="Due_Date" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="date" id="Due_Date" name="Due_Date" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Submission_Date" class="block text-sm font-medium text-gray-700 mb-1">Submission Date</label>
<input type="date" id="Submission_Date" name="Submission_Date" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="date" id="Submission_Date" name="Submission_Date" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Due_Date_Source" class="block text-sm font-medium text-gray-700 mb-1">Due Date Source</label>
<input type="text" id="Due_Date_Source" name="Due_Date_Source" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Due_Date_Source" name="Due_Date_Source" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Due_Date_Counter" class="block text-sm font-medium text-gray-700 mb-1">Due Date Counter</label>
<input type="text" id="Due_Date_Counter" name="Due_Date_Counter" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Due_Date_Counter" name="Due_Date_Counter" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Due_Time" class="block text-sm font-medium text-gray-700 mb-1">Due Time</label>
<input type="time" id="Due_Time" name="Due_Time" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="time" id="Due_Time" name="Due_Time" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Schedule_Confidence" class="block text-sm font-medium text-gray-700 mb-1">Schedule Confidence</label>
<input type="text" id="Schedule_Confidence" name="Schedule_Confidence" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Schedule_Confidence" name="Schedule_Confidence" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
</div>
</div>
<!-- Links & References Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Links & References</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div>
<label for="Job_QB_Link" class="block text-sm font-medium text-gray-700 mb-1">Job QB Link</label>
<input type="url" id="Job_QB_Link" name="Job_QB_Link" placeholder="https://..." class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="url" id="Job_QB_Link" name="Job_QB_Link" placeholder="https://..." class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Voxer_Link" class="block text-sm font-medium text-gray-700 mb-1">Voxer Link</label>
<input type="url" id="Voxer_Link" name="Voxer_Link" placeholder="https://..." class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="url" id="Voxer_Link" name="Voxer_Link" placeholder="https://..." class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Folder_Link" class="block text-sm font-medium text-gray-700 mb-1">Job Folder Link</label>
<input type="url" id="Job_Folder_Link" name="Job_Folder_Link" placeholder="https://..." class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="url" id="Job_Folder_Link" name="Job_Folder_Link" placeholder="https://..." class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Codes" class="block text-sm font-medium text-gray-700 mb-1">Job Codes</label>
<input type="text" id="Job_Codes" name="Job_Codes" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Codes" name="Job_Codes" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div>
<label for="Job_Number_Parent" class="block text-sm font-medium text-gray-700 mb-1">Job Number Parent</label>
<input type="text" id="Job_Number_Parent" name="Job_Number_Parent" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="text" id="Job_Number_Parent" name="Job_Number_Parent" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
</div>
</div>
@@ -401,7 +440,7 @@
<!-- Status Flags Tab Content -->
<div id="dashboard-tab-flags" class="dashboard-tab-content hidden space-y-6 min-h-[500px]">
<!-- Status Flags Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<h3 class="text-lg font-semibold text-gray-900 mb-4">Status Flags</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="flex items-center">
@@ -459,7 +498,7 @@
<!-- Notes Tab Content -->
<div id="dashboard-tab-notes" class="dashboard-tab-content hidden min-h-[500px]">
<!-- Notes Card -->
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-900">Notes</h3>
<button
@@ -483,7 +522,7 @@
</div>
<div>
<label for="noteSentiment" class="block text-sm font-medium text-gray-700 mb-1">Sentiment</label>
<select id="noteSentiment" name="noteSentiment" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
<select id="noteSentiment" name="noteSentiment" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-sm transition-all shadow-sm">
<option value="">Select sentiment...</option>
<option value="Positive">Positive</option>
<option value="Negative">Negative</option>
@@ -517,13 +556,13 @@
<button
type="button"
onclick="closeJobModal()"
class="w-full sm:w-auto px-6 py-2.5 bg-white border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition shadow-sm"
class="w-full sm:w-auto px-6 py-2.5 backdrop-blur-sm bg-white/60 border-2 border-gray-300/60 text-gray-700 rounded-xl font-medium hover:bg-white/80 focus:outline-none focus:ring-2 focus:ring-gray-500/50 transition-all shadow-lg hover:shadow-xl"
>
Cancel
</button>
<button
type="submit"
class="w-full sm:w-auto px-6 py-2.5 bg-indigo-600 text-white rounded-lg font-medium hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition shadow-sm"
class="w-full sm:w-auto px-6 py-2.5 backdrop-blur-sm bg-indigo-600/90 text-white rounded-xl font-medium hover:bg-indigo-700/90 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all shadow-lg hover:shadow-xl"
>
Save Job
</button>
@@ -536,6 +575,8 @@
<script src="js/pocketbase.js"></script>
<script src="js/config.js"></script>
<script src="js/auth.js"></script>
<script src="js/preferences.js"></script>
<script src="js/custom-dropdown.js"></script>
<script src="js/jobs.js"></script>
<script src="js/notes.js"></script>
<script>
@@ -544,12 +585,43 @@
// Check authentication
checkAuth();
// Set user email
// Initialize dark mode
initializeDarkMode();
// Set user email and initials
const user = pb.authStore.model;
if (user) {
document.getElementById('userEmail').textContent = user.email || '';
const email = user.email || '';
document.getElementById('userEmail').textContent = email;
// Set initials for avatar
const initials = email
.split('@')[0]
.split('.')
.map(part => part.charAt(0).toUpperCase())
.join('')
.substring(0, 2) || 'U';
document.getElementById('userInitials').textContent = initials;
}
// Toggle user dropdown
function toggleUserDropdown() {
const dropdown = document.getElementById('userDropdown');
dropdown.classList.toggle('hidden');
}
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('userDropdown');
const button = document.getElementById('userAvatarBtn');
if (dropdown && !dropdown.contains(e.target) && !button.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
// Make function globally accessible
window.toggleUserDropdown = toggleUserDropdown;
// Initialize Quill editor for notes
function initNoteEditor() {
if (!noteEditor) {
@@ -628,6 +700,18 @@
// Show notes tab for existing jobs
document.getElementById('notesTabButton').classList.remove('hidden');
initNoteEditor();
// Initialize custom dropdowns after loading job data
setTimeout(() => {
if (typeof initCustomDropdowns === 'function') {
initCustomDropdowns();
}
// Update dropdown values
['Job_Type', 'Job_Status', 'Job_Division', 'Estimator', 'Office_Rep'].forEach(fieldId => {
if (typeof updateCustomDropdown === 'function') {
updateCustomDropdown(fieldId);
}
});
}, 100);
});
} else {
document.getElementById('jobForm').reset();
@@ -637,6 +721,12 @@
document.getElementById('dashboardJobStatus').textContent = '-';
// Hide notes tab for new jobs
document.getElementById('notesTabButton').classList.add('hidden');
// Initialize custom dropdowns for new job
setTimeout(() => {
if (typeof initCustomDropdowns === 'function') {
initCustomDropdowns();
}
}, 100);
}
// Start with overview tab
switchDashboardTab('overview');
@@ -895,8 +985,20 @@
setTimeout(async () => {
// Wait for config to load
await initConfig();
// Load default view from preferences
if (typeof getUserPreferences !== 'undefined') {
const prefs = await getUserPreferences();
if (prefs.defaultView) {
currentView = prefs.defaultView;
localStorage.setItem('jobsView', currentView);
}
}
await loadEstimatorFilters();
await loadStatusFilters();
// Initialize custom dropdowns
if (typeof initCustomDropdowns === 'function') {
initCustomDropdowns();
}
// Initialize view display
if (typeof updateViewDisplay === 'function') {
updateViewDisplay();
+2 -2
View File
@@ -18,7 +18,7 @@ async function login(email, password) {
*/
function logout() {
pb.authStore.clear();
window.location.href = 'index.html';
window.location.href = 'index';
}
/**
@@ -26,7 +26,7 @@ function logout() {
*/
function checkAuth() {
if (!pb.authStore.isValid) {
window.location.href = 'index.html';
window.location.href = 'index';
return false;
}
return true;
+197
View File
@@ -0,0 +1,197 @@
// Custom dropdown component with liquid glass styling
// Replaces native select elements with styled custom dropdowns
/**
* Initialize custom dropdowns for all select elements
*/
function initCustomDropdowns() {
const selects = document.querySelectorAll('select');
selects.forEach(select => {
// Skip if already converted
if (select.dataset.customDropdown === 'true') {
return;
}
createCustomDropdown(select);
});
}
/**
* Create a custom dropdown for a select element
*/
function createCustomDropdown(select) {
// Mark as converted
select.dataset.customDropdown = 'true';
// Create dropdown container
const dropdownContainer = document.createElement('div');
dropdownContainer.className = 'relative';
// Create button
const button = document.createElement('button');
button.type = 'button';
button.className = 'w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm text-left flex items-center justify-between';
button.id = select.id + '_button';
// Get selected option text
const selectedOption = select.options[select.selectedIndex];
const buttonText = document.createElement('span');
buttonText.className = 'flex-1 text-gray-900';
buttonText.textContent = selectedOption ? selectedOption.text : 'Select...';
button.appendChild(buttonText);
// Add chevron icon
const chevron = document.createElement('svg');
chevron.className = 'w-5 h-5 text-gray-400 flex-shrink-0 ml-2 transition-transform';
chevron.id = select.id + '_chevron';
chevron.fill = 'none';
chevron.stroke = 'currentColor';
chevron.viewBox = '0 0 24 24';
chevron.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>';
button.appendChild(chevron);
// Create dropdown menu
const menu = document.createElement('div');
menu.className = 'hidden absolute z-50 w-full mt-1 backdrop-blur-xl bg-white/80 rounded-2xl shadow-2xl border border-white/30 overflow-hidden max-h-60 overflow-y-auto';
menu.id = select.id + '_menu';
// Create options
Array.from(select.options).forEach((option, index) => {
const menuItem = document.createElement('button');
menuItem.type = 'button';
menuItem.className = 'w-full text-left px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none focus:bg-white/40 transition-colors';
if (option.value === select.value) {
menuItem.className += ' bg-indigo-50/50 text-indigo-600';
}
menuItem.textContent = option.text;
menuItem.dataset.value = option.value;
menuItem.addEventListener('click', () => {
select.value = option.value;
buttonText.textContent = option.text;
select.dispatchEvent(new Event('change', { bubbles: true }));
closeDropdown(select.id);
// Update selected state
menu.querySelectorAll('button').forEach(item => {
if (item.dataset.value === option.value) {
item.className = 'w-full text-left px-4 py-2.5 text-sm text-indigo-600 bg-indigo-50/50 hover:bg-white/40 focus:outline-none focus:bg-white/40 transition-colors';
} else {
item.className = 'w-full text-left px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none focus:bg-white/40 transition-colors';
}
});
});
menu.appendChild(menuItem);
});
// Toggle dropdown
button.addEventListener('click', (e) => {
e.stopPropagation();
const isOpen = !menu.classList.contains('hidden');
// Close all other dropdowns
document.querySelectorAll('[id$="_menu"]').forEach(m => {
if (m.id !== menu.id) {
m.classList.add('hidden');
const otherChevron = document.getElementById(m.id.replace('_menu', '_chevron'));
if (otherChevron) {
otherChevron.classList.remove('rotate-180');
}
}
});
if (isOpen) {
closeDropdown(select.id);
} else {
openDropdown(select.id);
}
});
// Close on outside click
document.addEventListener('click', (e) => {
if (!dropdownContainer.contains(e.target)) {
closeDropdown(select.id);
}
});
// Wrap select and add custom elements
select.style.display = 'none';
select.style.position = 'absolute';
select.style.opacity = '0';
select.style.pointerEvents = 'none';
dropdownContainer.appendChild(button);
dropdownContainer.appendChild(menu);
select.parentNode.insertBefore(dropdownContainer, select);
dropdownContainer.appendChild(select);
// Listen for programmatic changes to select
select.addEventListener('change', () => {
updateCustomDropdown(select.id);
});
}
/**
* Open dropdown
*/
function openDropdown(selectId) {
const menu = document.getElementById(selectId + '_menu');
const chevron = document.getElementById(selectId + '_chevron');
if (menu) {
menu.classList.remove('hidden');
if (chevron) {
chevron.classList.add('rotate-180');
}
}
}
/**
* Close dropdown
*/
function closeDropdown(selectId) {
const menu = document.getElementById(selectId + '_menu');
const chevron = document.getElementById(selectId + '_chevron');
if (menu) {
menu.classList.add('hidden');
if (chevron) {
chevron.classList.remove('rotate-180');
}
}
}
/**
* Update custom dropdown when select value changes programmatically
*/
function updateCustomDropdown(selectId) {
const select = document.getElementById(selectId);
if (!select || select.dataset.customDropdown !== 'true') {
return;
}
const button = document.getElementById(selectId + '_button');
const buttonText = button?.querySelector('span');
const selectedOption = select.options[select.selectedIndex];
if (buttonText && selectedOption) {
buttonText.textContent = selectedOption.text;
}
// Update selected state in menu
const menu = document.getElementById(selectId + '_menu');
if (menu) {
menu.querySelectorAll('button').forEach(item => {
if (item.dataset.value === select.value) {
item.className = 'w-full text-left px-4 py-2.5 text-sm text-indigo-600 bg-indigo-50/50 hover:bg-white/40 focus:outline-none focus:bg-white/40 transition-colors';
} else {
item.className = 'w-full text-left px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none focus:bg-white/40 transition-colors';
}
});
}
}
// Make functions globally accessible
if (typeof window !== 'undefined') {
window.initCustomDropdowns = initCustomDropdowns;
window.createCustomDropdown = createCustomDropdown;
window.updateCustomDropdown = updateCustomDropdown;
}
+43 -9
View File
@@ -208,7 +208,7 @@ function displayJobsCards(jobs) {
return `
<div
onclick="openJobModal('${job.id}')"
class="bg-white rounded-lg shadow border border-gray-200 p-6 hover:shadow-lg hover:border-indigo-300 cursor-pointer transition-all duration-200 hover:bg-indigo-50/30"
class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6 hover:shadow-2xl hover:border-white/50 hover:bg-white/70 cursor-pointer transition-all duration-300 hover:scale-[1.02]"
>
<div class="mb-4">
<div class="flex items-center justify-between">
@@ -226,6 +226,7 @@ function displayJobsCards(jobs) {
<div>
<span class="text-xs text-gray-500 uppercase tracking-wide">Client</span>
<p class="text-sm font-medium text-gray-900 mt-1">${job.Company_Client || '-'}</p>
${job.Phone_Number ? `<a href="tel:${job.Phone_Number.replace(/[^\d+]/g, '')}" onclick="event.stopPropagation()" class="text-xs text-indigo-600 hover:text-indigo-800 mt-1 block hover:underline">${job.Phone_Number}</a>` : ''}
</div>
<div>
@@ -242,6 +243,31 @@ function displayJobsCards(jobs) {
</div>
</div>
${job.Job_Address ? `
<div>
<span class="text-xs text-gray-500 uppercase tracking-wide">Address</span>
<p class="text-sm font-medium text-gray-900 mt-1">${job.Job_Address}</p>
<div class="mt-2 flex flex-col gap-2">
<a
href="https://maps.apple.com/?q=${encodeURIComponent(job.Job_Address)}"
target="_blank"
onclick="event.stopPropagation()"
class="w-full px-3 py-1.5 backdrop-blur-sm bg-gray-900/90 text-white text-xs font-medium rounded-xl hover:bg-gray-800/90 transition-all shadow-lg hover:shadow-xl text-center"
>
Apple Maps
</a>
<a
href="https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(job.Job_Address)}"
target="_blank"
onclick="event.stopPropagation()"
class="w-full px-3 py-1.5 backdrop-blur-sm bg-blue-600/90 text-white text-xs font-medium rounded-xl hover:bg-blue-700/90 transition-all shadow-lg hover:shadow-xl text-center"
>
Google Maps
</a>
</div>
</div>
` : ''}
<div class="grid grid-cols-2 gap-4 sm:col-span-2 lg:col-span-1">
<div>
<span class="text-xs text-gray-500 uppercase tracking-wide">Type</span>
@@ -278,26 +304,34 @@ function updateViewDisplay() {
const cardViewBtn = document.getElementById('cardViewBtn');
if (currentView === 'table') {
// Show table, hide card
if (tableView) tableView.classList.remove('hidden');
if (cardView) cardView.classList.add('hidden');
// Table button: active (indigo with shadow)
if (tableViewBtn) {
tableViewBtn.classList.add('bg-indigo-600', 'text-white');
tableViewBtn.classList.remove('text-gray-700', 'hover:bg-gray-50');
tableViewBtn.classList.remove('text-gray-700', 'hover:bg-white/40');
tableViewBtn.classList.add('bg-indigo-600/90', 'text-white', 'shadow-lg', 'hover:bg-indigo-700/90');
}
// Card button: inactive (transparent)
if (cardViewBtn) {
cardViewBtn.classList.remove('bg-indigo-600', 'text-white');
cardViewBtn.classList.add('text-gray-700', 'hover:bg-gray-50');
cardViewBtn.classList.remove('bg-indigo-600/90', 'text-white', 'shadow-lg', 'hover:bg-indigo-700/90');
cardViewBtn.classList.add('text-gray-700', 'hover:bg-white/40');
}
} else {
// Show card, hide table
if (tableView) tableView.classList.add('hidden');
if (cardView) cardView.classList.remove('hidden');
// Table button: inactive (transparent)
if (tableViewBtn) {
tableViewBtn.classList.remove('bg-indigo-600', 'text-white');
tableViewBtn.classList.add('text-gray-700', 'hover:bg-gray-50');
tableViewBtn.classList.remove('bg-indigo-600/90', 'text-white', 'shadow-lg', 'hover:bg-indigo-700/90');
tableViewBtn.classList.add('text-gray-700', 'hover:bg-white/40');
}
// Card button: active (indigo with shadow)
if (cardViewBtn) {
cardViewBtn.classList.add('bg-indigo-600', 'text-white');
cardViewBtn.classList.remove('text-gray-700', 'hover:bg-gray-50');
cardViewBtn.classList.remove('text-gray-700', 'hover:bg-white/40');
cardViewBtn.classList.add('bg-indigo-600/90', 'text-white', 'shadow-lg', 'hover:bg-indigo-700/90');
}
}
}
+267
View File
@@ -0,0 +1,267 @@
// User preferences management
// Stores preferences in the users collection PRISM column as JSON
// Uses "Job-Pages" key to avoid conflicts with other applications
/**
* Get user preferences from PRISM column
*/
async function getUserPreferences() {
try {
const user = pb.authStore.model;
if (!user) {
return getDefaultPreferences();
}
// Get current user record
const userRecord = await pb.collection('users').getOne(user.id);
// Parse PRISM JSON if it exists
let prism = {};
if (userRecord.PRISM) {
try {
prism = typeof userRecord.PRISM === 'string'
? JSON.parse(userRecord.PRISM)
: userRecord.PRISM;
} catch (e) {
console.warn('Error parsing PRISM data:', e);
prism = {};
}
}
// Get Job-Pages preferences (isolated from other apps)
const jobPagesPrefs = prism['Job-Pages'] || {};
// Merge with defaults
return {
darkMode: jobPagesPrefs.darkMode ?? false,
defaultView: jobPagesPrefs.defaultView ?? 'table'
};
} catch (error) {
console.error('Error loading preferences:', error);
return getDefaultPreferences();
}
}
/**
* Get default preferences
*/
function getDefaultPreferences() {
return {
darkMode: false,
defaultView: 'table'
};
}
/**
* Save user preferences to PRISM column
*/
async function saveUserPreferences(preferences) {
try {
const user = pb.authStore.model;
if (!user) {
throw new Error('User not authenticated');
}
// Get current user record
const userRecord = await pb.collection('users').getOne(user.id);
// Parse existing PRISM JSON
let prism = {};
if (userRecord.PRISM) {
try {
prism = typeof userRecord.PRISM === 'string'
? JSON.parse(userRecord.PRISM)
: userRecord.PRISM;
} catch (e) {
console.warn('Error parsing PRISM data:', e);
prism = {};
}
}
// Update only Job-Pages preferences (preserve other app data)
prism['Job-Pages'] = {
darkMode: preferences.darkMode,
defaultView: preferences.defaultView
};
// Save back to PRISM column
await pb.collection('users').update(user.id, {
PRISM: JSON.stringify(prism)
});
return true;
} catch (error) {
console.error('Error saving preferences:', error);
throw error;
}
}
/**
* Apply dark mode to the page
*/
function applyDarkMode(enabled) {
const html = document.documentElement;
if (enabled) {
html.classList.add('dark');
localStorage.setItem('darkMode', 'true');
} else {
html.classList.remove('dark');
localStorage.setItem('darkMode', 'false');
}
}
/**
* Initialize dark mode from preferences
*/
async function initializeDarkMode() {
try {
const preferences = await getUserPreferences();
applyDarkMode(preferences.darkMode);
} catch (error) {
console.error('Error initializing dark mode:', error);
}
}
/**
* Update view option visual state
*/
function updateViewOptionVisuals() {
const selectedView = document.querySelector('input[name="defaultView"]:checked')?.value || 'table';
const options = document.querySelectorAll('.view-option');
options.forEach(option => {
const input = option.querySelector('input[type="radio"]');
const icon = option.querySelector('.view-icon');
const title = option.querySelector('.view-title');
const isSelected = input.value === selectedView;
if (isSelected) {
option.classList.add('border-indigo-500', 'bg-indigo-50/50', 'shadow-md');
option.classList.remove('border-white/30', 'bg-white/40');
if (icon) icon.classList.add('text-indigo-600');
if (icon) icon.classList.remove('text-gray-400');
if (title) title.classList.add('text-indigo-600');
if (title) title.classList.remove('text-gray-900');
} else {
option.classList.remove('border-indigo-500', 'bg-indigo-50/50', 'shadow-md');
option.classList.add('border-white/30', 'bg-white/40');
if (icon) icon.classList.remove('text-indigo-600');
if (icon) icon.classList.add('text-gray-400');
if (title) title.classList.remove('text-indigo-600');
if (title) title.classList.add('text-gray-900');
}
});
}
/**
* Load preferences and populate form
*/
async function loadPreferences() {
try {
const preferences = await getUserPreferences();
// Set dark mode toggle
const darkModeToggle = document.getElementById('darkModeToggle');
if (darkModeToggle) {
darkModeToggle.checked = preferences.darkMode;
applyDarkMode(preferences.darkMode);
}
// Set default view radio buttons
const viewTable = document.getElementById('viewTable');
const viewCard = document.getElementById('viewCard');
if (viewTable && viewCard) {
if (preferences.defaultView === 'table') {
viewTable.checked = true;
} else {
viewCard.checked = true;
}
// Update visual state
updateViewOptionVisuals();
}
// Add change listeners to radio buttons
const radioButtons = document.querySelectorAll('input[name="defaultView"]');
radioButtons.forEach(radio => {
radio.addEventListener('change', updateViewOptionVisuals);
});
} catch (error) {
console.error('Error loading preferences:', error);
}
}
/**
* Save preferences from form
*/
async function savePreferences() {
try {
const darkMode = document.getElementById('darkModeToggle').checked;
const defaultView = document.querySelector('input[name="defaultView"]:checked')?.value || 'table';
const preferences = {
darkMode: darkMode,
defaultView: defaultView
};
await saveUserPreferences(preferences);
applyDarkMode(darkMode);
showMessage('Preferences saved successfully', 'success');
} catch (error) {
console.error('Error saving preferences:', error);
showMessage('Error saving preferences: ' + (error.message || 'Unknown error'), 'error');
}
}
/**
* Show message to user
*/
function showMessage(message, type = 'success') {
const container = document.getElementById('messageContainer');
if (!container) return;
const bgColor = type === 'success'
? 'bg-green-50 border-green-200 text-green-700'
: 'bg-red-50 border-red-200 text-red-700';
container.innerHTML = `
<div class="${bgColor} border px-4 py-3 rounded-xl text-sm backdrop-blur-sm">
${message}
</div>
`;
// Auto-hide after 5 seconds
setTimeout(() => {
container.innerHTML = '';
}, 5000);
}
// Form submission handler
if (typeof document !== 'undefined') {
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('preferencesForm');
if (form) {
form.addEventListener('submit', async (e) => {
e.preventDefault();
await savePreferences();
});
}
// Dark mode toggle change handler
const darkModeToggle = document.getElementById('darkModeToggle');
if (darkModeToggle) {
darkModeToggle.addEventListener('change', (e) => {
applyDarkMode(e.target.checked);
});
}
});
}
// Make functions globally accessible
if (typeof window !== 'undefined') {
window.getUserPreferences = getUserPreferences;
window.saveUserPreferences = saveUserPreferences;
window.initializeDarkMode = initializeDarkMode;
window.loadPreferences = loadPreferences;
window.savePreferences = savePreferences;
}
+69 -15
View File
@@ -9,26 +9,48 @@
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
<script src="https://cdn.quilljs.com/1.3.6/quill.js"></script>
</head>
<body class="min-h-screen bg-gray-50">
<body class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40">
<!-- Navigation Header -->
<nav class="bg-white shadow-sm border-b border-gray-200">
<nav class="backdrop-blur-xl bg-white/70 border-b border-white/20 shadow-sm sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row justify-between items-center h-16 sm:h-auto py-4 sm:py-0 gap-4 sm:gap-0">
<div class="flex items-center gap-4 w-full sm:w-auto">
<a href="dashboard.html" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base">← Dashboard</a>
<div class="text-lg sm:text-xl font-bold text-gray-800">Employee Reports</div>
<div class="flex flex-row justify-between items-center py-4 px-2">
<div class="flex items-center gap-4">
<a href="dashboard" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base transition-colors">← Dashboard</a>
</div>
<div class="flex flex-col sm:flex-row items-center gap-3 w-full sm:w-auto">
<div class="text-xs sm:text-sm text-gray-600" id="userEmail"></div>
<div class="flex items-center gap-3 relative">
<div class="relative">
<button
onclick="toggleUserDropdown()"
class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-medium text-sm hover:scale-105 focus:outline-none transition-all duration-300 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/50"
id="userAvatarBtn"
>
<span id="userInitials" class="text-xs font-semibold"></span>
</button>
<div
id="userDropdown"
class="hidden absolute right-0 mt-2 w-48 backdrop-blur-xl bg-white/80 rounded-2xl shadow-2xl border border-white/30 py-2 z-50 overflow-hidden"
>
<div class="px-4 py-3 border-b border-white/20">
<p class="text-xs text-gray-500 font-medium">Signed in as</p>
<p class="text-sm font-semibold text-gray-900 truncate mt-0.5" id="userEmail"></p>
</div>
<a
href="preferences"
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none transition-colors font-medium"
>
Preferences
</a>
<button
onclick="logout()"
class="w-full sm:w-auto bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition"
class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50/50 focus:outline-none transition-colors font-medium"
>
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
@@ -57,7 +79,7 @@
</div>
<!-- Reports Table Container -->
<div id="reportsTableContainer" class="hidden bg-white rounded-lg shadow border border-gray-200 overflow-hidden">
<div id="reportsTableContainer" class="hidden backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
@@ -115,7 +137,7 @@
<div>
<label for="noteJob" class="block text-sm font-medium text-gray-700 mb-1">Job *</label>
<select id="noteJob" name="job" required class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="noteJob" name="job" required class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select a job...</option>
</select>
</div>
@@ -128,7 +150,7 @@
<div>
<label for="noteSentiment" class="block text-sm font-medium text-gray-700 mb-1">Sentiment</label>
<select id="noteSentiment" name="sentiment" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<select id="noteSentiment" name="sentiment" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
<option value="">Select sentiment...</option>
<option value="Positive">Positive</option>
<option value="Negative">Negative</option>
@@ -138,7 +160,7 @@
<div>
<label for="noteDate" class="block text-sm font-medium text-gray-700 mb-1">Date & Time</label>
<input type="datetime-local" id="noteDate" name="date" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent outline-none text-base">
<input type="datetime-local" id="noteDate" name="date" class="w-full px-3 py-2 border-2 border-gray-300/60 rounded-xl backdrop-blur-sm bg-white/40 focus:ring-2 focus:ring-indigo-500/50 focus:border-indigo-500/70 outline-none text-base transition-all shadow-sm">
</div>
<div class="flex flex-col sm:flex-row justify-end space-y-2 sm:space-y-0 sm:space-x-3 pt-4 border-t">
@@ -163,6 +185,7 @@
<script src="js/pocketbase.js"></script>
<script src="js/config.js"></script>
<script src="js/auth.js"></script>
<script src="js/preferences.js"></script>
<script src="js/jobs.js"></script>
<script src="js/notes.js"></script>
<script>
@@ -171,12 +194,43 @@
// Check authentication
checkAuth();
// Set user email
// Initialize dark mode
initializeDarkMode();
// Set user email and initials
const user = pb.authStore.model;
if (user) {
document.getElementById('userEmail').textContent = user.email || '';
const email = user.email || '';
document.getElementById('userEmail').textContent = email;
// Set initials for avatar
const initials = email
.split('@')[0]
.split('.')
.map(part => part.charAt(0).toUpperCase())
.join('')
.substring(0, 2) || 'U';
document.getElementById('userInitials').textContent = initials;
}
// Toggle user dropdown
function toggleUserDropdown() {
const dropdown = document.getElementById('userDropdown');
dropdown.classList.toggle('hidden');
}
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('userDropdown');
const button = document.getElementById('userAvatarBtn');
if (dropdown && !dropdown.contains(e.target) && !button.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
// Make function globally accessible
window.toggleUserDropdown = toggleUserDropdown;
// Initialize Quill editor
noteEditor = new Quill('#noteEditor', {
theme: 'snow',
+191
View File
@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Tracking - Preferences</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/pocketbase@latest/dist/pocketbase.umd.js"></script>
</head>
<body class="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-indigo-50/40">
<!-- Navigation Header -->
<nav class="backdrop-blur-xl bg-white/70 border-b border-white/20 shadow-sm sticky top-0 z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-row justify-between items-center py-4 px-2">
<div class="flex items-center gap-4">
<a href="dashboard" class="text-indigo-600 hover:text-indigo-700 font-medium text-sm sm:text-base transition-colors">← Dashboard</a>
</div>
<div class="flex items-center gap-3 relative">
<div class="relative">
<button
onclick="toggleUserDropdown()"
class="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 flex items-center justify-center text-white font-medium text-sm hover:scale-105 focus:outline-none transition-all duration-300 shadow-lg shadow-indigo-500/30 hover:shadow-indigo-500/50"
id="userAvatarBtn"
>
<span id="userInitials" class="text-xs font-semibold"></span>
</button>
<div
id="userDropdown"
class="hidden absolute right-0 mt-2 w-48 backdrop-blur-xl bg-white/80 rounded-2xl shadow-2xl border border-white/30 py-2 z-50 overflow-hidden"
>
<div class="px-4 py-3 border-b border-white/20">
<p class="text-xs text-gray-500 font-medium">Signed in as</p>
<p class="text-sm font-semibold text-gray-900 truncate mt-0.5" id="userEmail"></p>
</div>
<a
href="preferences"
class="block px-4 py-2.5 text-sm text-gray-700 hover:bg-white/40 focus:outline-none transition-colors font-medium"
>
Preferences
</a>
<button
onclick="logout()"
class="w-full text-left px-4 py-2.5 text-sm text-red-600 hover:bg-red-50/50 focus:outline-none transition-colors font-medium"
>
Logout
</button>
</div>
</div>
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Page Header -->
<div class="mb-8">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2">Preferences</h1>
<p class="text-sm sm:text-base text-gray-600">Customize your Job-Pages experience</p>
</div>
<!-- Message Container -->
<div id="messageContainer" class="mb-4"></div>
<!-- Preferences Form -->
<div class="backdrop-blur-xl bg-white/60 rounded-2xl shadow-xl border border-white/30 p-6">
<form id="preferencesForm" class="space-y-6">
<!-- Dark Mode Toggle -->
<div class="flex items-center justify-between py-4 border-b border-white/20">
<div class="flex-1">
<h3 class="text-lg font-semibold text-gray-900 mb-1">Dark Mode</h3>
<p class="text-sm text-gray-600">Switch between light and dark themes</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
id="darkModeToggle"
class="sr-only peer"
>
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 dark:peer-focus:ring-indigo-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-600"></div>
</label>
</div>
<!-- Default View Preference -->
<div class="py-4 border-b border-white/20">
<h3 class="text-lg font-semibold text-gray-900 mb-2">Default View</h3>
<p class="text-sm text-gray-600 mb-4">Choose your preferred default view for the jobs page</p>
<div class="flex flex-col sm:flex-row gap-4">
<label class="view-option flex items-center p-4 border-2 border-white/30 backdrop-blur-sm bg-white/40 rounded-2xl cursor-pointer hover:border-indigo-500/50 hover:bg-white/60 transition-all shadow-sm" data-value="table">
<input
type="radio"
name="defaultView"
value="table"
id="viewTable"
class="sr-only"
>
<div class="flex-1">
<div class="flex items-center gap-3">
<svg class="w-6 h-6 text-gray-400 view-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M3 14h18m-9-4v8m-7 0h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
<div>
<div class="font-medium text-gray-900 view-title">Table View</div>
<div class="text-sm text-gray-500">View jobs in a table format</div>
</div>
</div>
</div>
</label>
<label class="view-option flex items-center p-4 border-2 border-white/30 backdrop-blur-sm bg-white/40 rounded-2xl cursor-pointer hover:border-indigo-500/50 hover:bg-white/60 transition-all shadow-sm" data-value="card">
<input
type="radio"
name="defaultView"
value="card"
id="viewCard"
class="sr-only"
>
<div class="flex-1">
<div class="flex items-center gap-3">
<svg class="w-6 h-6 text-gray-400 view-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"></path>
</svg>
<div>
<div class="font-medium text-gray-900 view-title">Card View</div>
<div class="text-sm text-gray-500">View jobs in a card format</div>
</div>
</div>
</div>
</label>
</div>
</div>
<!-- Save Button -->
<div class="flex justify-end pt-4">
<button
type="submit"
class="px-6 py-2.5 backdrop-blur-sm bg-indigo-600/90 text-white rounded-xl font-medium hover:bg-indigo-700/90 focus:outline-none focus:ring-2 focus:ring-indigo-500/50 transition-all shadow-lg hover:shadow-xl"
>
Save Preferences
</button>
</div>
</form>
</div>
</main>
<script src="js/pocketbase.js"></script>
<script src="js/config.js"></script>
<script src="js/auth.js"></script>
<script src="js/preferences.js"></script>
<script>
// Check authentication
checkAuth();
// Set user email and initials
const user = pb.authStore.model;
if (user) {
const email = user.email || '';
document.getElementById('userEmail').textContent = email;
// Set initials for avatar
const initials = email
.split('@')[0]
.split('.')
.map(part => part.charAt(0).toUpperCase())
.join('')
.substring(0, 2) || 'U';
document.getElementById('userInitials').textContent = initials;
}
// Toggle user dropdown
function toggleUserDropdown() {
const dropdown = document.getElementById('userDropdown');
dropdown.classList.toggle('hidden');
}
// Close dropdown when clicking outside
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('userDropdown');
const button = document.getElementById('userAvatarBtn');
if (dropdown && !dropdown.contains(e.target) && !button.contains(e.target)) {
dropdown.classList.add('hidden');
}
});
// Make function globally accessible
window.toggleUserDropdown = toggleUserDropdown;
// Load preferences on page load
loadPreferences();
</script>
</body>
</html>