Add roll-call Start/Stop UX to business and customer dashboards.

Owners/admins can run timed work days, manage tasks in a modal, and stop from the FAB or row control.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-07 13:21:07 +03:30
co-authored by Cursor
parent ac287e02cc
commit c4e846564e
30 changed files with 4063 additions and 83 deletions
@@ -6,6 +6,8 @@ export interface MultiSelectOption<T extends string | number = number> {
value: T
label: string
depth?: number
/** Extra text matched by search (e.g. EN+FA names, tags). */
keywords?: string
}
interface MultiSelectDropdownProps<T extends string | number = number> {
@@ -43,7 +45,10 @@ export function MultiSelectDropdown<T extends string | number = number>({
const filteredOptions = useMemo(() => {
const query = searchQuery.trim().toLowerCase()
if (!query) return options
return options.filter((o) => o.label.toLowerCase().includes(query))
return options.filter((o) => {
const haystack = `${o.label} ${o.keywords ?? ''}`.toLowerCase()
return haystack.includes(query)
})
}, [options, searchQuery])
const triggerLabel = useMemo(() => {
+2
View File
@@ -2,10 +2,12 @@ export interface BusinessCategory {
id: number
parentId: number | null
name: string
nameFa: string | null
slug: string
description: string | null
icon: string | null
sortOrder: number
tags?: string[]
}
export interface BusinessCategoriesResponse {
+20 -1
View File
@@ -1,6 +1,17 @@
import type { BusinessCategory } from '../types/category'
import type { MultiSelectOption } from '../components/MultiSelectDropdown'
/** Match IranBay: "English / فارسی" when both exist. */
export function businessCategoryLabel(
category: Pick<BusinessCategory, 'name' | 'nameFa'>,
) {
const nameFa = category.nameFa?.trim()
if (nameFa && nameFa !== category.name) {
return `${category.name} / ${nameFa}`
}
return category.name
}
export function flattenBusinessCategories(
categories: BusinessCategory[],
): MultiSelectOption<number>[] {
@@ -12,7 +23,15 @@ export function flattenBusinessCategories(
.sort((a, b) => a.sortOrder - b.sortOrder || a.name.localeCompare(b.name))
for (const child of children) {
result.push({ value: child.id, label: child.name, depth })
const keywords = [child.name, child.nameFa ?? '', child.slug, ...(child.tags ?? [])]
.join(' ')
.trim()
result.push({
value: child.id,
label: businessCategoryLabel(child),
depth,
keywords,
})
walk(child.id, depth + 1)
}
}