chore: initial commit of TaskTracker project
Existing ASP.NET API with vanilla JS SPA, WindowWatcher, Chrome extension, and MCP server. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
119
TaskTracker.Api/wwwroot/js/pages/mappings.js
Normal file
119
TaskTracker.Api/wwwroot/js/pages/mappings.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import * as api from '../api.js';
|
||||
import { showModal } from '../components/modal.js';
|
||||
|
||||
const el = () => document.getElementById('page-mappings');
|
||||
|
||||
export async function initMappings() {
|
||||
el().innerHTML = `
|
||||
<h1 class="page-title">App Mappings</h1>
|
||||
<div class="flex-between mb-16">
|
||||
<div class="text-muted text-sm">Map process names, window titles, or URLs to categories</div>
|
||||
<button class="btn btn-primary" id="btn-new-mapping">+ New Mapping</button>
|
||||
</div>
|
||||
<div id="mapping-list" class="card table-wrap"></div>`;
|
||||
|
||||
document.getElementById('btn-new-mapping').addEventListener('click', () => showMappingForm());
|
||||
await loadMappings();
|
||||
}
|
||||
|
||||
async function loadMappings() {
|
||||
try {
|
||||
const mappings = await api.mappings.list();
|
||||
const container = document.getElementById('mapping-list');
|
||||
if (!mappings || !mappings.length) {
|
||||
container.innerHTML = `<div class="empty-state">No mappings configured</div>`;
|
||||
return;
|
||||
}
|
||||
container.innerHTML = `
|
||||
<table>
|
||||
<thead><tr><th>Pattern</th><th>Match Type</th><th>Category</th><th>Friendly Name</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
${mappings.map(m => `
|
||||
<tr>
|
||||
<td><code>${esc(m.pattern)}</code></td>
|
||||
<td><span class="badge badge-pending">${m.matchType}</span></td>
|
||||
<td>${esc(m.category)}</td>
|
||||
<td>${esc(m.friendlyName) || '<span class="text-muted">-</span>'}</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-sm" data-edit="${m.id}">Edit</button>
|
||||
<button class="btn btn-sm btn-danger" data-delete="${m.id}">Delete</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>`).join('')}
|
||||
</tbody>
|
||||
</table>`;
|
||||
container.querySelectorAll('[data-edit]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const m = mappings.find(x => x.id === parseInt(btn.dataset.edit));
|
||||
if (m) showMappingForm(m);
|
||||
});
|
||||
});
|
||||
container.querySelectorAll('[data-delete]').forEach(btn => {
|
||||
btn.addEventListener('click', () => confirmDelete(parseInt(btn.dataset.delete)));
|
||||
});
|
||||
} catch (e) {
|
||||
document.getElementById('mapping-list').innerHTML = `<div class="empty-state">Failed to load mappings</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function showMappingForm(existing = null) {
|
||||
const title = existing ? 'Edit Mapping' : 'New Mapping';
|
||||
showModal(title, `
|
||||
<div class="form-group">
|
||||
<label class="form-label">Pattern *</label>
|
||||
<input type="text" class="form-input" id="map-pattern" value="${esc(existing?.pattern || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Match Type *</label>
|
||||
<select class="form-select" id="map-match-type">
|
||||
<option value="ProcessName" ${existing?.matchType === 'ProcessName' ? 'selected' : ''}>Process Name</option>
|
||||
<option value="TitleContains" ${existing?.matchType === 'TitleContains' ? 'selected' : ''}>Title Contains</option>
|
||||
<option value="UrlContains" ${existing?.matchType === 'UrlContains' ? 'selected' : ''}>URL Contains</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Category *</label>
|
||||
<input type="text" class="form-input" id="map-category" value="${esc(existing?.category || '')}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Friendly Name</label>
|
||||
<input type="text" class="form-input" id="map-friendly" value="${esc(existing?.friendlyName || '')}">
|
||||
</div>`,
|
||||
[
|
||||
{ label: 'Cancel', onClick: () => {} },
|
||||
{
|
||||
label: existing ? 'Save' : 'Create', cls: 'btn-primary', onClick: async (modal) => {
|
||||
const pattern = modal.querySelector('#map-pattern').value.trim();
|
||||
const matchType = modal.querySelector('#map-match-type').value;
|
||||
const category = modal.querySelector('#map-category').value.trim();
|
||||
const friendlyName = modal.querySelector('#map-friendly').value.trim() || null;
|
||||
if (!pattern || !category) { alert('Pattern and Category are required'); throw new Error('cancel'); }
|
||||
const body = { pattern, matchType, category, friendlyName };
|
||||
if (existing) await api.mappings.update(existing.id, body);
|
||||
else await api.mappings.create(body);
|
||||
loadMappings();
|
||||
},
|
||||
},
|
||||
]);
|
||||
setTimeout(() => document.getElementById('map-pattern')?.focus(), 100);
|
||||
}
|
||||
|
||||
function confirmDelete(id) {
|
||||
showModal('Delete Mapping', `<p>Are you sure you want to delete this mapping?</p>`, [
|
||||
{ label: 'Cancel', onClick: () => {} },
|
||||
{
|
||||
label: 'Delete', cls: 'btn-danger', onClick: async () => {
|
||||
await api.mappings.remove(id);
|
||||
loadMappings();
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
function esc(str) {
|
||||
if (!str) return '';
|
||||
const d = document.createElement('div');
|
||||
d.textContent = str;
|
||||
return d.innerHTML;
|
||||
}
|
||||
Reference in New Issue
Block a user