botui/ui/suite/project/project.html
Rodrigo Rodriguez (Pragmatismo) 80c91f6304 Redesign home page with beautiful layout, add People/Contacts, rename Tools to Compliance
- Complete home page redesign with large icons, full descriptions, recent documents
- Add People (Contacts) menu item and page with contacts management
- Move Paper right after Chat in menu order
- Rename Tools to Compliance with shield icon
- Settings moved to end of menu
- Logo click now shows home page
- Add Project, Canvas, Goals, Player, Workspace, Video, Learn to menu
- New CSS for home page with modern card layout
2026-01-09 20:56:59 -03:00

758 lines
20 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

<!-- =============================================================================
PROJECT APP - Project Management with Gantt Chart
Respects Theme Manager - No hardcoded theme
============================================================================= -->
<div class="project-app">
<!-- Sidebar -->
<aside class="project-sidebar">
<div class="sidebar-header">
<h2 data-i18n="project-title">Projects</h2>
<button
class="btn-icon"
id="new-project-btn"
title="New Project"
hx-get="/api/ui/project/new"
hx-target="#project-modal"
hx-swap="innerHTML"
>
<span>+</span>
</button>
</div>
<div class="sidebar-search">
<input
type="search"
placeholder="Search projects..."
hx-get="/projects"
hx-trigger="keyup changed delay:300ms"
hx-target="#project-list"
hx-swap="innerHTML"
name="q"
/>
</div>
<nav class="sidebar-nav">
<div
id="project-list"
hx-get="/projects"
hx-trigger="load, projectCreated from:body, projectDeleted from:body"
hx-swap="innerHTML"
>
<div class="loading-placeholder">Loading projects...</div>
</div>
</nav>
</aside>
<!-- Main Content -->
<main class="project-main">
<!-- Project Header -->
<header class="project-header">
<div class="project-info">
<h1 id="project-name">Select a Project</h1>
<div class="project-meta">
<span class="meta-item" id="project-status">
<span class="status-dot"></span>
<span>No project selected</span>
</span>
<span class="meta-item" id="project-progress">
<span class="progress-label">Progress:</span>
<span class="progress-value">--</span>
</span>
</div>
</div>
<div class="project-actions">
<div class="view-toggle">
<button class="view-btn active" data-view="gantt" onclick="switchView('gantt')">
<span>📊</span> Gantt
</button>
<button class="view-btn" data-view="timeline" onclick="switchView('timeline')">
<span>📅</span> Timeline
</button>
<button class="view-btn" data-view="list" onclick="switchView('list')">
<span>📋</span> List
</button>
<button class="view-btn" data-view="board" onclick="switchView('board')">
<span>📌</span> Board
</button>
</div>
<button
class="btn-primary"
id="add-task-btn"
hx-get="/api/ui/project/task/new"
hx-target="#project-modal"
hx-swap="innerHTML"
disabled
>
<span>+</span> Add Task
</button>
</div>
</header>
<!-- View Containers -->
<div class="project-views">
<!-- Gantt Chart View -->
<div id="gantt-view" class="view-container active">
<div class="gantt-toolbar">
<div class="gantt-zoom">
<button class="zoom-btn" onclick="zoomGantt('day')">Day</button>
<button class="zoom-btn active" onclick="zoomGantt('week')">Week</button>
<button class="zoom-btn" onclick="zoomGantt('month')">Month</button>
<button class="zoom-btn" onclick="zoomGantt('quarter')">Quarter</button>
</div>
<div class="gantt-filters">
<label>
<input type="checkbox" id="show-critical" checked onchange="toggleCriticalPath()">
<span data-i18n="project-critical-path">Show Critical Path</span>
</label>
<label>
<input type="checkbox" id="show-milestones" checked onchange="toggleMilestones()">
<span data-i18n="project-milestones">Show Milestones</span>
</label>
</div>
<button class="btn-secondary" onclick="fitGanttToScreen()">
Fit to Screen
</button>
</div>
<div class="gantt-container">
<div class="gantt-table">
<div class="gantt-table-header">
<div class="col-name">Task Name</div>
<div class="col-start">Start</div>
<div class="col-end">End</div>
<div class="col-duration">Duration</div>
<div class="col-progress">Progress</div>
<div class="col-assignee">Assignee</div>
</div>
<div
id="gantt-table-body"
class="gantt-table-body"
hx-get="/api/ui/project/tasks"
hx-trigger="projectSelected from:body"
hx-swap="innerHTML"
>
<div class="empty-state-inline">
Select a project to view tasks
</div>
</div>
</div>
<div class="gantt-chart">
<div class="gantt-timeline-header" id="gantt-timeline-header">
<!-- Timeline headers generated by JS -->
</div>
<div
id="gantt-chart-body"
class="gantt-chart-body"
hx-get="/api/ui/project/gantt"
hx-trigger="projectSelected from:body"
hx-swap="innerHTML"
>
<div class="empty-state-inline">
<p>No tasks to display</p>
</div>
</div>
</div>
</div>
</div>
<!-- Timeline View -->
<div id="timeline-view" class="view-container">
<div
class="timeline-container"
hx-get="/api/ui/project/timeline"
hx-trigger="projectSelected from:body"
hx-swap="innerHTML"
>
<div class="empty-state-inline">Select a project to view timeline</div>
</div>
</div>
<!-- List View -->
<div id="list-view" class="view-container">
<div
class="list-container"
hx-get="/api/ui/project/tasks/list"
hx-trigger="projectSelected from:body"
hx-swap="innerHTML"
>
<div class="empty-state-inline">Select a project to view tasks</div>
</div>
</div>
<!-- Board View -->
<div id="board-view" class="view-container">
<div class="board-columns">
<div class="board-column" data-status="not-started">
<h3>Not Started</h3>
<div class="column-tasks" hx-get="/api/ui/project/tasks?status=not_started" hx-trigger="projectSelected from:body" hx-swap="innerHTML"></div>
</div>
<div class="board-column" data-status="in-progress">
<h3>In Progress</h3>
<div class="column-tasks" hx-get="/api/ui/project/tasks?status=in_progress" hx-trigger="projectSelected from:body" hx-swap="innerHTML"></div>
</div>
<div class="board-column" data-status="completed">
<h3>Completed</h3>
<div class="column-tasks" hx-get="/api/ui/project/tasks?status=completed" hx-trigger="projectSelected from:body" hx-swap="innerHTML"></div>
</div>
</div>
</div>
</div>
<!-- Empty State -->
<div id="project-empty" class="empty-state">
<div class="empty-state-icon">📋</div>
<h2>No Project Selected</h2>
<p>Select a project from the sidebar or create a new one</p>
<button
class="btn-primary"
hx-get="/api/ui/project/new"
hx-target="#project-modal"
hx-swap="innerHTML"
>
<span>+</span> Create Project
</button>
</div>
</main>
<!-- Details Panel -->
<aside class="details-panel collapsed" id="details-panel">
<button class="panel-toggle" onclick="toggleDetailsPanel()">
<span></span>
</button>
<div class="panel-content">
<div id="task-details">
<p class="empty-message">Select a task to view details</p>
</div>
</div>
</aside>
<!-- Modal Container -->
<div id="project-modal" class="modal-container"></div>
</div>
<style>
.project-app {
display: flex;
height: 100%;
background: var(--bg-primary);
color: var(--text-primary);
}
.project-sidebar {
width: 280px;
min-width: 280px;
background: var(--bg-secondary);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
overflow: hidden;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
border-bottom: 1px solid var(--border-color);
}
.sidebar-header h2 {
font-size: 1rem;
font-weight: 600;
margin: 0;
}
.sidebar-search {
padding: 0.75rem 1rem;
}
.sidebar-search input {
width: 100%;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--bg-primary);
color: var(--text-primary);
font-size: 0.875rem;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.project-main {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.project-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border-color);
background: var(--bg-secondary);
}
.project-info h1 {
font-size: 1.25rem;
font-weight: 600;
margin: 0 0 0.25rem 0;
}
.project-meta {
display: flex;
gap: 1.5rem;
font-size: 0.875rem;
color: var(--text-muted);
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-muted);
margin-right: 0.375rem;
}
.project-actions {
display: flex;
gap: 1rem;
align-items: center;
}
.view-toggle {
display: flex;
background: var(--bg-primary);
border-radius: 6px;
padding: 2px;
border: 1px solid var(--border-color);
}
.view-btn {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.75rem;
border: none;
background: transparent;
color: var(--text-secondary);
font-size: 0.875rem;
cursor: pointer;
border-radius: 4px;
transition: all 0.15s;
}
.view-btn:hover {
color: var(--text-primary);
}
.view-btn.active {
background: var(--accent-color);
color: white;
}
.project-views {
flex: 1;
overflow: hidden;
position: relative;
}
.view-container {
display: none;
height: 100%;
overflow: auto;
}
.view-container.active {
display: flex;
flex-direction: column;
}
/* Gantt Chart Styles */
.gantt-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
}
.gantt-zoom {
display: flex;
gap: 0.25rem;
}
.zoom-btn {
padding: 0.375rem 0.75rem;
border: 1px solid var(--border-color);
background: var(--bg-primary);
color: var(--text-secondary);
font-size: 0.75rem;
cursor: pointer;
border-radius: 4px;
}
.zoom-btn.active {
background: var(--accent-color);
color: white;
border-color: var(--accent-color);
}
.gantt-filters {
display: flex;
gap: 1rem;
}
.gantt-filters label {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.875rem;
color: var(--text-secondary);
cursor: pointer;
}
.gantt-container {
flex: 1;
display: flex;
overflow: hidden;
}
.gantt-table {
width: 400px;
min-width: 400px;
border-right: 2px solid var(--border-color);
overflow-y: auto;
}
.gantt-table-header {
display: grid;
grid-template-columns: 1fr 80px 80px 70px 70px 90px;
padding: 0.75rem 0.5rem;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
font-size: 0.75rem;
font-weight: 600;
color: var(--text-muted);
text-transform: uppercase;
position: sticky;
top: 0;
z-index: 10;
}
.gantt-table-body {
font-size: 0.875rem;
}
.gantt-chart {
flex: 1;
overflow: auto;
position: relative;
}
.gantt-timeline-header {
display: flex;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 10;
min-width: max-content;
}
.gantt-chart-body {
position: relative;
min-height: 200px;
}
/* Board View */
.board-columns {
display: flex;
gap: 1rem;
padding: 1rem;
height: 100%;
overflow-x: auto;
}
.board-column {
width: 300px;
min-width: 300px;
background: var(--bg-secondary);
border-radius: 8px;
display: flex;
flex-direction: column;
}
.board-column h3 {
padding: 1rem;
margin: 0;
font-size: 0.875rem;
font-weight: 600;
border-bottom: 1px solid var(--border-color);
}
.column-tasks {
flex: 1;
padding: 0.5rem;
overflow-y: auto;
}
/* Details Panel */
.details-panel {
width: 320px;
background: var(--bg-secondary);
border-left: 1px solid var(--border-color);
transition: width 0.2s;
}
.details-panel.collapsed {
width: 48px;
}
.details-panel.collapsed .panel-content {
display: none;
}
.panel-toggle {
width: 100%;
padding: 0.75rem;
border: none;
background: transparent;
cursor: pointer;
font-size: 1.25rem;
}
.panel-content {
padding: 1rem;
overflow-y: auto;
height: calc(100% - 48px);
}
/* Empty State */
.empty-state {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
color: var(--text-muted);
}
.project-app:not(.has-project) .project-views {
display: none;
}
.project-app:not(.has-project) .empty-state {
display: flex;
}
.empty-state-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty-state-inline {
padding: 2rem;
text-align: center;
color: var(--text-muted);
}
/* Buttons */
.btn-icon {
width: 28px;
height: 28px;
border: none;
background: transparent;
color: var(--text-secondary);
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.btn-primary {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--accent-color);
color: white;
border: none;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
}
.btn-primary:hover {
background: var(--accent-hover);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-secondary {
padding: 0.375rem 0.75rem;
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
font-size: 0.875rem;
cursor: pointer;
}
.loading-placeholder {
color: var(--text-muted);
font-size: 0.875rem;
padding: 1rem;
text-align: center;
}
.modal-container:empty {
display: none;
}
@media (max-width: 1024px) {
.gantt-table {
width: 300px;
min-width: 300px;
}
.gantt-table-header {
grid-template-columns: 1fr 70px 70px 60px;
}
.gantt-table-header .col-progress,
.gantt-table-header .col-assignee {
display: none;
}
}
@media (max-width: 768px) {
.project-sidebar {
position: absolute;
left: -280px;
height: 100%;
z-index: 50;
transition: left 0.2s;
}
.project-sidebar.open {
left: 0;
}
.details-panel {
display: none;
}
.view-toggle {
display: none;
}
}
</style>
<script>
let currentView = 'gantt';
let currentZoom = 'week';
function switchView(view) {
currentView = view;
document.querySelectorAll('.view-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.view === view);
});
document.querySelectorAll('.view-container').forEach(container => {
container.classList.toggle('active', container.id === `${view}-view`);
});
}
function zoomGantt(level) {
currentZoom = level;
document.querySelectorAll('.zoom-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent.toLowerCase() === level);
});
htmx.trigger('#gantt-chart-body', 'ganttZoomChanged');
}
function toggleCriticalPath() {
const show = document.getElementById('show-critical').checked;
document.querySelectorAll('.gantt-bar.critical').forEach(bar => {
bar.style.display = show ? '' : 'none';
});
}
function toggleMilestones() {
const show = document.getElementById('show-milestones').checked;
document.querySelectorAll('.gantt-milestone').forEach(ms => {
ms.style.display = show ? '' : 'none';
});
}
function fitGanttToScreen() {
const container = document.querySelector('.gantt-chart');
if (container) {
container.scrollLeft = 0;
}
}
function toggleDetailsPanel() {
const panel = document.getElementById('details-panel');
panel.classList.toggle('collapsed');
}
function selectProject(projectId) {
document.querySelector('.project-app').classList.add('has-project');
document.getElementById('add-task-btn').disabled = false;
htmx.trigger(document.body, 'projectSelected', { projectId: projectId });
}
document.addEventListener('DOMContentLoaded', function() {
generateTimelineHeaders();
});
function generateTimelineHeaders() {
const header = document.getElementById('gantt-timeline-header');
if (!header) return;
const today = new Date();
let html = '';
for (let i = 0; i < 30; i++) {
const date = new Date(today);
date.setDate(date.getDate() + i);
const day = date.getDate();
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
html += `
<div class="timeline-day ${isWeekend ? 'weekend' : ''}" style="width: 40px; text-align: center; padding: 0.5rem 0; border-right: 1px solid var(--border-color);">
<div style="font-size: 0.625rem; color: var(--text-muted);">${dayName}</div>
<div style="font-size: 0.75rem; font-weight: 600;">${day}</div>
</div>
`;
}
header.innerHTML = html;
}
</script>