Add override detection with colored tags, fix tag padding, remove schema drop

This commit is contained in:
Ian Gulliver
2026-02-15 18:59:41 -08:00
parent cf633a5143
commit 0a0d1fc476
4 changed files with 80 additions and 7 deletions

View File

@@ -33,6 +33,7 @@
.trip-name { font-weight: bold; font-size: 1rem; color: var(--wa-color-brand-60); }
.tags { display: flex; flex-wrap: wrap; gap: 0.25rem; }
wa-tag { transition: opacity var(--wa-transition-normal); }
wa-tag::part(base) { padding: 0.15rem 0.4rem; }
wa-input.email { max-width: 20rem; }
.add-form { display: flex; flex-direction: column; gap: 0.3rem; max-width: 20rem; }
.add-form wa-button { align-self: flex-start; }

View File

@@ -32,6 +32,7 @@
.student-name { font-weight: bold; display: block; margin-bottom: 0.3rem; }
.tags { display: flex; flex-wrap: wrap; gap: 0.25rem; margin-bottom: 0.3rem; }
wa-tag { transition: opacity var(--wa-transition-normal); }
wa-tag::part(base) { padding: 0.15rem 0.4rem; }
wa-input.email { max-width: 20rem; }
.add-form { display: flex; flex-direction: column; gap: 0.3rem; max-width: 20rem; }
.add-form wa-button { align-self: flex-start; }
@@ -60,6 +61,9 @@
.constraint-level { font-size: 0.7rem; font-weight: bold; opacity: 0.6; }
.constraint-add { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.3rem; }
.constraint-add select { font-size: 0.75rem; padding: 0.15rem; border: 1px solid var(--wa-color-neutral-300, #ccc); border-radius: 0.25rem; }
#conflicts { margin-bottom: 0.5rem; }
.conflict-row { margin-bottom: 0.2rem; }
.conflict-icon { background: var(--wa-color-danger-50, #dc3545); color: white; border-radius: 0.15rem; padding: 0 0.15rem; font-size: 0.6rem; line-height: 1.2; vertical-align: middle; margin-right: 0.1rem; display: inline-block; }
</style>
</head>
<body>
@@ -76,6 +80,7 @@
<label for="room-size">Students per room:</label>
<input id="room-size" type="number" min="1">
</div>
<div id="conflicts"></div>
<div id="students"></div>
<wa-details summary="Add Student">
<div class="add-form">

View File

@@ -27,6 +27,70 @@ async function loadStudents() {
api('GET', '/api/trips/' + tripID + '/students'),
api('GET', '/api/trips/' + tripID + '/constraints')
]);
const kindLabels = { must: 'Must', prefer: 'Prefer', prefer_not: 'Prefer Not', must_not: 'Must Not' };
const kindVariant = { must: 'success', prefer: 'brand', prefer_not: 'warning', must_not: 'danger' };
const kindColor = { must: 'var(--wa-color-success-50)', prefer: 'var(--wa-color-brand-50)', prefer_not: 'var(--wa-color-warning-50)', must_not: 'var(--wa-color-danger-50)' };
const kindOrder = { must: 0, prefer: 1, prefer_not: 2, must_not: 3 };
const isPositive = kind => kind === 'must' || kind === 'prefer';
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
const pairs = {};
for (const c of constraints) {
const k = c.student_a_id + '-' + c.student_b_id;
if (!pairs[k]) pairs[k] = [];
pairs[k].push(c);
}
const conflictList = [];
const conflictMap = {};
for (const group of Object.values(pairs)) {
const pos = group.filter(c => isPositive(c.kind));
const neg = group.filter(c => !isPositive(c.kind));
if (pos.length === 0 || neg.length === 0) continue;
conflictList.push({
names: group[0].student_a_name + ' \u2192 ' + group[0].student_b_name,
positives: pos.map(c => ({ level: c.level, kind: c.kind })),
negatives: neg.map(c => ({ level: c.level, kind: c.kind }))
});
for (const c of group) {
const opposing = isPositive(c.kind) ? neg : pos;
conflictMap[c.id] = opposing.map(o => capitalize(o.level) + ' says ' + kindLabels[o.kind]).join(', ');
}
}
const conflictsEl = document.getElementById('conflicts');
const conflictsWasOpen = conflictsEl.querySelector('wa-details')?.open;
conflictsEl.innerHTML = '';
if (conflictList.length > 0) {
const det = document.createElement('wa-details');
det.summary = '\u26a0 Overrides (' + conflictList.length + ')';
if (conflictsWasOpen) det.open = true;
const kindSpan = (kind) => {
const span = document.createElement('span');
span.textContent = kindLabels[kind];
span.style.color = kindColor[kind];
span.style.fontWeight = 'bold';
return span;
};
for (const conflict of conflictList) {
const div = document.createElement('div');
div.className = 'conflict-row';
div.appendChild(document.createTextNode(conflict.names + ': '));
conflict.positives.forEach((p, i) => {
if (i > 0) div.appendChild(document.createTextNode(', '));
div.appendChild(document.createTextNode(capitalize(p.level) + ' '));
div.appendChild(kindSpan(p.kind));
});
div.appendChild(document.createTextNode(' vs '));
conflict.negatives.forEach((n, i) => {
if (i > 0) div.appendChild(document.createTextNode(', '));
div.appendChild(document.createTextNode(capitalize(n.level) + ' '));
div.appendChild(kindSpan(n.kind));
});
det.appendChild(div);
}
conflictsEl.appendChild(det);
}
const container = document.getElementById('students');
const openStates = {};
for (const card of container.children) {
@@ -103,9 +167,6 @@ async function loadStudents() {
const cDetails = document.createElement('wa-details');
cDetails.summary = 'Constraints';
const kindVariant = { must: 'success', prefer: 'brand', prefer_not: 'warning', must_not: 'danger' };
const kindLabels = { must: 'Must', prefer: 'Prefer', prefer_not: 'Prefer Not', must_not: 'Must Not' };
const kindOrder = { must: 0, prefer: 1, prefer_not: 2, must_not: 3 };
const myConstraints = constraints.filter(c => c.student_a_id === student.id || c.student_b_id === student.id);
for (const level of ['admin', 'parent', 'student']) {
@@ -130,11 +191,20 @@ async function loadStudents() {
tag.size = 'small';
tag.variant = kindVariant[c.kind];
tag.setAttribute('with-remove', '');
tag.textContent = kindLabels[c.kind] + ': ' + otherName;
tag.addEventListener('wa-remove', async () => {
await api('DELETE', '/api/trips/' + tripID + '/constraints/' + c.id);
loadStudents();
});
if (conflictMap[c.id]) {
const icon = document.createElement('span');
icon.className = 'conflict-icon';
icon.textContent = '\u26a0 ';
tag.appendChild(icon);
tag.appendChild(document.createTextNode(kindLabels[c.kind] + ': ' + otherName));
tag.title = 'Overrides: ' + conflictMap[c.id];
} else {
tag.textContent = kindLabels[c.kind] + ': ' + otherName;
}
group.appendChild(tag);
}
cDetails.appendChild(group);