Mass UI Updates
This commit is contained in:
@@ -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
|
||||
================================================================================
|
||||
File diff suppressed because it is too large
Load Diff
+77
-23
@@ -7,20 +7,42 @@
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<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 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>
|
||||
@@ -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
@@ -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');
|
||||
|
||||
@@ -8,24 +8,63 @@
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<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>
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+74
-20
@@ -9,23 +9,45 @@
|
||||
<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>
|
||||
<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"
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
<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>
|
||||
@@ -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',
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user