Move constraint override detection from JS to server
This commit is contained in:
82
main.go
82
main.go
@@ -755,13 +755,14 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc {
|
|||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
type constraint struct {
|
type constraint struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
StudentAID int64 `json:"student_a_id"`
|
StudentAID int64 `json:"student_a_id"`
|
||||||
StudentAName string `json:"student_a_name"`
|
StudentAName string `json:"student_a_name"`
|
||||||
StudentBID int64 `json:"student_b_id"`
|
StudentBID int64 `json:"student_b_id"`
|
||||||
StudentBName string `json:"student_b_name"`
|
StudentBName string `json:"student_b_name"`
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
|
Override *string `json:"override"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var constraints []constraint
|
var constraints []constraint
|
||||||
@@ -776,8 +777,73 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc {
|
|||||||
if constraints == nil {
|
if constraints == nil {
|
||||||
constraints = []constraint{}
|
constraints = []constraint{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type levelKind struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
}
|
||||||
|
type overrideEntry struct {
|
||||||
|
Names string `json:"names"`
|
||||||
|
Positives []levelKind `json:"positives"`
|
||||||
|
Negatives []levelKind `json:"negatives"`
|
||||||
|
}
|
||||||
|
var overrides []overrideEntry
|
||||||
|
|
||||||
|
if role == "admin" {
|
||||||
|
type pairKey struct{ a, b int64 }
|
||||||
|
pairGroups := map[pairKey][]int{}
|
||||||
|
for i := range constraints {
|
||||||
|
pk := pairKey{constraints[i].StudentAID, constraints[i].StudentBID}
|
||||||
|
pairGroups[pk] = append(pairGroups[pk], i)
|
||||||
|
}
|
||||||
|
isPositive := func(kind string) bool { return kind == "must" || kind == "prefer" }
|
||||||
|
kindLabel := map[string]string{"must": "Must", "prefer": "Prefer", "prefer_not": "Prefer Not", "must_not": "Must Not"}
|
||||||
|
for _, idxs := range pairGroups {
|
||||||
|
var posIdx, negIdx []int
|
||||||
|
for _, i := range idxs {
|
||||||
|
if isPositive(constraints[i].Kind) {
|
||||||
|
posIdx = append(posIdx, i)
|
||||||
|
} else {
|
||||||
|
negIdx = append(negIdx, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(posIdx) == 0 || len(negIdx) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var positives, negatives []levelKind
|
||||||
|
for _, i := range posIdx {
|
||||||
|
positives = append(positives, levelKind{constraints[i].Level, constraints[i].Kind})
|
||||||
|
}
|
||||||
|
for _, i := range negIdx {
|
||||||
|
negatives = append(negatives, levelKind{constraints[i].Level, constraints[i].Kind})
|
||||||
|
}
|
||||||
|
overrides = append(overrides, overrideEntry{
|
||||||
|
Names: constraints[idxs[0]].StudentAName + " \u2192 " + constraints[idxs[0]].StudentBName,
|
||||||
|
Positives: positives,
|
||||||
|
Negatives: negatives,
|
||||||
|
})
|
||||||
|
for _, i := range idxs {
|
||||||
|
var opposing []int
|
||||||
|
if isPositive(constraints[i].Kind) {
|
||||||
|
opposing = negIdx
|
||||||
|
} else {
|
||||||
|
opposing = posIdx
|
||||||
|
}
|
||||||
|
parts := make([]string, len(opposing))
|
||||||
|
for j, o := range opposing {
|
||||||
|
parts[j] = strings.ToUpper(constraints[o].Level[:1]) + constraints[o].Level[1:] + " says " + kindLabel[constraints[o].Kind]
|
||||||
|
}
|
||||||
|
desc := strings.Join(parts, ", ")
|
||||||
|
constraints[i].Override = &desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if overrides == nil {
|
||||||
|
overrides = []overrideEntry{}
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(constraints)
|
json.NewEncoder(w).Encode(map[string]any{"constraints": constraints, "overrides": overrides})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,10 +49,12 @@ document.getElementById('np-cost').addEventListener('change', async () => {
|
|||||||
let lastOveralls = {};
|
let lastOveralls = {};
|
||||||
|
|
||||||
async function loadStudents() {
|
async function loadStudents() {
|
||||||
const [students, constraints] = await Promise.all([
|
const [students, constraintData] = await Promise.all([
|
||||||
api('GET', '/api/trips/' + tripID + '/students'),
|
api('GET', '/api/trips/' + tripID + '/students'),
|
||||||
api('GET', '/api/trips/' + tripID + '/constraints')
|
api('GET', '/api/trips/' + tripID + '/constraints')
|
||||||
]);
|
]);
|
||||||
|
const constraints = constraintData.constraints;
|
||||||
|
const conflictList = constraintData.overrides;
|
||||||
const kindLabels = { must: 'Must', prefer: 'Prefer', prefer_not: 'Prefer Not', must_not: 'Must Not' };
|
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 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 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)' };
|
||||||
@@ -60,27 +62,9 @@ async function loadStudents() {
|
|||||||
const isPositive = kind => kind === 'must' || kind === 'prefer';
|
const isPositive = kind => kind === 'must' || kind === 'prefer';
|
||||||
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
|
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 = {};
|
const conflictMap = {};
|
||||||
for (const group of Object.values(pairs)) {
|
for (const c of constraints) {
|
||||||
const pos = group.filter(c => isPositive(c.kind));
|
if (c.override) conflictMap[c.id] = c.override;
|
||||||
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 kindSpan = (kind) => {
|
const kindSpan = (kind) => {
|
||||||
@@ -630,10 +614,11 @@ if (DOMAIN) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function renderMemberView(me) {
|
async function renderMemberView(me) {
|
||||||
const [students, constraints] = await Promise.all([
|
const [students, constraintData] = await Promise.all([
|
||||||
api('GET', '/api/trips/' + tripID + '/students'),
|
api('GET', '/api/trips/' + tripID + '/students'),
|
||||||
api('GET', '/api/trips/' + tripID + '/constraints')
|
api('GET', '/api/trips/' + tripID + '/constraints')
|
||||||
]);
|
]);
|
||||||
|
const constraints = constraintData.constraints;
|
||||||
|
|
||||||
const myStudentIDs = new Set(me.students.map(s => s.id));
|
const myStudentIDs = new Set(me.students.map(s => s.id));
|
||||||
const container = document.getElementById('member-students');
|
const container = document.getElementById('member-students');
|
||||||
|
|||||||
Reference in New Issue
Block a user