Files
TimeTracker/app/static/enhanced-ui.css
Dries Peeters e1d6fbafa0 feat: standardize table templates and implement bulk actions with enhanced UX
Standardize all table templates across the application and implement comprehensive bulk operations with improved loading states and empty state handling.

Frontend Improvements:

- Standardize table templates: Apply consistent styling, filters, and bulk actions across all list pages

- Add bulk action UI: Implement bulk selectors, action dropdowns, and confirmation dialogs

- Implement loading states: Add skeleton loaders, progress indicators for exports and bulk operations

- Enhance empty states: Create context-aware empty states with clear CTAs

- Improve filter UX: Make filter sections collapsible with localStorage persistence

Backend Implementation:

- Add bulk delete routes: Implement bulk deletion endpoints for invoices, expenses, payments, per diem, and mileage

- Add bulk status update routes: Implement bulk status update endpoints for all applicable resources

- Ensure proper permission checks and comprehensive error handling

UI Components:

- Enhance empty_state macro with context-awareness

- Add skeleton_table, skeleton_card, and progress_indicator components

- Extend enhanced-ui.css with progress-indeterminate animation and fade-in-up transitions

Files Changed: 15 files modified with 1,274 additions
2025-11-05 07:35:26 +01:00

755 lines
15 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================
ENHANCED UI STYLES
Supporting styles for improved UX features
============================================ */
/* Animations */
@keyframes float {
0%, 100% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
@keyframes slideInRight {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.animate-float {
animation: float 3s ease-in-out infinite;
}
.animate-shimmer {
animation: shimmer 2s infinite;
}
.animate-slide-in-right {
animation: slideInRight 0.3s ease-out;
}
.animate-slide-out-right {
animation: slideOutRight 0.3s ease-in;
}
/* Enhanced Table Styles */
.enhanced-table {
position: relative;
}
.enhanced-table th {
user-select: none;
position: relative;
}
.enhanced-table th.sortable {
cursor: pointer;
transition: background-color 0.2s;
}
.enhanced-table th.sortable:hover {
background-color: rgba(0, 0, 0, 0.03);
}
.dark .enhanced-table th.sortable:hover {
background-color: rgba(255, 255, 255, 0.03);
}
.enhanced-table th.sorted-asc::after,
.enhanced-table th.sorted-desc::after {
content: '';
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
}
.enhanced-table th.sorted-asc::after {
border-bottom: 4px solid currentColor;
}
.enhanced-table th.sorted-desc::after {
border-top: 4px solid currentColor;
}
.enhanced-table tr.selected {
background-color: rgba(59, 130, 246, 0.1);
}
.dark .enhanced-table tr.selected {
background-color: rgba(59, 130, 246, 0.2);
}
.enhanced-table tbody tr {
transition: background-color 0.15s;
}
.enhanced-table tbody tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
.dark .enhanced-table tbody tr:hover {
background-color: rgba(255, 255, 255, 0.02);
}
/* Bulk Actions Bar */
.bulk-actions-bar {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
padding: 16px 24px;
display: flex;
align-items: center;
gap: 16px;
z-index: 40;
transition: transform 0.3s ease-out;
}
.dark .bulk-actions-bar {
background: #2d3748;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
}
.bulk-actions-bar.show {
transform: translateX(-50%) translateY(0);
}
/* Filter Chips */
.filter-chips-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
/* Search Enhancement */
.search-container {
position: relative;
}
.search-input {
padding-left: 40px;
padding-right: 100px;
}
.search-icon {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
pointer-events: none;
}
.search-clear {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #9ca3af;
cursor: pointer;
opacity: 0;
transition: opacity 0.2s;
}
.search-clear.show {
opacity: 1;
}
.search-clear:hover {
color: #ef4444;
}
/* Live Search Results */
.search-results-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
margin-top: 4px;
max-height: 400px;
overflow-y: auto;
z-index: 50;
display: none;
}
.dark .search-results-dropdown {
background: #2d3748;
border-color: #4a5568;
}
.search-results-dropdown.show {
display: block;
}
.search-result-item {
padding: 12px;
border-bottom: 1px solid #f3f4f6;
cursor: pointer;
transition: background-color 0.15s;
}
.dark .search-result-item {
border-bottom-color: #374151;
}
.search-result-item:hover {
background-color: #f9fafb;
}
.dark .search-result-item:hover {
background-color: #374151;
}
.search-result-item:last-child {
border-bottom: none;
}
/* Column Resizer */
.column-resizer {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
user-select: none;
background: transparent;
}
.column-resizer:hover,
.column-resizer.resizing {
background: #3b82f6;
}
/* Drag & Drop */
.draggable {
cursor: move;
transition: opacity 0.2s;
}
.draggable:hover {
opacity: 0.8;
}
.dragging {
opacity: 0.5;
}
.drop-zone {
border: 2px dashed #cbd5e0;
border-radius: 8px;
padding: 20px;
text-align: center;
transition: all 0.2s;
}
.drop-zone.drag-over {
border-color: #3b82f6;
background-color: rgba(59, 130, 246, 0.05);
}
/* Inline Edit */
.inline-edit {
position: relative;
}
.inline-edit-input {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: white;
border: 2px solid #3b82f6;
border-radius: 4px;
padding: 4px 8px;
font-family: inherit;
font-size: inherit;
}
.dark .inline-edit-input {
background: #1a202c;
}
/* Toast Notifications */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 12px;
max-width: 400px;
}
.toast {
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 16px;
display: flex;
align-items: start;
gap: 12px;
animation: slideInRight 0.3s ease-out;
}
.dark .toast {
background: #2d3748;
color: #E2E8F0; /* ensure readable text on dark background */
}
.toast.removing {
animation: slideOutRight 0.3s ease-in;
}
.toast-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.toast-success .toast-icon {
background: #10b981;
color: white;
}
.toast-error .toast-icon {
background: #ef4444;
color: white;
}
.toast-warning .toast-icon {
background: #f59e0b;
color: white;
}
.toast-info .toast-icon {
background: #3b82f6;
color: white;
}
/* Undo Bar */
.undo-bar {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: #1f2937;
color: white;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
display: flex;
align-items: center;
gap: 12px;
z-index: 9998;
transition: transform 0.3s ease-out;
}
.undo-bar.show {
transform: translateX(-50%) translateY(0);
}
/* Recently Viewed Dropdown */
.recently-viewed-dropdown {
position: absolute;
top: 100%;
right: 0;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
margin-top: 8px;
width: 320px;
max-height: 400px;
overflow-y: auto;
z-index: 50;
display: none;
}
.dark .recently-viewed-dropdown {
background: #2d3748;
border-color: #4a5568;
}
.recently-viewed-dropdown.show {
display: block;
}
.recently-viewed-item {
padding: 12px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: background-color 0.15s;
}
.dark .recently-viewed-item {
border-bottom-color: #374151;
}
.recently-viewed-item:hover {
background-color: #f9fafb;
}
.dark .recently-viewed-item:hover {
background-color: #374151;
}
/* Progress Ring for Timer */
.progress-ring {
transform: rotate(-90deg);
}
.progress-ring-circle {
transition: stroke-dashoffset 0.35s;
transform-origin: 50% 50%;
}
/* Favorites Star */
.favorite-star {
cursor: pointer;
transition: all 0.2s;
color: #d1d5db;
}
.favorite-star:hover {
transform: scale(1.2);
color: #fbbf24;
}
.favorite-star.active {
color: #fbbf24;
}
/* Quick Filter Buttons */
.quick-filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.quick-filter-btn {
padding: 6px 12px;
border: 1px solid #e5e7eb;
border-radius: 6px;
background: white;
color: #6b7280;
cursor: pointer;
transition: all 0.2s;
font-size: 14px;
}
.dark .quick-filter-btn {
background: #374151;
border-color: #4b5563;
color: #9ca3af;
}
.quick-filter-btn:hover {
border-color: #3b82f6;
color: #3b82f6;
}
.quick-filter-btn.active {
background: #3b82f6;
border-color: #3b82f6;
color: white;
}
/* Form Auto-save Indicator */
.autosave-indicator {
position: fixed;
bottom: 20px;
right: 20px;
padding: 8px 16px;
background: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: #6b7280;
opacity: 0;
transition: opacity 0.3s;
z-index: 40;
}
.dark .autosave-indicator {
background: #374151;
color: #9ca3af;
}
.autosave-indicator.show {
opacity: 1;
}
.autosave-indicator.saving {
color: #3b82f6;
}
.autosave-indicator.saved {
color: #10b981;
}
/* Column Visibility Toggle */
.column-toggle-dropdown {
position: absolute;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 12px;
z-index: 50;
display: none;
min-width: 200px;
}
.dark .column-toggle-dropdown {
background: #2d3748;
border-color: #4a5568;
}
.column-toggle-dropdown.show {
display: block;
}
.column-toggle-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px;
cursor: pointer;
border-radius: 4px;
}
.column-toggle-item:hover {
background-color: #f3f4f6;
}
.dark .column-toggle-item:hover {
background-color: #374151;
}
/* Responsive Utility Classes */
@media (max-width: 768px) {
.toast-container {
top: 10px;
right: 10px;
left: 10px;
max-width: none;
}
.bulk-actions-bar {
left: 10px;
right: 10px;
transform: translateX(0) translateY(100px);
}
.bulk-actions-bar.show {
transform: translateX(0) translateY(0);
}
.recently-viewed-dropdown {
width: calc(100vw - 20px);
left: 10px;
right: 10px;
}
}
/* Accessibility */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
/* Focus visible for keyboard navigation */
:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}
/* Skip to content link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #3b82f6;
color: white;
padding: 8px 16px;
z-index: 100;
border-radius: 0 0 4px 0;
}
.skip-link:focus {
top: 0;
}
/* ============================================
Markdown content styling (fallback to match app)
Applies when Tailwind Typography is not available
============================================ */
.prose, .prose-sm {
color: #2D3748;
line-height: 1.7;
}
.dark .prose, .dark .prose-sm { color: #E2E8F0; }
.prose h1, .prose h2, .prose h3, .prose-sm h1, .prose-sm h2, .prose-sm h3 {
color: inherit;
font-weight: 700;
margin: 0.75rem 0 0.5rem;
}
.prose h4, .prose h5, .prose h6, .prose-sm h4, .prose-sm h5, .prose-sm h6 {
color: inherit;
font-weight: 600;
margin: 0.75rem 0 0.5rem;
}
.prose p, .prose-sm p { margin: 0.5rem 0; }
.prose a, .prose-sm a { color: #3B82F6; text-decoration: underline; }
.dark .prose a, .dark .prose-sm a { color: #60A5FA; }
.prose ul, .prose ol, .prose-sm ul, .prose-sm ol { padding-left: 1.25rem; margin: 0.5rem 0; }
.prose li, .prose-sm li { margin: 0.25rem 0; }
.prose code, .prose-sm code {
background: #F7F9FB;
color: #1F2937;
padding: 0.1rem 0.3rem;
border-radius: 4px;
}
.dark .prose code, .dark .prose-sm code { background: #1F2937; color: #E5E7EB; }
.prose pre, .prose-sm pre {
background: #0B1220;
color: #E5E7EB;
padding: 0.75rem 1rem;
border-radius: 8px;
overflow-x: auto;
}
.dark .prose pre, .dark .prose-sm pre { background: #0B1220; }
.prose blockquote, .prose-sm blockquote {
border-left: 4px solid #3B82F6;
padding-left: 0.75rem;
margin: 0.75rem 0;
color: #475569;
}
.dark .prose blockquote, .dark .prose-sm blockquote { color: #94A3B8; }
.prose table, .prose-sm table { width: 100%; border-collapse: collapse; }
.prose table th, .prose table td, .prose-sm table th, .prose-sm table td {
border: 1px solid #E2E8F0; padding: 0.5rem 0.75rem;
}
.dark .prose table th, .dark .prose table td, .dark .prose-sm table th, .dark .prose-sm table td { border-color: #4A5568; }
.prose img, .prose-sm img { max-width: 100%; border-radius: 8px; }
/* ============================================
Toast UI Editor theme bridging to match app
============================================ */
.toastui-editor-defaultUI {
background: #FFFFFF;
border: 1px solid #E2E8F0;
border-radius: 8px;
}
.dark .toastui-editor-defaultUI {
background: #2D3748;
border-color: #4A5568;
}
.toastui-editor-defaultUI .toastui-editor-toolbar {
background: transparent;
border-bottom-color: #E2E8F0;
}
.dark .toastui-editor-defaultUI .toastui-editor-toolbar { border-bottom-color: #4A5568; }
.toastui-editor-defaultUI .ProseMirror,
.toastui-editor-contents {
color: #2D3748;
}
.dark .toastui-editor-defaultUI .ProseMirror,
.dark .toastui-editor-contents {
color: #E2E8F0;
}
.toastui-editor-contents a { color: #3B82F6; }
.dark .toastui-editor-contents a { color: #60A5FA; }
.toastui-editor-contents pre { background: #0B1220; color: #E5E7EB; border-radius: 8px; }
.toastui-editor-contents code { background: #F7F9FB; color: #1F2937; padding: 0.1rem 0.3rem; border-radius: 4px; }
.dark .toastui-editor-contents code { background: #1F2937; color: #E5E7EB; }
/* Progress Indicator Animation */
@keyframes progress-indeterminate {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(0%);
}
100% {
transform: translateX(100%);
}
}
.fade-in-up {
animation: fadeInUp 0.5s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}