Added more things to dashboard
This commit is contained in:
@@ -384,4 +384,72 @@ async function deleteAssociation(assocId, projectId) {
|
|||||||
await apiRequest(`/api/association?id=${assocId}`, 'DELETE');
|
await apiRequest(`/api/association?id=${assocId}`, 'DELETE');
|
||||||
await reloadAssociationTable(projectId);
|
await reloadAssociationTable(projectId);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDashboardView() {
|
||||||
|
const locTbody = document.getElementById('dash-locations-body');
|
||||||
|
const projTbody = document.getElementById('dash-projects-body');
|
||||||
|
if (!locTbody && !projTbody) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const locData = await apiRequest('/api/location');
|
||||||
|
if (locTbody && locData && locData.locations) {
|
||||||
|
locTbody.innerHTML = '';
|
||||||
|
if (locData.locations.length === 0) {
|
||||||
|
locTbody.innerHTML = '<tr><td style="color: var(--text-muted); padding: 1rem;">No locations found.</td></tr>';
|
||||||
|
} else {
|
||||||
|
locData.locations.forEach(loc => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `<td style="cursor: pointer; color: var(--accent); font-weight: 500; padding: 0.75rem 1rem;">${loc.name}</td>`;
|
||||||
|
tr.onclick = () => openDashboardModal(`/api/location?id=${loc.id}&content=true`, `Items in ${loc.name}`, 'contents');
|
||||||
|
locTbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const projData = await apiRequest('/api/project');
|
||||||
|
if (projTbody && projData && projData.projects) {
|
||||||
|
projTbody.innerHTML = '';
|
||||||
|
if (projData.projects.length === 0) {
|
||||||
|
projTbody.innerHTML = '<tr><td style="color: var(--text-muted); padding: 1rem;">No projects found.</td></tr>';
|
||||||
|
} else {
|
||||||
|
projData.projects.forEach(p => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `<td style="cursor: pointer; color: var(--accent); font-weight: 500; padding: 0.75rem 1rem;">${p.name}</td>`;
|
||||||
|
tr.onclick = () => openDashboardModal(`/api/project?id=${p.id}&details=true`, `Items assigned to ${p.name}`, 'items');
|
||||||
|
projTbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openDashboardModal(url, title, dataKey) {
|
||||||
|
document.getElementById('dash-modal-title').innerText = title;
|
||||||
|
const tbody = document.getElementById('dash-modal-body');
|
||||||
|
if (!tbody) return;
|
||||||
|
tbody.innerHTML = '<tr><td colspan="2" style="text-align:center; padding:1.5rem; color:var(--text-muted);">Loading...</td></tr>';
|
||||||
|
document.getElementById('dash-details-modal').classList.add('show');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await apiRequest(url);
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
const list = data[dataKey] || [];
|
||||||
|
if (list.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="2" style="color:var(--text-muted); text-align:center; padding:1.5rem;">No active items found.</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.forEach(i => {
|
||||||
|
tbody.innerHTML += `
|
||||||
|
<tr>
|
||||||
|
<td style="color:var(--text); padding:0.75rem 1rem;">${i.item_name}</td>
|
||||||
|
<td style="text-align:right; padding:0.75rem 1rem;"><span class="badge success">${i.quantity}</span></td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="2" style="color:var(--error); text-align:center; padding:1.5rem;">Failed to load data.</td></tr>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,22 @@ func Dashboard(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
err := dashboard.ExecuteTemplate(w, "base.html", struct {
|
err := dashboard.ExecuteTemplate(w, "base.html", struct {
|
||||||
Title string
|
Title string
|
||||||
|
Stats struct {
|
||||||
|
Items int
|
||||||
|
Projects int
|
||||||
|
Locations int
|
||||||
|
}
|
||||||
}{
|
}{
|
||||||
Title: "Dashboard",
|
Title: "Dashboard",
|
||||||
|
Stats: struct {
|
||||||
|
Items int
|
||||||
|
Projects int
|
||||||
|
Locations int
|
||||||
|
}{
|
||||||
|
Items: 1,
|
||||||
|
Projects: 1,
|
||||||
|
Locations: 3,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ .Title }} | MiauInv</title>
|
<title>{{ .Title }} | MiauInv</title>
|
||||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||||
|
<script src="/assets/js/api.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/css/theme.css">
|
<link rel="stylesheet" href="/assets/css/theme.css">
|
||||||
<link rel="stylesheet" href="/assets/css/dashboard.css">
|
<link rel="stylesheet" href="/assets/css/dashboard.css">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
@@ -3,35 +3,108 @@
|
|||||||
<h1>Dashboard Overview</h1>
|
<h1>Dashboard Overview</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stats-grid">
|
<div class="stats-grid" style="margin-bottom: 2rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem;">
|
||||||
<div class="stat-card">
|
<div class="stat-card" style="background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; display: flex; align-items: center; gap: 1rem;">
|
||||||
<div class="stat-icon" style="background: rgba(59, 130, 246, 0.1); color: var(--accent);">
|
<div class="stat-icon" style="background: rgba(59, 130, 246, 0.1); color: var(--accent); padding: 0.75rem; border-radius: 10px; display: flex; align-items: center; justify-content: center;">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<p>Total Items</p>
|
<p style="color: var(--text-muted); font-size: 0.9rem; margin: 0;">Total Items</p>
|
||||||
<h2>{{ .Stats.Items }}</h2>
|
<h2 style="font-size: 1.5rem; font-weight: 700; margin: 0;">
|
||||||
|
{{ if .Stats }}{{ .Stats.Items }}{{ else }}0{{ end }}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card" style="background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; display: flex; align-items: center; gap: 1rem;">
|
||||||
<div class="stat-icon" style="background: rgba(16, 185, 129, 0.1); color: var(--success);">
|
<div class="stat-icon" style="background: rgba(16, 185, 129, 0.1); color: var(--success); padding: 0.75rem; border-radius: 10px; display: flex; align-items: center; justify-content: center;">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<p>Active Projects</p>
|
<p style="color: var(--text-muted); font-size: 0.9rem; margin: 0;">Active Projects</p>
|
||||||
<h2>{{ .Stats.Projects }}</h2>
|
<h2 style="font-size: 1.5rem; font-weight: 700; margin: 0;">
|
||||||
|
{{ if .Stats }}{{ .Stats.Projects }}{{ else }}0{{ end }}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat-card">
|
<div class="stat-card" style="background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; display: flex; align-items: center; gap: 1rem;">
|
||||||
<div class="stat-icon" style="background: rgba(245, 158, 11, 0.1); color: #f59e0b;">
|
<div class="stat-icon" style="background: rgba(245, 158, 11, 0.1); color: #f59e0b; padding: 0.75rem; border-radius: 10px; display: flex; align-items: center; justify-content: center;">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"></path><circle cx="12" cy="10" r="3"></circle></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-info">
|
<div class="stat-info">
|
||||||
<p>Locations</p>
|
<p style="color: var(--text-muted); font-size: 0.9rem; margin: 0;">Locations</p>
|
||||||
<h2>{{ .Stats.Locations }}</h2>
|
<h2 style="font-size: 1.5rem; font-weight: 700; margin: 0;">
|
||||||
|
{{ if .Stats }}{{ .Stats.Locations }}{{ else }}0{{ end }}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-split">
|
||||||
|
<div class="card" style="max-width: 100%; text-align: left; padding: 1.5rem;">
|
||||||
|
<h2 style="font-size: 1.25rem; margin-bottom: 1rem; color: var(--text);">Locations</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="inner-table" style="margin-top: 0; border-radius: 8px; width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align: left; padding: 0.75rem 1rem; background: #111827;">Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dash-locations-body">
|
||||||
|
<tr><td class="table-loader" style="padding: 1.5rem; text-align: center; color: var(--text-muted);">Loading locations...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="max-width: 100%; text-align: left; padding: 1.5rem;">
|
||||||
|
<h2 style="font-size: 1.25rem; margin-bottom: 1rem; color: var(--text);">Active Projects</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="inner-table" style="margin-top: 0; border-radius: 8px; width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align: left; padding: 0.75rem 1rem; background: #111827;">Project Name</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dash-projects-body">
|
||||||
|
<tr><td class="table-loader" style="padding: 1.5rem; text-align: center; color: var(--text-muted);">Loading projects...</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="dash-details-modal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h2 id="dash-modal-title">Details</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="inner-table" style="width: 100%;">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="text-align: left; padding: 0.75rem 1rem;">Item</th>
|
||||||
|
<th style="text-align: right; width: 100px; padding: 0.75rem 1rem;">Quantity</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="dash-modal-body"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="button-group">
|
||||||
|
<button type="button" class="btn btn-secondary" onclick="document.getElementById('dash-details-modal').classList.remove('show')">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
if (typeof handleDashboardView === "function") {
|
||||||
|
handleDashboardView();
|
||||||
|
}
|
||||||
|
if (window.htmx) {
|
||||||
|
htmx.on("htmx:afterOnLoad", function() {
|
||||||
|
if (typeof handleDashboardView === "function") {
|
||||||
|
handleDashboardView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
Reference in New Issue
Block a user