Files
Bridge-and-Join-s/internal/lkgateway/listquery.go
T
fontvielle 60b4d46277 Списки: период, сортировка, подгрузка; анкета участника — строкой исходящих
«Исходящие», «Входящие», «Журнал обмена»: быстрые периоды (сегодня, 7, 30
дней, всё время), даты с–по, сортировка (время, инвестор/контрагент,
направление, статус), первые 50 строк и подгрузка при прокрутке
(part=rows&offset=N). Раньше показывались только последние 200/100.

Анкета участника — исходящий пакет в НРД: строка в «Исходящих» и
«Журнале» с ответом НРД (принята / отклонена с кодом / ждёт), вместо
плашки над списком. Отправки копятся в participant-regs.json (прежний
participant-reg.json подхватывается). userTemplate — функции текущего
пользователя и для порций строк.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-22 10:50:43 +03:00

186 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
// listquery.go — списки «Исходящие», «Входящие», «Журнал обмена»: период (с–по, быстрые «сегодня /
// 7 / 30 дней»), сортировка и подгрузка частями при прокрутке. Страница отдаёт первые listPageSize
// строк; следующие браузер берёт тем же адресом с part=rows&offset=N (ответ — JSON с готовыми строками).
import (
"bytes"
"encoding/json"
"html/template"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
const listPageSize = 50
var listMSK = time.FixedZone("МСК", 3*3600)
// listQ — что выбрал пользователь: период, сортировка, с какой строки.
type listQ struct {
From, To time.Time // To — начало дня после «по» (исключительно); нулевые — без границы
FromS, ToS string // как в полях (ГГГГ-ММ-ДД)
Sort string
Asc bool
Offset int
Query string // параметры без offset/part — для подгрузки и ссылок
}
// parseListQ — параметры списка из адреса. sorts — допустимые ключи сортировки, первый — по умолчанию.
func parseListQ(r *http.Request, sorts []string) listQ {
v := r.URL.Query()
q := listQ{Sort: sorts[0]}
if t, err := time.ParseInLocation("2006-01-02", v.Get("from"), listMSK); err == nil {
q.From, q.FromS = t, v.Get("from")
}
if t, err := time.ParseInLocation("2006-01-02", v.Get("to"), listMSK); err == nil {
q.To, q.ToS = t.AddDate(0, 0, 1), v.Get("to")
}
for _, s := range sorts {
if v.Get("sort") == s {
q.Sort = s
}
}
q.Asc = v.Get("dir") == "asc"
q.Offset, _ = strconv.Atoi(v.Get("offset"))
if q.Offset < 0 {
q.Offset = 0
}
keep := url.Values{}
for _, k := range []string{"from", "to", "sort", "dir"} {
if s := v.Get(k); s != "" {
keep.Set(k, s)
}
}
q.Query = keep.Encode()
return q
}
// in — время попадает в выбранный период.
func (q listQ) in(t time.Time) bool {
return (q.From.IsZero() || !t.Before(q.From)) && (q.To.IsZero() || t.Before(q.To))
}
// listItem — строка списка: время, ключи сортировки и данные для шаблона строки.
type listItem struct {
T time.Time
Keys map[string]string
V any
}
// listApply — отбор по периоду, сортировка и одна порция строк.
func listApply(items []listItem, q listQ) (page []listItem, total int, more bool) {
out := items[:0:0]
for _, it := range items {
if q.in(it.T) {
out = append(out, it)
}
}
sort.SliceStable(out, func(i, j int) bool {
a, b := out[i], out[j]
if q.Sort != "time" {
ka, kb := strings.ToLower(a.Keys[q.Sort]), strings.ToLower(b.Keys[q.Sort])
if ka != kb {
if q.Asc {
return ka < kb
}
return ka > kb
}
return a.T.After(b.T) // при равенстве — новые выше
}
if q.Asc {
return a.T.Before(b.T)
}
return a.T.After(b.T)
})
total = len(out)
if q.Offset >= total {
return nil, total, false
}
end := q.Offset + listPageSize
if end > total {
end = total
}
return out[q.Offset:end], total, end < total
}
// listView — панель списка (период, сортировка) и состояние подгрузки — для шаблона "listTools".
type listView struct {
Q listQ
Path string
Body string // id <tbody>, куда дописываются строки
Total int
More bool
Next int
Sorts []listOpt
Presets []listOpt
}
type listOpt struct {
Key, Label, Href string
On bool
}
// newListView — панель: быстрые периоды и варианты сортировки (sorts — пары ключ, подпись).
func newListView(path, body string, q listQ, total int, more bool, sorts ...string) listView {
lv := listView{Q: q, Path: path, Body: body, Total: total, More: more, Next: q.Offset + listPageSize}
for i := 0; i+1 < len(sorts); i += 2 {
lv.Sorts = append(lv.Sorts, listOpt{Key: sorts[i], Label: sorts[i+1], On: q.Sort == sorts[i]})
}
today := time.Now().In(listMSK).Format("2006-01-02")
day := func(n int) string { return time.Now().In(listMSK).AddDate(0, 0, -n).Format("2006-01-02") }
href := func(from, to string) string {
v := url.Values{}
if from != "" {
v.Set("from", from)
v.Set("to", to)
}
if q.Sort != lv.Sorts[0].Key {
v.Set("sort", q.Sort)
}
if q.Asc {
v.Set("dir", "asc")
}
if s := v.Encode(); s != "" {
return path + "?" + s
}
return path
}
for _, p := range []struct{ label, from string }{{"Сегодня", today}, {"7 дней", day(6)}, {"30 дней", day(29)}, {"Всё время", ""}} {
to := today
if p.from == "" {
to = ""
}
lv.Presets = append(lv.Presets, listOpt{Label: p.label, Href: href(p.from, to), On: q.FromS == p.from && q.ToS == to})
}
return lv
}
// listRowsPart — ответ на part=rows: следующая порция строк готовым HTML (шаблон rowTpl на каждую строку).
func listRowsPart(w http.ResponseWriter, t *template.Template, rowTpl string, page []listItem, q listQ, more bool) {
tu := userTemplate(w, t)
var buf bytes.Buffer
for _, it := range page {
if err := tu.ExecuteTemplate(&buf, rowTpl, it.V); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Cache-Control", "no-store")
_ = json.NewEncoder(w).Encode(map[string]any{"html": buf.String(), "more": more, "next": q.Offset + len(page)})
}
// listVals — данные строк порции для шаблона страницы.
func listVals(page []listItem) []any {
out := make([]any, 0, len(page))
for _, it := range page {
out = append(out, it.V)
}
return out
}