filters: add faceted Status (task_status) filter — meta.statuses, boards/bookings/map filtering (map marker visibility), UI dropdown under Size in filter grid
This commit is contained in:
parent
053bf2587d
commit
c7eae87660
@ -76,6 +76,16 @@
|
||||
<label for="date-end">Дата окончания (до)</label>
|
||||
<input type="date" id="date-end" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Статус</label>
|
||||
<div class="dropdown" id="status-dropdown">
|
||||
<button type="button" class="dropdown-btn" id="status-btn">
|
||||
<span id="status-btn-text">Все статусы</span>
|
||||
<span class="caret">▾</span>
|
||||
</button>
|
||||
<div class="dropdown-panel" id="status-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="search">Поиск (адрес / код)</label>
|
||||
<input type="text" id="search" placeholder="например, Азина" />
|
||||
|
||||
@ -8,6 +8,7 @@ function buildQuery(f: Filters): string {
|
||||
dimension: f.dimensions.join(','),
|
||||
brand: f.brands.join(','),
|
||||
manager: f.managers.join(','),
|
||||
status: f.statuses.join(','),
|
||||
search: f.search,
|
||||
date_start: f.dateStart,
|
||||
date_end: f.dateEnd,
|
||||
|
||||
@ -45,6 +45,7 @@ const cityDropdown = createDropdown('city');
|
||||
const brandDropdown = createDropdown('brand');
|
||||
const dimensionDropdown = createDropdown('dimension');
|
||||
const managerDropdown = createDropdown('manager');
|
||||
const statusDropdown = createDropdown('status');
|
||||
const view = createTimelineView(
|
||||
{
|
||||
timelineEl: el.timelineEl,
|
||||
@ -78,6 +79,7 @@ function currentFilters(): Filters {
|
||||
dimensions: dimensionDropdown.getSelected(),
|
||||
brands: brandDropdown.getSelected(),
|
||||
managers: managerDropdown.getSelected(),
|
||||
statuses: statusDropdown.getSelected(),
|
||||
search: el.search.value.trim(),
|
||||
dateStart: el.dateStart.value,
|
||||
dateEnd: el.dateEnd.value,
|
||||
@ -116,6 +118,7 @@ async function loadData(fit: boolean): Promise<void> {
|
||||
brandDropdown.updateValues(meta.brands);
|
||||
dimensionDropdown.updateValues(meta.dimensions);
|
||||
managerDropdown.updateValues(meta.managers);
|
||||
statusDropdown.updateValues(meta.statuses);
|
||||
lastBoards = boards;
|
||||
lastBookings = bookings;
|
||||
view.render(boards, bookings, fit, flags());
|
||||
@ -131,7 +134,7 @@ async function loadData(fit: boolean): Promise<void> {
|
||||
let mapLoadedKey = '';
|
||||
function facetKey(): string {
|
||||
const f = currentFilters();
|
||||
return JSON.stringify([f.cities, f.dimensions, f.brands, f.search]);
|
||||
return JSON.stringify([f.cities, f.dimensions, f.brands, f.managers, f.statuses, f.search]);
|
||||
}
|
||||
function mapVisible(): boolean {
|
||||
return !!viewModes && viewModes.getMode() !== 'timeline';
|
||||
@ -187,6 +190,7 @@ cityDropdown.onChange(scheduleReload);
|
||||
brandDropdown.onChange(scheduleReload);
|
||||
dimensionDropdown.onChange(scheduleReload);
|
||||
managerDropdown.onChange(scheduleReload);
|
||||
statusDropdown.onChange(scheduleReload);
|
||||
el.search.addEventListener('input', scheduleReload);
|
||||
el.dateStart.addEventListener('change', scheduleReload);
|
||||
el.dateEnd.addEventListener('change', scheduleReload);
|
||||
@ -202,6 +206,7 @@ async function init(): Promise<void> {
|
||||
brandDropdown.setValues([], 'Все бренды');
|
||||
dimensionDropdown.setValues([], 'Все размеры');
|
||||
managerDropdown.setValues([], 'Все менеджеры');
|
||||
statusDropdown.setValues([], 'Все статусы');
|
||||
rebuildDecorMenu();
|
||||
view.applyLayout();
|
||||
await loadData(true);
|
||||
|
||||
@ -8,6 +8,7 @@ export interface Meta {
|
||||
dimensions: string[];
|
||||
brands: string[];
|
||||
managers: string[];
|
||||
statuses: string[];
|
||||
}
|
||||
|
||||
/** One row of GET /api/boards (grouped per board_id). */
|
||||
@ -35,6 +36,7 @@ export interface Filters {
|
||||
dimensions: string[];
|
||||
brands: string[];
|
||||
managers: string[];
|
||||
statuses: string[];
|
||||
search: string;
|
||||
dateStart: string; // YYYY-MM-DD or '' — start_date >= dateStart
|
||||
dateEnd: string; // YYYY-MM-DD or '' — end_date <= dateEnd
|
||||
|
||||
49
main.py
49
main.py
@ -20,6 +20,7 @@ BRAND_COND = "(length({brands:Array(String)}) = 0 OR brand IN {brands:Array(Stri
|
||||
# manager is Array(String) in pf_board (a task may have several managers), so we
|
||||
# match with hasAny: keep the row if any selected manager is among its managers.
|
||||
MANAGER_COND = "(length({managers:Array(String)}) = 0 OR hasAny(manager, {managers:Array(String)}))"
|
||||
STATUS_COND = "(length({statuses:Array(String)}) = 0 OR task_status IN {statuses:Array(String)})"
|
||||
SEARCH_COND = (
|
||||
"({search:String} = ''"
|
||||
" OR positionCaseInsensitive(address, {search:String}) > 0"
|
||||
@ -46,9 +47,15 @@ MAP_MANAGER_COND = (
|
||||
"(length({managers:Array(String)}) = 0"
|
||||
" OR board_key IN (SELECT board_key FROM default.pf_board WHERE hasAny(manager, {managers:Array(String)})))"
|
||||
)
|
||||
# Same idea for the status facet on the map: keep surfaces that have at least one
|
||||
# booking whose task_status is among the selected values.
|
||||
MAP_STATUS_COND = (
|
||||
"(length({statuses:Array(String)}) = 0"
|
||||
" OR board_key IN (SELECT board_key FROM default.pf_board WHERE task_status IN {statuses:Array(String)}))"
|
||||
)
|
||||
|
||||
|
||||
def build_where(city=False, dimension=False, brand=False, manager=False, search=False, date_start=False, date_end=False):
|
||||
def build_where(city=False, dimension=False, brand=False, manager=False, status=False, search=False, date_start=False, date_end=False):
|
||||
# Faceted WHERE: include only the requested facet conditions. For the
|
||||
# dependent-filter option lists we exclude a facet's own condition so its
|
||||
# available values reflect the OTHER filters (all-but-self).
|
||||
@ -61,6 +68,8 @@ def build_where(city=False, dimension=False, brand=False, manager=False, search=
|
||||
parts.append(BRAND_COND)
|
||||
if manager:
|
||||
parts.append(MANAGER_COND)
|
||||
if status:
|
||||
parts.append(STATUS_COND)
|
||||
if search:
|
||||
parts.append(SEARCH_COND)
|
||||
if date_start:
|
||||
@ -87,16 +96,18 @@ async def ch_query(sql: str, params: dict):
|
||||
return [json.loads(line) for line in text.splitlines()]
|
||||
|
||||
|
||||
def parse_filters(city: str, dimension: str, brand: str, manager: str, search: str, date_start: str, date_end: str):
|
||||
def parse_filters(city: str, dimension: str, brand: str, manager: str, status: str, search: str, date_start: str, date_end: str):
|
||||
cities = [c for c in city.split(",") if c] if city else []
|
||||
dimensions = [d for d in dimension.split(",") if d] if dimension else []
|
||||
brands = [b for b in brand.split(",") if b] if brand else []
|
||||
managers = [m for m in manager.split(",") if m] if manager else []
|
||||
statuses = [s for s in status.split(",") if s] if status else []
|
||||
return {
|
||||
"cities": cities,
|
||||
"dimensions": dimensions,
|
||||
"brands": brands,
|
||||
"managers": managers,
|
||||
"statuses": statuses,
|
||||
"search": search or "",
|
||||
"date_start": date_start or "",
|
||||
"date_end": date_end or "",
|
||||
@ -104,25 +115,29 @@ def parse_filters(city: str, dimension: str, brand: str, manager: str, search: s
|
||||
|
||||
|
||||
@app.get("/api/meta")
|
||||
async def meta(city: str = "", dimension: str = "", brand: str = "", manager: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, search, date_start, date_end)
|
||||
async def meta(city: str = "", dimension: str = "", brand: str = "", manager: str = "", status: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, status, search, date_start, date_end)
|
||||
hds = bool(params["date_start"])
|
||||
hde = bool(params["date_end"])
|
||||
cities = await ch_query(
|
||||
f"SELECT DISTINCT city FROM default.pf_board WHERE {build_where(dimension=True, brand=True, manager=True, search=True, date_start=hds, date_end=hde)} AND char_length(city) > 0 ORDER BY city",
|
||||
f"SELECT DISTINCT city FROM default.pf_board WHERE {build_where(dimension=True, brand=True, manager=True, status=True, search=True, date_start=hds, date_end=hde)} AND char_length(city) > 0 ORDER BY city",
|
||||
params,
|
||||
)
|
||||
brands = await ch_query(
|
||||
f"SELECT DISTINCT brand FROM default.pf_board WHERE {build_where(city=True, dimension=True, manager=True, search=True, date_start=hds, date_end=hde)} AND char_length(brand) > 0 ORDER BY brand",
|
||||
f"SELECT DISTINCT brand FROM default.pf_board WHERE {build_where(city=True, dimension=True, manager=True, status=True, search=True, date_start=hds, date_end=hde)} AND char_length(brand) > 0 ORDER BY brand",
|
||||
params,
|
||||
)
|
||||
dimensions = await ch_query(
|
||||
f"SELECT DISTINCT dimension FROM default.pf_board WHERE {build_where(city=True, brand=True, manager=True, search=True, date_start=hds, date_end=hde)} AND char_length(dimension) > 0 ORDER BY dimension",
|
||||
f"SELECT DISTINCT dimension FROM default.pf_board WHERE {build_where(city=True, brand=True, manager=True, status=True, search=True, date_start=hds, date_end=hde)} AND char_length(dimension) > 0 ORDER BY dimension",
|
||||
params,
|
||||
)
|
||||
# manager is an Array(String) column — ARRAY JOIN to enumerate distinct names.
|
||||
managers = await ch_query(
|
||||
f"SELECT DISTINCT m AS manager FROM default.pf_board ARRAY JOIN manager AS m WHERE {build_where(city=True, dimension=True, brand=True, search=True, date_start=hds, date_end=hde)} AND char_length(m) > 0 ORDER BY manager",
|
||||
f"SELECT DISTINCT m AS manager FROM default.pf_board ARRAY JOIN manager AS m WHERE {build_where(city=True, dimension=True, brand=True, status=True, search=True, date_start=hds, date_end=hde)} AND char_length(m) > 0 ORDER BY manager",
|
||||
params,
|
||||
)
|
||||
statuses = await ch_query(
|
||||
f"SELECT DISTINCT task_status FROM default.pf_board WHERE {build_where(city=True, dimension=True, brand=True, manager=True, search=True, date_start=hds, date_end=hde)} AND char_length(task_status) > 0 ORDER BY task_status",
|
||||
params,
|
||||
)
|
||||
return {
|
||||
@ -130,13 +145,14 @@ async def meta(city: str = "", dimension: str = "", brand: str = "", manager: st
|
||||
"dimensions": [r["dimension"] for r in dimensions],
|
||||
"brands": [r["brand"] for r in brands],
|
||||
"managers": [r["manager"] for r in managers],
|
||||
"statuses": [r["task_status"] for r in statuses],
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/boards")
|
||||
async def boards(city: str = "", dimension: str = "", brand: str = "", manager: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, search, date_start, date_end)
|
||||
where = build_where(city=True, dimension=True, brand=True, manager=True, search=True, date_start=bool(params["date_start"]), date_end=bool(params["date_end"]))
|
||||
async def boards(city: str = "", dimension: str = "", brand: str = "", manager: str = "", status: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, status, search, date_start, date_end)
|
||||
where = build_where(city=True, dimension=True, brand=True, manager=True, status=True, search=True, date_start=bool(params["date_start"]), date_end=bool(params["date_end"]))
|
||||
sql = f"""
|
||||
SELECT
|
||||
board_id,
|
||||
@ -153,9 +169,9 @@ async def boards(city: str = "", dimension: str = "", brand: str = "", manager:
|
||||
|
||||
|
||||
@app.get("/api/bookings")
|
||||
async def bookings(city: str = "", dimension: str = "", brand: str = "", manager: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, search, date_start, date_end)
|
||||
where = build_where(city=True, dimension=True, brand=True, manager=True, search=True, date_start=bool(params["date_start"]), date_end=bool(params["date_end"]))
|
||||
async def bookings(city: str = "", dimension: str = "", brand: str = "", manager: str = "", status: str = "", search: str = "", date_start: str = "", date_end: str = ""):
|
||||
params = parse_filters(city, dimension, brand, manager, status, search, date_start, date_end)
|
||||
where = build_where(city=True, dimension=True, brand=True, manager=True, status=True, search=True, date_start=bool(params["date_start"]), date_end=bool(params["date_end"]))
|
||||
sql = f"""
|
||||
SELECT
|
||||
board_id,
|
||||
@ -174,11 +190,11 @@ async def bookings(city: str = "", dimension: str = "", brand: str = "", manager
|
||||
|
||||
|
||||
@app.get("/api/map")
|
||||
async def map_data(city: str = "", dimension: str = "", brand: str = "", manager: str = "", search: str = ""):
|
||||
async def map_data(city: str = "", dimension: str = "", brand: str = "", manager: str = "", status: str = "", search: str = ""):
|
||||
# Surfaces come from board_info (has coordinates). Occupancy is computed "as of
|
||||
# today" and is intentionally NOT affected by the date-range filters — only the
|
||||
# Город/Бренд/Размер/Менеджер/Поиск facets narrow which surfaces are shown.
|
||||
params = parse_filters(city, dimension, brand, manager, search, "", "")
|
||||
params = parse_filters(city, dimension, brand, manager, status, search, "", "")
|
||||
where = " AND ".join([
|
||||
"char_length(city) > 0",
|
||||
CITY_COND,
|
||||
@ -186,6 +202,7 @@ async def map_data(city: str = "", dimension: str = "", brand: str = "", manager
|
||||
SEARCH_COND,
|
||||
MAP_BRAND_COND,
|
||||
MAP_MANAGER_COND,
|
||||
MAP_STATUS_COND,
|
||||
])
|
||||
surfaces_sql = f"""
|
||||
SELECT
|
||||
|
||||
Loading…
Reference in New Issue
Block a user