Дашборд: переводы обоих направлений, переключатель «Сегодня / Всего»
На 9 при одном входящем переводе (Витвицкий) дашборд показывал «Переводов ещё нет»: сводка и «Последние переводы» считали только исходящие, и только последние 200. - Сводка по направлениям: исходящие и входящие — всего, исполнено, в работе, отказ / истёк срок; исход — по состоянию «чего ждёт заявка» (livestatus.go), то же, что в журнале: принятый, но не исполненный перевод — «в работе». - Период «Сегодня / Всего» (?period=, выбор помнится в куке; по умолчанию — сегодня, с 00:00 МСК). Плитка «Переводы» ведёт в журнал, счётчики за всё время. - «Последние переводы» — общая лента журнала (без анкет участника): направление, инвестор, контрагент, статус и строка состояния. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
This commit is contained in:
+70
-27
@@ -503,13 +503,11 @@ var globalRC *RuntimeConfig
|
||||
type homeData struct {
|
||||
page
|
||||
Status SystemStatus
|
||||
Counts struct {
|
||||
Total int
|
||||
Confirmed int
|
||||
InProgress int
|
||||
Failed int
|
||||
}
|
||||
Recent []ClaimView
|
||||
// Сводка по переводам обоих направлений (23.10.2026: считались только исходящие, последние 200).
|
||||
Period string // today | all — выбранный период сводки
|
||||
Sum homePeriod // за выбранный период
|
||||
All homePeriod // за всё время (плитка «Журнал»)
|
||||
Recent []journalRow
|
||||
News []NewsItem // top-3 активных или свежих новостей
|
||||
|
||||
// Сводка готовности системы для hero-блока дашборда.
|
||||
@@ -731,21 +729,80 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// homeDir — сводка дашборда по одному направлению: исход — по состоянию «чего ждёт заявка»
|
||||
// (livestatus.go): в работе, исполнено (в т. ч. частично), отказ / истёк срок.
|
||||
type homeDir struct{ Total, Done, InProgress, Failed int }
|
||||
|
||||
func (d *homeDir) add(st LiveStatus) {
|
||||
d.Total++
|
||||
switch {
|
||||
case st.Active:
|
||||
d.InProgress++
|
||||
case st.Kind == "bad":
|
||||
d.Failed++
|
||||
case st.Kind == "ok" || st.Kind == "warn":
|
||||
d.Done++
|
||||
default:
|
||||
d.InProgress++
|
||||
}
|
||||
}
|
||||
|
||||
type homePeriod struct{ Out, In homeDir }
|
||||
|
||||
// homePeriodOf — период сводки: ?period=today|all, выбор помнится в куке; по умолчанию — сегодня.
|
||||
func homePeriodOf(w http.ResponseWriter, r *http.Request) string {
|
||||
p := r.URL.Query().Get("period")
|
||||
if p == "today" || p == "all" {
|
||||
http.SetCookie(w, &http.Cookie{Name: "bj_home_period", Value: p, Path: "/admin", MaxAge: 365 * 24 * 3600, HttpOnly: true, SameSite: http.SameSiteLaxMode})
|
||||
return p
|
||||
}
|
||||
if c, err := r.Cookie("bj_home_period"); err == nil && c.Value == "all" {
|
||||
return "all"
|
||||
}
|
||||
return "today"
|
||||
}
|
||||
|
||||
// homeSummary — сводка и последние переводы (исходящие + входящие, без анкет участника) за период.
|
||||
func homeSummary(ctx context.Context, svc *Service, period string) (sum, all homePeriod, recent []journalRow) {
|
||||
store := globalIncomingStore
|
||||
if store == nil {
|
||||
store = &IncomingStore{}
|
||||
}
|
||||
n := time.Now().In(mskZone)
|
||||
today := time.Date(n.Year(), n.Month(), n.Day(), 0, 0, 0, 0, mskZone)
|
||||
for _, row := range buildJournalRows(ctx, svc, store, time.Time{}) {
|
||||
if row.DirKind != "out" && row.DirKind != "in" || strings.HasPrefix(row.LiveKey, "pf:") {
|
||||
continue
|
||||
}
|
||||
isToday := !row.Time.Before(today)
|
||||
for _, p := range []*homePeriod{&all, &sum} {
|
||||
if p == &sum && period == "today" && !isToday {
|
||||
continue
|
||||
}
|
||||
if row.DirKind == "out" {
|
||||
p.Out.add(row.Live)
|
||||
} else {
|
||||
p.In.add(row.Live)
|
||||
}
|
||||
}
|
||||
if len(recent) < 10 && (period == "all" || isToday) {
|
||||
recent = append(recent, row)
|
||||
}
|
||||
}
|
||||
return sum, all, recent
|
||||
}
|
||||
|
||||
func (a *admin) renderHome(w http.ResponseWriter, r *http.Request, svc *Service, rc *RuntimeConfig, opts CheckOptions) {
|
||||
ctx := r.Context()
|
||||
status := CheckAll(ctx, opts)
|
||||
recent, err := svc.ListClaims(ctx, m2mcore.Filter{Limit: 10})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
data := homeData{
|
||||
page: nowPage("Дашборд", "home"),
|
||||
Status: status,
|
||||
Recent: recent.Items,
|
||||
Period: homePeriodOf(w, r),
|
||||
News: topNews(visibleNews(rc, rc.Snapshot().News.Items), 3),
|
||||
LKUrl: rc.Snapshot().LK.PublicURL,
|
||||
}
|
||||
data.Sum, data.All, data.Recent = homeSummary(ctx, svc, data.Period)
|
||||
data.NSDEmuOn, _ = lkEmulateNSD(rc)
|
||||
data.QuikEmuOn = rc.Snapshot().Quik.Emulate && devMode(rc)
|
||||
if q := rc.Snapshot().Quik; q.RealWrite && devMode(rc) {
|
||||
@@ -760,20 +817,6 @@ func (a *admin) renderHome(w http.ResponseWriter, r *http.Request, svc *Service,
|
||||
}
|
||||
}
|
||||
data.AllReady = data.TotalCount > 0 && data.NotReadyCount == 0
|
||||
full, err := svc.ListClaims(ctx, m2mcore.Filter{Limit: 200})
|
||||
if err == nil {
|
||||
for _, c := range full.Items {
|
||||
data.Counts.Total++
|
||||
switch c.Status {
|
||||
case string(m2mcore.StateConfirmed), string(m2mcore.StateAwaitingSUB16), string(m2mcore.StateDone):
|
||||
data.Counts.Confirmed++
|
||||
case string(m2mcore.StateRejected), string(m2mcore.StateTimedOut):
|
||||
data.Counts.Failed++
|
||||
default:
|
||||
data.Counts.InProgress++
|
||||
}
|
||||
}
|
||||
}
|
||||
render(w, a.home, data)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user