Files
Job-Info/instructions/auth/VIEWS.md
T
2026-01-09 04:08:06 +00:00

17 KiB

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:

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:

<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:

.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:

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:

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:

<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:

<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:

.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:

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:

<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:

<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:

<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:

<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:

<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

<!-- 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

<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

<span class="badge badge-active">Active</span>
<span class="badge badge-pending">Pending</span>
<span class="badge badge-completed">Completed</span>

Input Styles

<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

<!-- 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:

<!-- ✅ 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:

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.