Кабинет: поиск клиентов с подсказками, чистая страница документов, ширина настроек

Поиск клиентов (режим сотрудника):
- поле поиска вынесено наверх страницы, рядом «Найти» и «Сбросить»;
- браузер сам подсказывает выпадающим списком по мере ввода — договоры
  депозитария отдаются страницей в <datalist> (скриптов в кабинете нет,
  CSP остаётся строгой), список кэшируется в ядре на 10 минут;
- по запросу фильтруются и учётки кабинета, и договоры депозитария,
  под формой показывается, сколько найдено;
- шаблон LIKE экранируется, чтобы % и _ в номере договора означали себя.

Документы: убраны пустое табло, «Мои заявки» и форма заказа документа —
остался список готовых документов депозитария и пояснение, куда обращаться.

Настройка: колонка сетки больше не растягивается широкой таблицей
(min-width:0), у страницы пропал горизонтальный ползунок.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
zuevav
2026-09-18 18:45:54 +03:00
parent 4b80d030ab
commit 683f516d07
7 changed files with 184 additions and 50 deletions
+51
View File
@@ -47,6 +47,19 @@ func fakeBJ(t *testing.T) *httptest.Server {
}})
}
})
mux.HandleFunc("/api/lk/admin/clients", func(w http.ResponseWriter, r *http.Request) {
if !keyOK(w, r) || r.Header.Get("X-BJ-Session") != "sess-1" {
return
}
search := r.URL.Query().Get("search")
clients := []any{map[string]any{"login": "bublikov", "contract_number": "БР-719", "full_name": "Аминов Вячеслав Маркович", "active": true}}
depo := []any{map[string]any{"contract": "БР-719 Аминов Вячеслав Маркович", "name": "Аминов Вячеслав Маркович", "securities": 14}}
if search != "" && !strings.Contains(strings.ToLower("БР-719 Аминов Вячеслав Маркович"), strings.ToLower(search)) {
clients, depo = nil, nil
}
_ = json.NewEncoder(w).Encode(map[string]any{"clients": clients, "depo": depo, "search": search,
"suggest": []string{"БР-719 Аминов Вячеслав Маркович", "БР-746 Бубликов Иван Петрович"}})
})
mux.HandleFunc("/api/lk/security", func(w http.ResponseWriter, r *http.Request) {
if keyOK(w, r) && r.Header.Get("X-BJ-Session") == "sess-1" {
_ = json.NewEncoder(w).Encode(map[string]any{"security": map[string]any{
@@ -510,3 +523,41 @@ func TestAgentMode(t *testing.T) {
t.Fatalf("чужой ключ принят: код %d", rec.Code)
}
}
// Поиск клиентов в режиме сотрудника: подсказки списком и результат под формой.
func TestClientsSearch(t *testing.T) {
bj := fakeBJ(t)
s, err := New(Config{BJURL: bj.URL, BJKey: "lk-key", Title: "Кабинет"})
if err != nil {
t.Fatal(err)
}
h := s.Handler()
get := func(path string) string {
req := httptest.NewRequest(http.MethodGet, path, nil)
req.AddCookie(&http.Cookie{Name: "bj_lk", Value: "sess-1"})
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("%s: код %d", path, rec.Code)
}
return rec.Body.String()
}
page := get("/clients")
for _, want := range []string{`list="cl-suggest"`, `<datalist id="cl-suggest">`,
`<option value="БР-746 Бубликов Иван Петрович"></option>`, "Найти"} {
if !strings.Contains(page, want) {
t.Errorf("на странице клиентов нет %q", want)
}
}
found := get("/clients?search=" + url.QueryEscape("Аминов"))
if !strings.Contains(found, "найдено: 2") {
t.Errorf("не показан итог поиска: %s", found)
}
if !strings.Contains(found, "БР-719 Аминов Вячеслав Маркович") {
t.Error("найденный договор не выведен списком")
}
empty := get("/clients?search=" + url.QueryEscape("Петров"))
if !strings.Contains(empty, "договоров не найдено") {
t.Errorf("пустой результат не объяснён: %s", empty)
}
}