formatting improvements.

This commit is contained in:
2025-11-18 23:11:14 -08:00
parent 5524d7308c
commit 0bb071f337
6 changed files with 660 additions and 407 deletions

View File

@@ -27,18 +27,37 @@ rothbard/
├── generate_sample.py # Utility to generate sample Filevine API responses
├── requirements.txt # Python dependencies
├── rothbard-service-account.json # Firebase service account credentials
├── rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json # Firebase service account for staging
├── static/
│ └── auth.js # Client-side authentication handling
├── templates/ # Jinja2 HTML templates
│ ├── base.html # Base template with navigation
│ ├── login.html # Firebase login page
│ ├── welcome.html # User welcome/onboarding page
── dashboard.html # Main case dashboard
── dashboard.html # Main case dashboard
│ ├── admin_users.html # Admin user management interface
│ ├── admin_user_edit.html # Admin user edit interface
│ ├── admin_user_create.html # Admin user creation interface
│ └── _pagination.html # Pagination component
├── examples/ # Sample Filevine API responses
│ ├── project_list.json
│ ├── project_contacts.json
── client.json
└── .env # Environment variables (not tracked)
│ ├── forms__complaintInfo.json
│ ├── forms__newFileReview.json
── project_tasks.json
│ ├── project_type_pahe_list.json
│ ├── project_team.json
│ └── ...
├── .env # Environment variables (not tracked)
├── .gcloudignore # Files ignored by Google Cloud Build
├── .gitignore # Files ignored by Git
├── cloudbuild.yaml # Google Cloud Build configuration
├── Dockerfile # Containerization configuration
├── deploy.sh # Deployment script
├── main.tf # Terraform infrastructure as code
├── firestore.rules # Firestore security rules
├── column_mapping.json # Column mapping configuration (unused)
├── DEPLOY.md # Deployment instructions
├── CLAUDE.md # Development guidance
└── utils.py # Utility functions for user profile management
```
## Core Features
@@ -48,6 +67,7 @@ rothbard/
- Server-side session management with 8-hour expiration
- User profile management in Firestore
- Role-based access control (admin-enabled users only)
- Admin interface for user management
### Case Management Dashboard
- Real-time fetching of projects from Filevine API
@@ -57,6 +77,9 @@ rothbard/
- Project numbers and incident dates
- Contact information and project URLs
- Responsive design using Tailwind CSS
- Configurable column visibility with local storage
- Pagination for large datasets
- Admin simulation mode to view other users' cases
### API Integration
- OAuth 2.0 authentication with Filevine API
@@ -66,6 +89,16 @@ rothbard/
- Individual project details
- Client information
- Project contacts
- Project tasks and forms
### Admin Interface
- Comprehensive user management dashboard
- Enable/disable user access
- Grant/revoke admin privileges
- Reset user passwords
- Create new users
- View all user profiles
- Simulate case email access for testing
## Configuration
@@ -78,11 +111,17 @@ Create a `.env` file with the following variables:
FLASK_SECRET_KEY=your-secret-key-here
# Firebase Configuration
# Choose ONE of these approaches:
# 1) Path to JSON creds file
GOOGLE_APPLICATION_CREDENTIALS=./rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json
# 2) Or inline JSON (escaped as single line)
# FIREBASE_SERVICE_ACCOUNT_JSON="{\"type\":\"service_account\",...}"
# Front-end Firebase (public — safe to expose)
FIREBASE_API_KEY=your-firebase-api-key
FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
FIREBASE_PROJECT_ID=your-firebase-project-id
FIREBASE_APP_ID=your-firebase-app-id
FIREBASE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}' # or set GOOGLE_APPLICATION_CREDENTIALS
# Filevine API Configuration
FILEVINE_CLIENT_ID=your-filevine-client-id
@@ -97,8 +136,9 @@ FILEVINE_USER_ID=your-filevine-user-id
1. Create a Firebase project at https://console.firebase.google.com
2. Enable Authentication with Email/Password provider
3. Create a Firestore database
4. Generate a service account key and save as `rothbard-service-account.json`
4. Generate a service account key and save as `rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json`
5. Configure Authentication settings for your web app
6. Set up Firestore security rules (provided in `firestore.rules`)
### Filevine API Setup
@@ -133,7 +173,7 @@ FILEVINE_USER_ID=your-filevine-user-id
```
5. **Initialize Firebase**
- Place your service account JSON file at `rothbard-service-account.json`
- Place your service account JSON file at `rothbard-staging2-12345-firebase-adminsdk-fbsvc-7f95268383.json`
- Or set the `GOOGLE_APPLICATION_CREDENTIALS` environment variable
6. **Run the application**
@@ -158,6 +198,8 @@ FILEVINE_USER_ID=your-filevine-user-id
1. User profiles are stored in Firestore at `users/{uid}`
2. Enable users by setting `enabled: true` and providing a `caseEmail`
3. The `caseEmail` field determines which projects the user can access
4. Admins can access the admin interface at `/admin/users` to manage all users
5. Admins can simulate other users' case access by entering their email in the simulation field on the dashboard
## Development Tools
@@ -169,9 +211,10 @@ python generate_sample.py
```
This will create JSON files in the `examples/` directory containing:
- Sample project lists
- Project contacts
- Client information
- Sample project forms (complaintInfo, newFileReview)
- Project tasks
- Project type phases
- Project team members
These samples are useful for development and testing without hitting the live API.
@@ -183,6 +226,8 @@ These samples are useful for development and testing without hitting the live AP
- User access is controlled through Firestore profiles
- Sensitive credentials are stored in environment variables
- Filevine API tokens are properly scoped and managed
- Firestore security rules restrict access to user's own profile
- Google Cloud Build and deployment use secure practices
## Future Enhancements
@@ -204,6 +249,8 @@ Planned improvements include:
- **python-dotenv 1.0.1** - Environment variable management
- **requests 2.32.3** - HTTP client for API calls
- **itsdangerous 2.2.0** - Security utilities for Flask
- **gunicorn 23.0.0** - WSGI HTTP Server
- **pytz 2024.1** - Timezone library
## API Endpoints
@@ -214,11 +261,19 @@ Planned improvements include:
- `GET /logout` - Session termination
- `GET /welcome` - User onboarding page
- `GET /dashboard` - Main case dashboard (authenticated users only)
- `GET /admin/users` - Admin user management interface
- `GET /admin/users/<uid>` - Admin user detail view
- `POST /admin/users/update` - Update user information
- `POST /admin/users/create` - Create new user
- `POST /admin/users/<uid>/reset-password` - Reset user password
### Filevine API Integration
- Projects: `/fv-app/v2/Projects`
- Project Details: `/fv-app/v2/Projects/{id}`
- Contacts: `/fv-app/v2/projects/{id}/contacts`
- Project Team: `/fv-app/v2/Projects/{id}/team`
- Project Tasks: `/fv-app/v2/Projects/{id}/tasks`
- Project Forms: `/fv-app/v2/Projects/{id}/Forms/{form}`
- Project Contacts: `/fv-app/v2/projects/{id}/contacts`
- Client Info: `/fv-app/v2/contacts/{id}`
## License

43
app.py
View File

@@ -1,61 +1,20 @@
import json
import os
from functools import wraps
from datetime import datetime, timedelta
import pytz
from flask import Flask, render_template, request, redirect, url_for, session, abort, jsonify
from dotenv import load_dotenv
load_dotenv()
import requests
from filevine_client import FilevineClient
from utils import get_user_profile
from firebase_init import db
from firebase_admin import auth as fb_auth
import config
app = Flask(__name__)
app.secret_key = os.environ.get("FLASK_SECRET_KEY", os.urandom(32))
# --- Firebase Admin init --- (moved to firebase_init.py)
# --- Filevine env ---
FV_CLIENT_ID = os.environ.get("FILEVINE_CLIENT_ID")
FV_CLIENT_SECRET = os.environ.get("FILEVINE_CLIENT_SECRET")
FV_PAT = os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN")
FV_ORG_ID = os.environ.get("FILEVINE_ORG_ID")
FV_USER_ID = os.environ.get("FILEVINE_USER_ID")
if not all([FV_CLIENT_ID, FV_CLIENT_SECRET, FV_PAT, FV_ORG_ID, FV_USER_ID]):
print("[WARN] Missing one or more Filevine env vars — dashboard will fail until set.")
PHASES = {
209436: "Nonpayment File Review",
209437: "Attorney File Review",
209438: "Notice Preparation",
209439: "Notice Pending",
209440: "Notice Expired",
209442: "Preparing and Filing UD",
209443: "Waiting for Answer",
209444: "Archived",
210761: "Service of Process",
211435: "Default",
211436: "Pre-Answer Motion",
211437: "Request for Trial",
211438: "Trial Prep and Trial",
211439: "Writ and Sheriff",
211440: "Lockout Pending",
211441: "Stipulation Preparation",
211442: "Stipulation Pending",
211443: "Stipulation Expired",
211446: "On Hold",
211466: "Request for Monetary Judgment",
211467: "Appeals and Post-Poss. Motions",
211957: "Migrated",
213691: "Close Out/ Invoicing",
213774: "Judgment After Stip & Order",
}
# --- Helpers ---
def login_required(view):

38
config.py Normal file
View File

@@ -0,0 +1,38 @@
import os
# --- Filevine env ---
FV_CLIENT_ID = os.environ.get("FILEVINE_CLIENT_ID")
FV_CLIENT_SECRET = os.environ.get("FILEVINE_CLIENT_SECRET")
FV_PAT = os.environ.get("FILEVINE_PERSONAL_ACCESS_TOKEN")
FV_ORG_ID = os.environ.get("FILEVINE_ORG_ID")
FV_USER_ID = os.environ.get("FILEVINE_USER_ID")
if not all([FV_CLIENT_ID, FV_CLIENT_SECRET, FV_PAT, FV_ORG_ID, FV_USER_ID]):
print("[WARN] Missing one or more Filevine env vars — dashboard will fail until set.")
PHASES = {
209436: "Nonpayment File Review",
209437: "Attorney File Review",
209438: "Notice Preparation",
209439: "Notice Pending",
209440: "Notice Expired",
209442: "Preparing and Filing UD",
209443: "Waiting for Answer",
209444: "Archived",
210761: "Service of Process",
211435: "Default",
211436: "Pre-Answer Motion",
211437: "Request for Trial",
211438: "Trial Prep and Trial",
211439: "Writ and Sheriff",
211440: "Lockout Pending",
211441: "Stipulation Preparation",
211442: "Stipulation Pending",
211443: "Stipulation Expired",
211446: "On Hold",
211466: "Request for Monetary Judgment",
211467: "Appeals and Post-Poss. Motions",
211957: "Migrated",
213691: "Close Out/ Invoicing",
213774: "Judgment After Stip & Order",
}

View File

@@ -40,7 +40,7 @@ def convert_to_pacific_time(date_str):
pacific_time = utc_time.astimezone(pytz.timezone('America/Los_Angeles'))
# Format as YYYY-MM-DD
return pacific_time.strftime('%Y-%m-%d')
return pacific_time.strftime('%d/%m/%Y')
except (ValueError, AttributeError) as e:
print(f"[WARN] Date conversion failed for '{date_str}': {e}")
return ''

6
templates/_expander.html Normal file
View File

@@ -0,0 +1,6 @@
{% macro expander() %}
<div x-ref="content" class="content relative overflow-hidden transition-all duration-500"
:class="expanded ? 'max-h-100' : 'max-h-[3em]'">
{{ caller() }}
<div>
{% endmacro %}

View File

@@ -7,29 +7,24 @@
{% if profile.is_admin %}
<div class="mb-4 flex w-[400px]">
<label for="simulateCaseEmail" class=" text-sm font-medium text-slate-700 mb-1">Simulate case email:</label>
<input type="text"
id="simulateCaseEmail"
x-model="case_email_sim"
@keyup.debounce.1000ms="window.location.href=`/dashboard/1?case_email=${encodeURIComponent($data.case_email_sim)}`"
class="w-full px-3 py-2 border w-64 border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter case email to simulate">
<input type="text" id="simulateCaseEmail" x-model="case_email_sim"
@keyup.debounce.1000ms="window.location.href=`/dashboard/1?case_email=${encodeURIComponent($data.case_email_sim)}`"
class="w-full px-3 py-2 border w-64 border-slate-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
placeholder="Enter case email to simulate">
</div>
{% endif %}
{% endif %}
<!-- Configure Visible Columns Link -->
<div class="mb-4">
<button @click="showColumnModal = true"
class="text-blue-600 hover:text-blue-800 text-sm font-medium underline">
<button @click="showColumnModal = true" class="text-blue-600 hover:text-blue-800 text-sm font-medium underline">
Configure Visible Columns...
</button>
</div>
<!-- Column Configuration Modal -->
<div x-show="showColumnModal"
x-cloak
x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showColumnModal = false">
<div x-show="showColumnModal" x-cloak x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showColumnModal = false">
<div class="bg-white rounded-lg p-6 max-w-2xl w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Configure Visible Columns</h3>
@@ -42,10 +37,8 @@
<div class="mb-4">
<label class="flex items-center">
<input type="checkbox"
x-model="selectAll"
@change="toggleAllColumns()"
class="mr-2 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<input type="checkbox" x-model="selectAll" @change="toggleAllColumns()"
class="mr-2 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm font-medium">Select All / Deselect All</span>
</label>
</div>
@@ -53,12 +46,9 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 max-h-96 overflow-y-auto">
<template x-for="(column, index) in columns" :key="index">
<label class="flex items-center p-2 hover:bg-slate-50 rounded cursor-pointer">
<input type="checkbox"
:value="column"
x-model="visibleColumns"
@change="saveColumnSettings()"
:checked="visibleColumns.includes(column)"
class="mr-2 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<input type="checkbox" :value="column" x-model="visibleColumns" @change="saveColumnSettings()"
:checked="visibleColumns.includes(column)"
class="mr-2 rounded border-gray-300 text-blue-600 focus:ring-blue-500">
<span class="text-sm" x-text="column"></span>
</label>
</template>
@@ -66,365 +56,570 @@
<div class="flex justify-end space-x-3 mt-6">
<button @click="resetToDefault()"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors">
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-md transition-colors">
Reset to Default
</button>
<button @click="showColumnModal = false;"
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors">
class="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md transition-colors">
Apply Changes
</button>
</div>
</div>
</div>
{% from "_expander.html" import expander %}
<div class="overflow-scroll">
<table class="w-full whitespace-nowrap shadow-md border border-slate-200">
<thead class=" text-left text-sm sticky top-0 z-10 border-b border-blue-800 text-white font-medium"
style="background-color: rgb(89, 121, 142);">
<tr>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Matter Num')}">Matter Num</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Client / Property')}">Client / Property</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Matter Description')}">Matter Description</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Defendant 1')}">Defendant 1</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Matter Open')}">Matter Open</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Practice Area')}">Practice Area</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Notice Type')}">Notice Type</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Case Number')}">Case Number</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Premises Address')}">Premises Address</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Premises City')}">Premises City</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Assigned Attorney')}">Assigned Attorney</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Primary Contact')}">Primary Contact</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Secondary Paralegal')}">Secondary Paralegal</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Documents')}">Documents</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Matter Stage')}">Matter Stage</th>
<th class="px-4 py-3 w-[400px]" :class="{'hidden': !isColumnVisible('Completed Tasks')}">Completed Tasks</th>
<th class="px-4 py-3 w-[400px]" :class="{'hidden': !isColumnVisible('Pending Tasks')}">Pending Tasks</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Notice Service Date')}">Notice Service Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Notice Expir. Date')}">Notice Expir. Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Date Case Filed')}">Date Case Filed</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Daily Rent Damages')}">Daily Rent Damages</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Default Date')}">Default Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Default Entered On')}">Default Entered On</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Motions:')}">Motions:</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Demurrer Hearing Date')}">Demurrer Hearing Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Motion To Strike Hearing Date')}">Motion To Strike Hearing Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Motion to Quash Hearing Date')}">Motion to Quash Hearing Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Other Motion Hearing Date')}">Other Motion Hearing Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('MSC Date')}">MSC Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('MSC Time')}">MSC Time</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('MSC Address')}">MSC Address</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('MSC Div/ Dept/ Room')}">MSC Div/ Dept/ Room</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Trial Date')}">Trial Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Trial Time')}">Trial Time</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Trial Address')}">Trial Address</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Trial Div/ Dept/ Room')}">Trial Div/ Dept/ Room</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Final Result of Trial/ MSC')}">Final Result of Trial/ MSC</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Date of Settlement')}">Date of Settlement</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Final Obligation Under the Stip')}">Final Obligation Under the Stip</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Def\'s Comply with the Stip?')}">Def's Comply with the Stip?</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Judgment Date')}">Judgment Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Writ Issued Date')}">Writ Issued Date</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Scheduled Lockout')}">Scheduled Lockout</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Oppose Stays?')}">Oppose Stays?</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Premises Safety or Access Issues')}">Premises Safety or Access Issues</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Matter Gate or Entry Code')}">Matter Gate or Entry Code</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Date Possession Recovered')}">Date Possession Recovered</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Attorney\'s Fees')}">Attorney's Fees</th>
<th class="px-4 py-3" :class="{'hidden': !isColumnVisible('Costs')}">Costs</th>
</tr>
</thead>
<tbody class="bg-slate-100 divide-y divide-slate-300">
{% for r in rows %}
<tr class="hover:bg-slate-200 transition-colors duration-150 ease-in-out">
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Num')}">{{ r.number }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Client / Property')}">{{ r.client }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Description')}">{{ r.matter_description }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Defendant 1')}">{{ r.defendant_1 }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Open')}">{{ r.matter_open }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Practice Area')}"></td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Notice Type')}">{{ r.notice_type }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Case Number')}">{{ r.case_number }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Premises Address')}">{{ r.premises_address }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Premises City')}">{{ r.premises_city }}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Assigned Attorney')}">
<table class="w-full table-fixed shadow-md border border-slate-200">
<thead class=" text-left text-sm z-10 border-b border-blue-800 text-white font-medium"
style="background-color: rgb(89, 121, 142);">
<tr>
<th class="px-4 py-3 w-16 sticky left-0 top-0 z-[40]" style="background-color: rgb(89, 121, 142);"></th>
<th class="px-4 py-3 w-32 sticky left-16 top-0 z-[40]" style="background-color: rgb(89, 121, 142);" :class="{'hidden': !isColumnVisible('Matter Num')}">Matter Num</th>
<th class="px-4 py-3 w-64 sticky left-48 top-0 z-[40]" style="background-color: rgb(89, 121, 142);" :class="{'hidden': !isColumnVisible('Client / Property')}">Client / Property</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-72 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Matter Description')}">Matter Description</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Defendant 1')}">Defendant 1</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Matter Open')}">Matter Open</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Practice Area')}">Practice Area</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-64 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Notice Type')}">Notice Type</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Case Number')}">Case Number</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-72 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Premises Address')}">Premises Address</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isCstyle="background-color: rgb(89, 121, 142);" olumnVisible('Premises City')}">Premises City</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Assigned Attorney')}">Assigned Attorney</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Primary Contact')}">Primary Contact</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Secondary Paralegal')}">Secondary Paralegal</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Documents')}">Documents</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-64 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Matter Stage')}">Matter Stage</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-[400px] sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Completed Tasks')}">Completed Tasks</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-[400px] sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Pending Tasks')}">Pending Tasks</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Notice Service Date')}">Notice Service Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Notice Expir. Date')}">Notice Expir. Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Date Case Filed')}">Date Case Filed</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Daily Rent Damages')}">Daily Rent Damages</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Default Date')}">Default Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Default Entered On')}">Default Entered On</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Motions:')}">Motions:</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Demurrer Hearing Date')}">Demurrer Hearing Date </th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Motion To Strike Hearing Date')}">Motion To Strike Hearing Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Motion to Quash Hearing Date')}">Motion to Quash Hearing Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Other Motion Hearing Date')}">Other Motion Hearing Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('MSC Date')}">MSC Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('MSC Time')}">MSC Time</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-72 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('MSC Address')}">MSC Address</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('MSC Div/ Dept/ Room')}">MSC Div/ Dept/ Room</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Trial Date')}">Trial Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Trial Time')}">Trial Time</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-72 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Trial Address')}">Trial Address</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Trial Div/ Dept/ Room')}">Trial Div/ Dept/ Room </th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-48 sticky top-0 z-[40]" :class="{'hidden': !isColumnVisible('Final Result of Trial/ MSC')}">Final Result of Trial/ MSC</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Date of Settlement')}">Date of Settlement</th> <th class="px-4 py-3 w-32" :class="{'hidden': !isColumnVisible('Final Obligation Under the Stip')}">Final Obligation Under the Stip</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Def\'s Comply with the Stip?')}">Def's Comply with the Stip?</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Judgment Date')}">Judgment Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Writ Issued Date')}">Writ Issued Date</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Scheduled Lockout')}">Scheduled Lockout</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Oppose Stays?')}">Oppose Stays?</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-64 sticky top-0" :class="{'hidden': !isColumnVisible('Premises Safety or Access Issues')}">Premises Safety or Access Issues</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-64 sticky top-0" :class="{'hidden': !isColumnVisible('Matter Gate or Entry Code')}">Matter Gate or Entry Code</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Date Possession Recovered')}">Date Possession Recovered</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Attorney\'s Fees')}">Attorney's Fees</th>
<th style="background-color: rgb(89, 121, 142);" class="px-4 py-3 w-32 sticky top-0" :class="{'hidden': !isColumnVisible('Costs')}">Costs</th>
</tr>
</thead>
<tbody class="bg-slate-100 divide-y divide-slate-300">
{% for r in rows %}
<tr class="hover:bg-slate-200 transition-colors duration-150 ease-in-out group"
x-data="{ expanded: false, hasOverflow: false }" x-init="
$nextTick(() => {
// Check overflow for ANY td in this row
const tds = $el.querySelectorAll('td .content');
hasOverflow = Array.from(tds).some(td => td.scrollHeight > td.clientHeight + 1);
});
">
<td class="px-4 py-3 sticky left-0 bg-slate-100 z-[30] group-hover:bg-slate-200 ease-in-out duration-150 transition-colors">
<button x-show="hasOverflow" x-on:click="expanded = !expanded"
class="mt-1 h-5 w-5 flex items-center justify-center rounded border cursor-pointer text-xs">
<span class="inline-block transition-transform duration-150" :class="expanded ? 'rotate-90' : ''"></span>
</button>
</td>
<td class="px-4 py-3 text-sm text-slate-800 left-16 bg-slate-100 sticky z-[30] group-hover:bg-slate-200 ease-in-out duration-150 transition-colors" :class="{'hidden': !isColumnVisible('Matter Num')}">
{{ r.number }}
</td>
<td class="px-4 py-3 text-sm text-slate-800 sticky bg-slate-100 left-48 w-64 z-[30] group-hover:bg-slate-200 ease-in-out duration-150 transition-colors" :class="{'hidden': !isColumnVisible('Client / Property')}">
{% call expander() %}
{{ r.client }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800 w-64"
:class="{'hidden': !isColumnVisible('Matter Description')}">
{% call expander() %}
{{ r.matter_description }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Defendant 1')}">
{% call expander() %}
{{ r.defendant_1 }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Open')}">
{% call expander() %}
{{ r.matter_open }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Practice Area')}">
{% call expander() %}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Notice Type')}">
{% call expander() %}
{{ r.notice_type }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Case Number')}">
{% call expander() %}
{{ r.case_number }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800 w-[200px]" :class="{'hidden': !isColumnVisible('Premises Address')}">
{% call expander() %}
{{ r.premises_address }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Premises City')}">
{% call expander() %}
{{ r.premises_city }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Assigned Attorney')}">
{% call expander() %}
{{ r.responsible_attorney }}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Primary Contact')}">
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Primary Contact')}">
{% call expander() %}
{{ r.staff_person }}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Secondary Paralegal')}">
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Secondary Paralegal')}">
{% call expander() %}
{{ r.staff_person_2 }}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Documents')}">
{% if r.documents_url %}
<a href="{{ r.documents_url }}">Documents</a>
{% endif %}</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Stage')}"> {{ r.phase_name }}</td>
<td class="px-4 py-3 text-sm align-top whitespace-normal w-[400px]" :class="{'hidden': !isColumnVisible('Completed Tasks')}" x-data="{ showCompletedModal: false}">
{% if r.completed_tasks %}
<div>
<table class="w-full text-xs">
<thead>
<tr>
<th class="text-left pb-1">Task</th>
<th class="text-right pb-1">Completed</th>
</tr>
</thead>
<tbody>
{% for x in r.completed_tasks[:2] %}
<tr>
<td class="py-1 pr-2 break-words">{{ x.description }}</td>
<td class="py-1 text-right whitespace-nowrap">{{ x.completed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if r.completed_tasks|length > 2 %}
<button @click="showCompletedModal = true" class="text-blue-500 hover:text-blue-700 text-sm mt-1">Show more...</button>
{% endif %}
</div>
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Documents')}">
<div x-show="showCompletedModal"
x-cloak
x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showCompletedModal = false">
<div class="bg-white rounded-lg p-6 max-w-lg w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Completed Tasks</h3>
<button @click="showCompletedModal = false" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<table class="w-full">
{% call expander() %}
{% endcall %}
{% if r.documents_url %}
<a class="text-blue-500 text-underline" href=" {{ r.documents_url }}">Documents</a>
{% endif %}
</td>
<td class="px-4 py-3 text-sm text-slate-800" :class="{'hidden': !isColumnVisible('Matter Stage')}">
{% call expander() %}
{{ r.phase_name }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm align-top whitespace-normal w-[400px]"
:class="{'hidden': !isColumnVisible('Completed Tasks')}" x-data="{ showCompletedModal: false}">
{% call expander() %}
{% if r.completed_tasks %}
<div>
<table class="w-full text-xs">
<thead>
<tr class="border-b">
<th class="text-left pb-2">Task Description</th>
<th class="text-right pb-2">Completed Date</th>
<tr>
<th class="text-left pb-1">Task</th>
<th class="text-right pb-1">Completed</th>
</tr>
</thead>
<tbody>
{% for x in r.completed_tasks %}
<tr class="border-b border-slate-200">
<td class="py-2 break-words">{{ x.description }}</td>
<td class="py-2 text-right whitespace-nowrap">{{ x.completed }}</td>
{% for x in r.completed_tasks[:2] %}
<tr>
<td class="py-1 pr-2 break-words">{{ x.description }}</td>
<td class="py-1 text-right whitespace-nowrap">{{ x.completed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if r.completed_tasks|length > 2 %}
<button @click="showCompletedModal = true" class="text-blue-500 hover:text-blue-700 text-sm mt-1">Show
more...</button>
{% endif %}
</div>
</div>
{% endif %}
</td>
<td class="px-4 py-3 text-sm align-top whitespace-normal w-[400px] min-w-[400px]" :class="{'hidden': !isColumnVisible('Pending Tasks')}" x-data="{ showPendingModal: false }">
{% if r.pending_tasks %}
<div>
<table class="w-full text-xs">
<thead>
<tr>
<th class="text-left pb-1">Task</th>
</tr>
</thead>
<tbody>
{% for x in r.pending_tasks[:2] %}
<tr>
<td class="py-1 break-words">{{ x.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if r.pending_tasks|length > 2 %}
<button @click="showPendingModal = true" class="text-blue-500 hover:text-blue-700 text-sm mt-1">Show more...</button>
{% endif %}
</div>
<div x-show="showPendingModal"
x-cloak
x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showPendingModal = false">
<div class="bg-white rounded-lg p-6 max-w-lg w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Pending Tasks</h3>
<button @click="showPendingModal = false" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
<div x-show="showCompletedModal" x-cloak x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showCompletedModal = false">
<div class="bg-white rounded-lg p-6 max-w-lg w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Completed Tasks</h3>
<button @click="showCompletedModal = false" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
</path>
</svg>
</button>
</div>
<table class="w-full">
<thead>
<tr class="border-b">
<th class="text-left pb-2">Task Description</th>
<th class="text-right pb-2">Completed Date</th>
</tr>
</thead>
<tbody>
{% for x in r.completed_tasks %}
<tr class="border-b border-slate-200">
<td class="py-2 break-words">{{ x.description }}</td>
<td class="py-2 text-right whitespace-nowrap">{{ x.completed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<table class="w-full">
</div>
{% endif %}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm align-top whitespace-normal w-[400px] min-w-[400px]"
:class="{'hidden': !isColumnVisible('Pending Tasks')}" x-data="{ showPendingModal: false }">
{% call expander() %}
{% if r.pending_tasks %}
<div>
<table class="w-full text-xs">
<thead>
<tr class="border-b">
<th class="text-left pb-2">Task Description</th>
<tr>
<th class="text-left pb-1">Task</th>
</tr>
</thead>
<tbody>
{% for x in r.pending_tasks %}
<tr class="border-b border-slate-200">
<td class="py-2 break-words">{{ x.description }}</td>
{% for x in r.pending_tasks[:2] %}
<tr>
<td class="py-1 break-words">{{ x.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if r.pending_tasks|length > 2 %}
<button @click="showPendingModal = true" class="text-blue-500 hover:text-blue-700 text-sm mt-1">Show
more...</button>
{% endif %}
</div>
</div>
{% endif %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Notice Service Date')}">{{ r.notice_service_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Notice Expir. Date')}">{{ r.notice_expiration_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date Case Filed')}">{{ r.case_field_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Daily Rent Damages')}">{% if r.daily_rent_damages %}${{ "{:,.2f}".format(r.daily_rent_damages) }}{% endif %}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Default Date')}">{{ r.default_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Default Entered On')}">{{ r.default_entered_on_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motions:')}"></td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Demurrer Hearing Date')}">{{ r.demurrer_hearing_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motion To Strike Hearing Date')}">{{ r.motion_to_strike_hearing_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motion to Quash Hearing Date')}">{{ r.motion_to_quash_hearing_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Other Motion Hearing Date')}">{{ r.other_motion_hearing_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Date')}">{{ r.msc_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Time')}">{{ r.msc_time }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Address')}">{{ r.msc_address }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Div/ Dept/ Room')}">{{ r.msc_div_dept_room }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Date')}">{{ r.trial_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Time')}">{{ r.trial_time }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Address')}">{{ r.trial_address }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Div/ Dept/ Room')}">{{ r.trial_div_dept_room }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Final Result of Trial/ MSC')}">{{ r.final_result }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date of Settlement')}">{{ r.date_of_settlement }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Final Obligation Under the Stip')}">{{ r.final_obligation }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Def\'s Comply with the Stip?')}">{{ r.def_comply_stip }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Judgment Date')}">{{ r.judgment_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Writ Issued Date')}">{{ r.writ_issued_date }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Scheduled Lockout')}">{{ r.scheduled_lockout }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Oppose Stays?')}">{{ r.oppose_stays }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Premises Safety or Access Issues')}">{{ r.premises_safety }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Matter Gate or Entry Code')}">{{ r.matter_gate_code }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date Possession Recovered')}">{{ r.date_possession_recovered }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Attorney\'s Fees')}">{{ r.attorney_fees }}</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Costs')}">{{ r.costs }}</td>
</tr>
{% else %}
<tr>
<td colspan="53" class="px-4 py-6 text-center text-slate-500">No matching projects found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% include '_pagination.html' %}
<div x-show="showPendingModal" x-cloak x-transition
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
@click.self="showPendingModal = false">
<div class="bg-white rounded-lg p-6 max-w-lg w-full max-h-[80vh] overflow-y-auto">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold">Pending Tasks</h3>
<button @click="showPendingModal = false" class="text-gray-500 hover:text-gray-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12">
</path>
</svg>
</button>
</div>
<table class="w-full">
<thead>
<tr class="border-b">
<th class="text-left pb-2">Task Description</th>
</tr>
</thead>
<tbody>
{% for x in r.pending_tasks %}
<tr class="border-b border-slate-200">
<td class="py-2 break-words">{{ x.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Notice Service Date')}">
{% call expander() %}
{{ r.notice_service_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Notice Expir. Date')}">
{% call expander() %}
{{ r.notice_expiration_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date Case Filed')}">
<script>
function columnConfig() {
return {
showColumnModal: false,
case_email_sim: '',
columns: [
'Matter Num',
'Client / Property',
'Matter Description',
'Defendant 1',
'Matter Open',
'Practice Area',
'Notice Type',
'Case Number',
'Premises Address',
'Premises City',
'Assigned Attorney',
'Primary Contact',
'Secondary Paralegal',
'Documents',
'Matter Stage',
'Completed Tasks',
'Pending Tasks',
'Notice Service Date',
'Notice Expir. Date',
'Date Case Filed',
'Daily Rent Damages',
'Default Date',
'Default Entered On',
'Motions:',
'Demurrer Hearing Date',
'Motion To Strike Hearing Date',
'Motion to Quash Hearing Date',
'Other Motion Hearing Date',
'MSC Date',
'MSC Time',
'MSC Address',
'MSC Div/ Dept/ Room',
'Trial Date',
'Trial Time',
'Trial Address',
'Trial Div/ Dept/ Room',
'Final Result of Trial/ MSC',
'Date of Settlement',
'Final Obligation Under the Stip',
'Def\'s Comply with the Stip?',
'Judgment Date',
'Writ Issued Date',
'Scheduled Lockout',
'Oppose Stays?',
'Premises Safety or Access Issues',
'Matter Gate or Entry Code',
'Date Possession Recovered',
'Attorney\'s Fees',
'Costs'
],
selectAll: true,
visibleColumns: [],
{% call expander() %}
{{ r.case_field_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Daily Rent Damages')}">
init() {
this.loadColumnSettings();
{% call expander() %}
{% if r.daily_rent_damages %}${{ "{:,.2f}".format(r.daily_rent_damages) }}{% endif %}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Default Date')}">
{% call expander() %}
{{ r.default_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Default Entered On')}">
{% call expander() %}
{{ r.default_entered_on_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motions:')}"></td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Demurrer Hearing Date')}">
{% call expander() %}
{{ r.demurrer_hearing_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motion To Strike Hearing Date')}">
{% call expander() %}
{{ r.motion_to_strike_hearing_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Motion to Quash Hearing Date')}">
// Extract case_email from URL query parameter
const urlParams = new URLSearchParams(window.location.search);
const caseEmail = urlParams.get('case_email');
if (caseEmail) {
this.case_email_sim = caseEmail;
{% call expander() %}
{{ r.motion_to_quash_hearing_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Other Motion Hearing Date')}">
{% call expander() %}
{{ r.other_motion_hearing_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Date')}">
{% call expander() %}
{{ r.msc_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Time')}">
{% call expander() %}
{{ r.msc_time }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Address')}">
{% call expander() %}
{{ r.msc_address }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('MSC Div/ Dept/ Room')}">
{% call expander() %}
{{ r.msc_div_dept_room }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Date')}">
{% call expander() %}
{{ r.trial_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Time')}">
{% call expander() %}
{{ r.trial_time }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Address')}">
{% call expander() %}
{{ r.trial_address }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Trial Div/ Dept/ Room')}">
{% call expander() %}
{{ r.trial_div_dept_room }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Final Result of Trial/ MSC')}">
{% call expander() %}
{{ r.final_result }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date of Settlement')}">
{% call expander() %}
{{ r.date_of_settlement }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Final Obligation Under the Stip')}">
{% call expander() %}
{{ r.final_obligation }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Def\'s Comply with the Stip?')}">
{% call expander() %}
{{ r.def_comply_stip }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Judgment Date')}">
{% call expander() %}
{{ r.judgment_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Writ Issued Date')}">
{% call expander() %}
{{ r.writ_issued_date }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Scheduled Lockout')}">
{% call expander() %}
{{ r.scheduled_lockout }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Oppose Stays?')}">
{% call expander() %}
{{ r.oppose_stays }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Premises Safety or Access Issues')}">
{% call expander() %}
{{ r.premises_safety }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Matter Gate or Entry Code')}">
{% call expander() %}
{{ r.matter_gate_code }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Date Possession Recovered')}">
{% call expander() %}
{{ r.date_possession_recovered }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Attorney\'s Fees')}">
{% call expander() %}
{{ r.attorney_fees }}
{% endcall %}
</td>
<td class="px-4 py-3 text-sm" :class="{'hidden': !isColumnVisible('Costs')}">
{% call expander() %}
{{ r.costs }}
{% endcall %}
</td>
</tr>
{% else %}
<tr>
<td colspan="53" class="px-4 py-6 text-center text-slate-500">No matching projects found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% include '_pagination.html' %}
<script>
function columnConfig() {
return {
showColumnModal: false,
case_email_sim: '',
columns: [
'Matter Num',
'Client / Property',
'Matter Description',
'Defendant 1',
'Matter Open',
'Practice Area',
'Notice Type',
'Case Number',
'Premises Address',
'Premises City',
'Assigned Attorney',
'Primary Contact',
'Secondary Paralegal',
'Documents',
'Matter Stage',
'Completed Tasks',
'Pending Tasks',
'Notice Service Date',
'Notice Expir. Date',
'Date Case Filed',
'Daily Rent Damages',
'Default Date',
'Default Entered On',
'Motions:',
'Demurrer Hearing Date',
'Motion To Strike Hearing Date',
'Motion to Quash Hearing Date',
'Other Motion Hearing Date',
'MSC Date',
'MSC Time',
'MSC Address',
'MSC Div/ Dept/ Room',
'Trial Date',
'Trial Time',
'Trial Address',
'Trial Div/ Dept/ Room',
'Final Result of Trial/ MSC',
'Date of Settlement',
'Final Obligation Under the Stip',
'Def\'s Comply with the Stip?',
'Judgment Date',
'Writ Issued Date',
'Scheduled Lockout',
'Oppose Stays?',
'Premises Safety or Access Issues',
'Matter Gate or Entry Code',
'Date Possession Recovered',
'Attorney\'s Fees',
'Costs'
],
selectAll: true,
visibleColumns: [],
init() {
this.loadColumnSettings();
// Extract case_email from URL query parameter
const urlParams = new URLSearchParams(window.location.search);
const caseEmail = urlParams.get('case_email');
if (caseEmail) {
this.case_email_sim = caseEmail;
}
},
isColumnVisible(columnName) {
return this.visibleColumns.includes(columnName);
},
loadColumnSettings() {
const stored = localStorage.getItem('dashboardColumnVisibility');
if (stored) {
this.visibleColumns = JSON.parse(stored);
this.selectAll = this.visibleColumns.length === this.columns.length;
} else {
// Default to showing all columns
this.visibleColumns = [...this.columns];
this.selectAll = true;
}
},
saveColumnSettings() {
localStorage.setItem('dashboardColumnVisibility', JSON.stringify(this.visibleColumns));
},
toggleAllColumns() {
if (this.visibleColumns.length === 0) {
this.visibleColumns = [...this.columns];
this.selectAll = true;
} else {
this.visibleColumns = [];
this.selectAll = false;
}
this.saveColumnSettings();
},
resetToDefault() {
this.visibleColumns = [...this.columns];
this.selectAll = true;
this.saveColumnSettings();
}
}
},
isColumnVisible(columnName) {
return this.visibleColumns.includes(columnName);
},
loadColumnSettings() {
const stored = localStorage.getItem('dashboardColumnVisibility');
if (stored) {
this.visibleColumns = JSON.parse(stored);
this.selectAll = this.visibleColumns.length === this.columns.length;
} else {
// Default to showing all columns
this.visibleColumns = [...this.columns];
this.selectAll = true;
}
},
saveColumnSettings() {
localStorage.setItem('dashboardColumnVisibility', JSON.stringify(this.visibleColumns));
},
toggleAllColumns() {
if (this.selectAll) {
this.visibleColumns = [];
} else {
this.visibleColumns = [...this.columns];
}
this.selectAll = !this.selectAll;
this.saveColumnSettings();
},
resetToDefault() {
this.visibleColumns = [...this.columns];
this.selectAll = true;
this.saveColumnSettings();
}
}
}
</script>
</script>
</div>
<!--