diff --git a/main.go b/main.go index 116be49..cc7b64f 100644 --- a/main.go +++ b/main.go @@ -755,13 +755,14 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc { defer rows.Close() type constraint struct { - ID int64 `json:"id"` - StudentAID int64 `json:"student_a_id"` - StudentAName string `json:"student_a_name"` - StudentBID int64 `json:"student_b_id"` - StudentBName string `json:"student_b_name"` - Kind string `json:"kind"` - Level string `json:"level"` + ID int64 `json:"id"` + StudentAID int64 `json:"student_a_id"` + StudentAName string `json:"student_a_name"` + StudentBID int64 `json:"student_b_id"` + StudentBName string `json:"student_b_name"` + Kind string `json:"kind"` + Level string `json:"level"` + Override *string `json:"override"` } var constraints []constraint @@ -776,8 +777,73 @@ func handleListConstraints(db *sql.DB) http.HandlerFunc { if constraints == nil { 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") - json.NewEncoder(w).Encode(constraints) + json.NewEncoder(w).Encode(map[string]any{"constraints": constraints, "overrides": overrides}) } } diff --git a/static/trip.js b/static/trip.js index 7b292c9..c9efa7e 100644 --- a/static/trip.js +++ b/static/trip.js @@ -49,10 +49,12 @@ document.getElementById('np-cost').addEventListener('change', async () => { let lastOveralls = {}; 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 + '/constraints') ]); + const constraints = constraintData.constraints; + const conflictList = constraintData.overrides; 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)' }; @@ -60,27 +62,9 @@ async function loadStudents() { 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(', '); - } + for (const c of constraints) { + if (c.override) conflictMap[c.id] = c.override; } const kindSpan = (kind) => { @@ -630,10 +614,11 @@ if (DOMAIN) { } 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 + '/constraints') ]); + const constraints = constraintData.constraints; const myStudentIDs = new Set(me.students.map(s => s.id)); const container = document.getElementById('member-students');