725 lines
17 KiB
Markdown
725 lines
17 KiB
Markdown
# View Architecture & Reference
|
|
|
|
**Version:** 1.0
|
|
**Status:** PRODUCTION
|
|
**Last Updated:** January 2026
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This document defines the professional view architecture for Job Info. All UI should follow these patterns for consistency, maintainability, and professional appearance.
|
|
|
|
---
|
|
|
|
## View Hierarchy
|
|
|
|
```
|
|
JobInfo Application
|
|
├── AuthView
|
|
│ └── Sign In Form
|
|
├── LandingView
|
|
│ ├── Header
|
|
│ ├── JobCardView
|
|
│ │ ├── Search & Filters
|
|
│ │ └── Job Cards (Grid)
|
|
│ └── Footer
|
|
├── ManagerInfoView
|
|
│ ├── Job Header
|
|
│ ├── Job Info Panel
|
|
│ ├── FileListView
|
|
│ │ ├── File Search
|
|
│ │ ├── File Categories
|
|
│ │ │ ├── Manager Info
|
|
│ │ │ ├── Contracts / Estimates
|
|
│ │ │ ├── Submittals
|
|
│ │ │ ├── Plans
|
|
│ │ │ └── Other
|
|
│ │ └── File Actions
|
|
│ ├── NotesSection
|
|
│ └── Footer
|
|
├── NotesView
|
|
│ ├── Notes Header
|
|
│ ├── Notes Content (Read-only)
|
|
│ └── Back to Job Detail
|
|
└── FilePreviewModal
|
|
├── PDF Viewer
|
|
├── Image Viewer
|
|
└── Word Document Handler
|
|
```
|
|
|
|
---
|
|
|
|
## 1. AuthView (Sign In Page)
|
|
|
|
**Purpose:** Authenticate user via PocketBase
|
|
|
|
**Components:**
|
|
- Logo/Header
|
|
- Email input
|
|
- Password input
|
|
- Sign In button
|
|
- Error messages
|
|
- Remember Me checkbox (optional)
|
|
|
|
**State:**
|
|
- `isLoading`: boolean
|
|
- `errorMessage`: string | null
|
|
- `email`: string
|
|
- `password`: string
|
|
|
|
**Key Functions:**
|
|
```javascript
|
|
async function handleSignIn(email, password) {
|
|
// 1. Show loading state
|
|
// 2. Call pb.collection('users').authWithPassword()
|
|
// 3. Store token in localStorage
|
|
// 4. Redirect to index.html (LandingView)
|
|
// 5. Handle errors gracefully
|
|
}
|
|
```
|
|
|
|
**HTML Structure:**
|
|
```html
|
|
<div class="auth-container">
|
|
<div class="auth-card">
|
|
<div class="auth-header">
|
|
<img src="assets/logo.png" alt="Logo" />
|
|
<h1>Job Info</h1>
|
|
</div>
|
|
|
|
<form id="signinForm" class="auth-form">
|
|
<div class="form-group">
|
|
<label for="email">Email</label>
|
|
<input
|
|
id="email"
|
|
type="email"
|
|
placeholder="you@example.com"
|
|
class="form-input"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Password</label>
|
|
<input
|
|
id="password"
|
|
type="password"
|
|
placeholder="••••••••"
|
|
class="form-input"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div id="errorMsg" class="error-message hidden"></div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-lg w-full">
|
|
Sign In
|
|
</button>
|
|
</form>
|
|
|
|
<div class="auth-footer">
|
|
<p class="text-sm text-gray-600">
|
|
Secure login via PocketBase
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**CSS Classes:**
|
|
```css
|
|
.auth-container { /* Full screen container */ }
|
|
.auth-card { /* Centered white card */ }
|
|
.auth-header { /* Logo + title */ }
|
|
.auth-form { /* Form styling */ }
|
|
.form-group { /* Input group */ }
|
|
.form-input { /* Input field */ }
|
|
.error-message { /* Red error text */ }
|
|
.btn { /* Button base */ }
|
|
.btn-primary { /* Blue button */ }
|
|
.btn-lg { /* Large button */ }
|
|
```
|
|
|
|
---
|
|
|
|
## 2. LandingView (Home / Jobs List)
|
|
|
|
**Purpose:** Display list of jobs with search and filters
|
|
|
|
**Components:**
|
|
- Header (with user info & sign out)
|
|
- Search input
|
|
- Filter controls (status, date range)
|
|
- Job cards in grid
|
|
- Pagination
|
|
- Loading skeleton
|
|
- Empty state
|
|
|
|
**State:**
|
|
```javascript
|
|
const landingState = {
|
|
jobs: [], // Job list
|
|
page: 1,
|
|
perPage: 10,
|
|
total: 0,
|
|
sort: '-Job_Number', // Default sort
|
|
searchTerm: '',
|
|
filters: {
|
|
status: '',
|
|
dateRange: { start: null, end: null }
|
|
},
|
|
isLoading: false,
|
|
error: null,
|
|
cache: {} // For pagination memory
|
|
};
|
|
```
|
|
|
|
**Key Functions:**
|
|
```javascript
|
|
async function loadJobs(page = 1, searchTerm = '', filters = {}) {
|
|
// 1. Show loading skeleton
|
|
// 2. Build cache key
|
|
// 3. Check Redis cache
|
|
// 4. If miss, fetch from /api/jobs
|
|
// 5. Display jobs
|
|
// 6. Handle pagination
|
|
}
|
|
|
|
function handleSearch(term) {
|
|
// 1. Debounce (300ms)
|
|
// 2. Update state.searchTerm
|
|
// 3. Reset to page 1
|
|
// 4. Call loadJobs()
|
|
}
|
|
|
|
function handleFilterChange(filter) {
|
|
// 1. Update state.filters
|
|
// 2. Reset to page 1
|
|
// 3. Call loadJobs()
|
|
}
|
|
|
|
async function handleJobClick(job) {
|
|
// 1. Navigate to ManagerInfoView
|
|
// 2. Store current job in state
|
|
// 3. Fetch job files
|
|
}
|
|
```
|
|
|
|
**HTML Structure:**
|
|
```html
|
|
<div class="landing-container">
|
|
<!-- Header -->
|
|
<header class="landing-header">
|
|
<div class="header-left">
|
|
<img src="assets/logo.png" alt="Logo" class="logo" />
|
|
<h1>Job Info</h1>
|
|
</div>
|
|
|
|
<div class="header-right">
|
|
<span class="user-name" id="userNameDisplay">John Doe</span>
|
|
<button class="btn btn-secondary" onclick="handleSignOut()">
|
|
Sign Out
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Search & Filters -->
|
|
<div class="search-filters-bar">
|
|
<div class="search-group">
|
|
<input
|
|
type="search"
|
|
id="jobSearch"
|
|
placeholder="Search jobs by name or number..."
|
|
class="search-input"
|
|
/>
|
|
<button class="btn-search">🔍</button>
|
|
</div>
|
|
|
|
<div class="filters-group">
|
|
<select id="statusFilter" class="filter-select">
|
|
<option value="">All Statuses</option>
|
|
<option value="Active">Active</option>
|
|
<option value="Completed">Completed</option>
|
|
<option value="Pending">Pending</option>
|
|
</select>
|
|
|
|
<input
|
|
type="date"
|
|
id="startDate"
|
|
class="filter-input"
|
|
/>
|
|
<input
|
|
type="date"
|
|
id="endDate"
|
|
class="filter-input"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Job Cards Grid -->
|
|
<div class="jobs-grid" id="jobsGrid">
|
|
<!-- JobCard components inserted here -->
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="pagination">
|
|
<button class="btn-pagination" onclick="previousPage()">← Previous</button>
|
|
<span id="pageInfo">Page 1 of 10</span>
|
|
<button class="btn-pagination" onclick="nextPage()">Next →</button>
|
|
</div>
|
|
|
|
<!-- Loading Skeleton -->
|
|
<div id="loadingSkeleton" class="skeleton-grid hidden">
|
|
<!-- 10 skeleton cards -->
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div id="emptyState" class="empty-state hidden">
|
|
<div class="empty-state-icon">📭</div>
|
|
<h3>No jobs found</h3>
|
|
<p>Try adjusting your filters or search term</p>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**JobCard Component:**
|
|
```html
|
|
<div class="job-card" onclick="handleJobClick(jobData)">
|
|
<div class="card-header">
|
|
<h3 class="card-title">Job# 4757</h3>
|
|
<span class="card-badge badge-active">Active</span>
|
|
</div>
|
|
|
|
<div class="card-body">
|
|
<p class="card-subtitle">Construction Project Name</p>
|
|
|
|
<div class="card-details">
|
|
<div class="detail-row">
|
|
<span class="detail-label">Manager:</span>
|
|
<span class="detail-value">John Smith</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Start Date:</span>
|
|
<span class="detail-value">Jan 15, 2025</span>
|
|
</div>
|
|
<div class="detail-row">
|
|
<span class="detail-label">Files:</span>
|
|
<span class="detail-value">42</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-footer">
|
|
<button class="btn-card-action">View Details →</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**CSS Tailwind Classes:**
|
|
```css
|
|
.landing-container /* Full page layout */
|
|
.landing-header /* Sticky header with logo */
|
|
.header-left /* Logo + title */
|
|
.header-right /* User info + sign out */
|
|
.search-filters-bar /* Search + filter controls */
|
|
.search-group /* Search input + button */
|
|
.filters-group /* Filter dropdowns */
|
|
.jobs-grid /* Grid of cards (responsive) */
|
|
.job-card /* Individual card */
|
|
.card-header /* Card title + badge */
|
|
.card-body /* Card content */
|
|
.card-details /* Key-value details */
|
|
.pagination /* Page navigation */
|
|
.skeleton-grid /* Loading placeholders */
|
|
.empty-state /* No results message */
|
|
```
|
|
|
|
---
|
|
|
|
## 3. ManagerInfoView (Job Detail)
|
|
|
|
**Purpose:** Display job details, files, and notes
|
|
|
|
**Components:**
|
|
- Job header with info
|
|
- File list (categorized)
|
|
- Notes section
|
|
- Back button
|
|
|
|
**State:**
|
|
```javascript
|
|
const managerInfoState = {
|
|
job: null, // Current job object
|
|
files: [], // Files for this job
|
|
fileFilter: '', // Search within files
|
|
notes: [], // Sticky notes
|
|
isLoadingFiles: false,
|
|
error: null
|
|
};
|
|
```
|
|
|
|
**HTML Structure:**
|
|
```html
|
|
<div class="manager-info-container">
|
|
<!-- Back Button -->
|
|
<button class="btn btn-secondary" onclick="goBackToLanding()">
|
|
← Back to Jobs
|
|
</button>
|
|
|
|
<!-- Job Header -->
|
|
<div class="job-header">
|
|
<div class="header-title">
|
|
<h1>Job# <span id="jobNumber">4757</span></h1>
|
|
<p class="job-name" id="jobName">Construction Project Name</p>
|
|
</div>
|
|
|
|
<div class="header-badges">
|
|
<span class="badge badge-active">Active</span>
|
|
<span class="badge">42 Files</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Job Info Panel -->
|
|
<div class="job-info-panel">
|
|
<div class="info-group">
|
|
<label>Manager</label>
|
|
<p class="info-value" id="jobManager">John Smith</p>
|
|
</div>
|
|
|
|
<div class="info-group">
|
|
<label>Contact</label>
|
|
<p class="info-value" id="jobContact">john@example.com</p>
|
|
</div>
|
|
|
|
<div class="info-group">
|
|
<label>Start Date</label>
|
|
<p class="info-value" id="jobStartDate">Jan 15, 2025</p>
|
|
</div>
|
|
|
|
<div class="info-group">
|
|
<label>Status</label>
|
|
<p class="info-value" id="jobStatus">Active</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- File List Section -->
|
|
<div class="file-list-section">
|
|
<h2>Files</h2>
|
|
|
|
<div class="file-search-row">
|
|
<input
|
|
id="fileSearchInput"
|
|
type="search"
|
|
placeholder="Search files..."
|
|
class="search-input"
|
|
/>
|
|
<a href="#" target="_blank" rel="noopener" class="btn btn-secondary">
|
|
Open Folder
|
|
</a>
|
|
</div>
|
|
|
|
<!-- File Groups (categorized) -->
|
|
<div id="fileGroups" class="file-groups">
|
|
<!-- FileGroup components inserted here -->
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div id="fileLoading" class="loading-spinner hidden">
|
|
<div class="spinner"></div>
|
|
<p>Loading files...</p>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div id="filesEmpty" class="empty-state hidden">
|
|
<p>No files found</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Section -->
|
|
<div class="notes-section">
|
|
<h2>Notes</h2>
|
|
|
|
<div class="notes-grid" id="notesGrid">
|
|
<!-- StickyNote components inserted here -->
|
|
</div>
|
|
|
|
<button class="btn btn-primary" onclick="expandNotes()">
|
|
Expand Notes
|
|
</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**FileGroup Component:**
|
|
```html
|
|
<div class="file-group">
|
|
<div class="group-header">
|
|
<h3>Manager Info <span class="file-count">(5)</span></h3>
|
|
</div>
|
|
|
|
<div class="file-list">
|
|
<div class="file-item">
|
|
<div class="file-icon">📄</div>
|
|
<div class="file-info">
|
|
<p class="file-name">manager_info.pdf</p>
|
|
<p class="file-meta">1.2 MB · Jan 20, 2025</p>
|
|
</div>
|
|
<div class="file-actions">
|
|
<button class="btn btn-sm btn-primary" onclick="previewFile(...)">
|
|
Preview
|
|
</button>
|
|
<a href="#" target="_blank" class="btn btn-sm btn-secondary">
|
|
Download
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- More files... -->
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
**StickyNote Component:**
|
|
```html
|
|
<div class="sticky-note note-yellow">
|
|
<div class="note-content">
|
|
<p>Project kickoff meeting scheduled for Monday</p>
|
|
</div>
|
|
<div class="note-date">Jan 20, 2025</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 4. NotesView (Expanded Notes)
|
|
|
|
**Purpose:** Full-screen view of job notes
|
|
|
|
**Components:**
|
|
- Notes list (full content)
|
|
- Back button
|
|
|
|
**HTML Structure:**
|
|
```html
|
|
<div class="notes-view-container">
|
|
<button class="btn btn-secondary" onclick="goBackToJobDetail()">
|
|
← Back to Job
|
|
</button>
|
|
|
|
<h1>Job# <span id="noteJobNumber">4757</span> - Notes</h1>
|
|
|
|
<div class="notes-expanded-grid" id="notesExpandedGrid">
|
|
<!-- Full StickyNote components with larger content -->
|
|
</div>
|
|
|
|
<div id="notesEmpty" class="empty-state hidden">
|
|
<p>No notes for this job</p>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 5. FilePreviewModal
|
|
|
|
**Purpose:** Preview files (PDF, images, documents)
|
|
|
|
**Components:**
|
|
- Modal overlay
|
|
- Viewer (PDF, image, or iframe for documents)
|
|
- File info
|
|
- Close button
|
|
|
|
**HTML Structure:**
|
|
```html
|
|
<div id="iframeViewerModal" class="modal hidden">
|
|
<div class="modal-content">
|
|
<!-- Header -->
|
|
<div class="modal-header">
|
|
<h3 id="iframeTitle">File Preview</h3>
|
|
<button class="btn-close" onclick="closeModal()">✕</button>
|
|
</div>
|
|
|
|
<!-- PDF Viewer -->
|
|
<div id="pdfViewer" class="viewer hidden">
|
|
<!-- Canvas for PDF rendering -->
|
|
</div>
|
|
|
|
<!-- Image Viewer -->
|
|
<div id="imageViewer" class="viewer hidden">
|
|
<img id="imageContent" src="" alt="" />
|
|
</div>
|
|
|
|
<!-- iFrame for Documents -->
|
|
<div id="iframeViewer" class="viewer hidden">
|
|
<iframe id="fileFrame" src=""></iframe>
|
|
</div>
|
|
|
|
<!-- Loading -->
|
|
<div id="iframeLoader" class="loader hidden">
|
|
<div class="spinner"></div>
|
|
<p>Loading file...</p>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="modal-footer">
|
|
<a id="downloadLink" href="" target="_blank" class="btn btn-primary">
|
|
Download
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Component Standards
|
|
|
|
### Button Styles
|
|
|
|
```html
|
|
<!-- Primary (Blue) -->
|
|
<button class="btn btn-primary">Action</button>
|
|
|
|
<!-- Secondary (Gray) -->
|
|
<button class="btn btn-secondary">Cancel</button>
|
|
|
|
<!-- Danger (Red) -->
|
|
<button class="btn btn-danger">Delete</button>
|
|
|
|
<!-- Disabled -->
|
|
<button class="btn btn-primary" disabled>Disabled</button>
|
|
|
|
<!-- Sizes -->
|
|
<button class="btn btn-sm">Small</button>
|
|
<button class="btn">Normal</button>
|
|
<button class="btn btn-lg">Large</button>
|
|
```
|
|
|
|
### Card Styles
|
|
|
|
```html
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>Title</h3>
|
|
</div>
|
|
<div class="card-body">
|
|
<p>Content</p>
|
|
</div>
|
|
<div class="card-footer">
|
|
<button>Action</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### Badge Styles
|
|
|
|
```html
|
|
<span class="badge badge-active">Active</span>
|
|
<span class="badge badge-pending">Pending</span>
|
|
<span class="badge badge-completed">Completed</span>
|
|
```
|
|
|
|
### Input Styles
|
|
|
|
```html
|
|
<input type="text" class="input-field" placeholder="..." />
|
|
<input type="email" class="input-field" placeholder="..." />
|
|
<input type="date" class="input-field" />
|
|
|
|
<select class="select-field">
|
|
<option>Option 1</option>
|
|
</select>
|
|
|
|
<textarea class="textarea-field"></textarea>
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Responsive Design
|
|
|
|
### Breakpoints (Tailwind)
|
|
|
|
```
|
|
Mobile: < 640px (sm: 640px)
|
|
Tablet: 768px (md: 768px)
|
|
Desktop: 1024px (lg: 1024px)
|
|
Wide: 1280px (xl: 1280px)
|
|
```
|
|
|
|
### Grid Layouts
|
|
|
|
```html
|
|
<!-- Job Cards: 1 col mobile, 2 col tablet, 3 col desktop -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
<!-- Cards -->
|
|
</div>
|
|
|
|
<!-- File List: Full width on mobile, 2 col on tablet+ -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<!-- Files -->
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Accessibility Standards
|
|
|
|
### WCAG 2.1 Level AA
|
|
|
|
- ✅ Color contrast ≥ 4.5:1 for text
|
|
- ✅ Semantic HTML (use `<button>`, not `<div onclick>`)
|
|
- ✅ ARIA labels for icons
|
|
- ✅ Keyboard navigation (Tab, Enter)
|
|
- ✅ Screen reader friendly
|
|
|
|
**Example:**
|
|
```html
|
|
<!-- ✅ CORRECT -->
|
|
<button class="btn" aria-label="Close modal">✕</button>
|
|
<label for="email">Email</label>
|
|
<input id="email" type="email" />
|
|
|
|
<!-- ❌ WRONG -->
|
|
<div onclick="close()" class="cursor-pointer">✕</div>
|
|
<input type="email" placeholder="Email" />
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Internationalization (i18n)
|
|
|
|
### Labels & Messages
|
|
|
|
All user-facing text should be:
|
|
- English
|
|
- Professional tone
|
|
- Clear and concise
|
|
- Consistent across views
|
|
|
|
**Example:**
|
|
```javascript
|
|
const LABELS = {
|
|
SIGN_IN: 'Sign In',
|
|
SEARCH_JOBS: 'Search jobs by name or number...',
|
|
NO_FILES_FOUND: 'No files found',
|
|
LOADING: 'Loading...',
|
|
ERROR: 'An error occurred. Please try again.'
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
| View | Purpose | Key Components |
|
|
|------|---------|-----------------|
|
|
| AuthView | User login | Form, inputs, validation |
|
|
| LandingView | Job list | Cards, search, filters, pagination |
|
|
| ManagerInfoView | Job detail | Header, files, notes, actions |
|
|
| NotesView | Expanded notes | Full-screen notes display |
|
|
| FilePreviewModal | File preview | Viewer (PDF/image/document) |
|
|
|
|
**All UI must follow these patterns for consistency and professionalism.**
|