Some improvements from lan-maestro
This commit is contained in:
parent
e68b10933f
commit
ab3f0b07c2
@ -193,7 +193,7 @@ class Query(QueryABC):
|
||||
|
||||
if "key" in kwargs:
|
||||
return await userSettingsDao.find_by(
|
||||
{UserSetting.user_id: user.id, UserSetting.key: kwargs["key"]}
|
||||
[{UserSetting.user_id: user.id}, {UserSetting.key: kwargs["key"]}]
|
||||
)
|
||||
return await userSettingsDao.find_by({UserSetting.user_id: user.id})
|
||||
|
||||
|
@ -7,6 +7,7 @@ from typing import Generic, Optional, Union, TypeVar, Any, Type
|
||||
from core.const import DATETIME_FORMAT
|
||||
from core.database.abc.db_model_abc import DbModelABC
|
||||
from core.database.database import Database
|
||||
from core.database.external_data_temp_table_builder import ExternalDataTempTableBuilder
|
||||
from core.get_value import get_value
|
||||
from core.logger import DBLogger
|
||||
from core.string import camel_to_snake
|
||||
@ -16,6 +17,7 @@ T_DBM = TypeVar("T_DBM", bound=DbModelABC)
|
||||
|
||||
|
||||
class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
_external_fields: dict[str, ExternalDataTempTableBuilder] = {}
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, source: str, model_type: Type[T_DBM], table_name: str):
|
||||
@ -30,6 +32,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
|
||||
self.__db_names: dict[str, str] = {}
|
||||
self.__foreign_tables: dict[str, str] = {}
|
||||
self.__foreign_table_keys: dict[str, str] = {}
|
||||
|
||||
self.__date_attributes: set[str] = set()
|
||||
self.__ignored_attributes: set[str] = set()
|
||||
@ -42,12 +45,12 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return self._table_name
|
||||
|
||||
def attribute(
|
||||
self,
|
||||
attr_name: Attribute,
|
||||
attr_type: type,
|
||||
db_name: str = None,
|
||||
ignore=False,
|
||||
primary_key=False,
|
||||
self,
|
||||
attr_name: Attribute,
|
||||
attr_type: type,
|
||||
db_name: str = None,
|
||||
ignore=False,
|
||||
primary_key=False,
|
||||
):
|
||||
"""
|
||||
Add an attribute for db and object mapping to the data access object
|
||||
@ -77,21 +80,20 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
self.__date_attributes.add(db_name)
|
||||
|
||||
def reference(
|
||||
self,
|
||||
attr: Attribute,
|
||||
primary_attr: Attribute,
|
||||
foreign_attr: Attribute,
|
||||
table_name: str,
|
||||
self,
|
||||
attr: Attribute,
|
||||
primary_attr: Attribute,
|
||||
foreign_attr: Attribute,
|
||||
table_name: str,
|
||||
):
|
||||
"""
|
||||
Add a reference to another table for the given attribute
|
||||
:param Attribute attr: Name of the attribute in the object
|
||||
:param str primary_attr: Name of the primary key in the foreign object
|
||||
:param str foreign_attr: Name of the foreign key in the object
|
||||
:param str table_name: Name of the table to reference
|
||||
:return:
|
||||
"""
|
||||
if table_name == self._table_name:
|
||||
return
|
||||
if isinstance(attr, property):
|
||||
attr = attr.fget.__name__
|
||||
|
||||
@ -105,11 +107,18 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
|
||||
foreign_attr = foreign_attr.lower().replace("_", "")
|
||||
|
||||
self.__foreign_table_keys[attr] = foreign_attr
|
||||
if table_name == self._table_name:
|
||||
return
|
||||
|
||||
self.__joins[foreign_attr] = (
|
||||
f"LEFT JOIN {table_name} ON {table_name}.{primary_attr} = {self._table_name}.{foreign_attr}"
|
||||
)
|
||||
self.__foreign_tables[attr] = table_name
|
||||
|
||||
def use_external_fields(self, builder: ExternalDataTempTableBuilder):
|
||||
self._external_fields[builder.table_name] = builder
|
||||
|
||||
def to_object(self, result: dict) -> T_DBM:
|
||||
"""
|
||||
Convert a result from the database to an object
|
||||
@ -136,7 +145,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
query += f" {self.__joins[join]}"
|
||||
|
||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||
query += f" WHERE {self._build_conditions(filters)}"
|
||||
conditions, external_table_deps = await self._build_conditions(filters)
|
||||
query = await self._handle_query_external_temp_tables(
|
||||
query, external_table_deps, ignore_fields=True
|
||||
)
|
||||
query += f" WHERE {conditions};"
|
||||
|
||||
result = await self._db.select_map(query)
|
||||
if len(result) == 0:
|
||||
@ -168,11 +181,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return self.to_object(result[0])
|
||||
|
||||
async def get_by(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
) -> list[T_DBM]:
|
||||
"""
|
||||
Get all objects by the given filters
|
||||
@ -185,7 +198,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
:raises ValueError: When no result is found
|
||||
"""
|
||||
result = await self._db.select_map(
|
||||
self._build_conditional_query(filters, sorts, take, skip)
|
||||
await self._build_conditional_query(filters, sorts, take, skip)
|
||||
)
|
||||
if not result or len(result) == 0:
|
||||
raise ValueError("No result found")
|
||||
@ -193,11 +206,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return [self.to_object(x) for x in result]
|
||||
|
||||
async def get_single_by(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
) -> T_DBM:
|
||||
"""
|
||||
Get a single object by the given filters
|
||||
@ -218,11 +231,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return result[0]
|
||||
|
||||
async def find_by(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
) -> list[Optional[T_DBM]]:
|
||||
"""
|
||||
Find all objects by the given filters
|
||||
@ -234,7 +247,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
:rtype: list[Optional[T_DBM]]
|
||||
"""
|
||||
result = await self._db.select_map(
|
||||
self._build_conditional_query(filters, sorts, take, skip)
|
||||
await self._build_conditional_query(filters, sorts, take, skip)
|
||||
)
|
||||
if not result or len(result) == 0:
|
||||
return []
|
||||
@ -242,11 +255,11 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return [self.to_object(x) for x in result]
|
||||
|
||||
async def find_single_by(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
) -> Optional[T_DBM]:
|
||||
"""
|
||||
Find a single object by the given filters
|
||||
@ -346,7 +359,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
await self._db.execute(query)
|
||||
|
||||
async def _build_delete_statement(
|
||||
self, obj: T_DBM, hard_delete: bool = False
|
||||
self, obj: T_DBM, hard_delete: bool = False
|
||||
) -> str:
|
||||
if hard_delete:
|
||||
return f"""
|
||||
@ -424,7 +437,7 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
return "NULL"
|
||||
|
||||
if isinstance(value, Enum):
|
||||
return str(value.value)
|
||||
return f"'{value.value}'"
|
||||
|
||||
if isinstance(value, bool):
|
||||
return "true" if value else "false"
|
||||
@ -461,77 +474,115 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
|
||||
return cast_type(value)
|
||||
|
||||
def _build_conditional_query(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
async def _handle_query_external_temp_tables(
|
||||
self, query: str, external_table_deps: list[str], ignore_fields=False
|
||||
) -> str:
|
||||
for dep in external_table_deps:
|
||||
temp_table = self._external_fields[dep]
|
||||
temp_table_sql = await temp_table.build()
|
||||
|
||||
if not ignore_fields:
|
||||
query = query.replace(
|
||||
" FROM",
|
||||
f", {','.join([f'{temp_table.table_name}.{x}' for x in temp_table.fields.keys() if x not in self.__db_names])} FROM",
|
||||
)
|
||||
|
||||
query = f"{temp_table_sql}\n{query}"
|
||||
query += f" LEFT JOIN {temp_table.table_name} ON {temp_table.join_ref_table}.{self.__primary_key} = {temp_table.table_name}.{temp_table.primary_key}"
|
||||
|
||||
return query
|
||||
|
||||
async def _build_conditional_query(
|
||||
self,
|
||||
filters: AttributeFilters = None,
|
||||
sorts: AttributeSorts = None,
|
||||
take: int = None,
|
||||
skip: int = None,
|
||||
) -> str:
|
||||
query = f"SELECT {self._table_name}.* FROM {self._table_name}"
|
||||
|
||||
for join in self.__joins:
|
||||
query += f" {self.__joins[join]}"
|
||||
|
||||
if filters is not None and (not isinstance(filters, list) or len(filters) > 0):
|
||||
query += f" WHERE {self._build_conditions(filters)}"
|
||||
conditions, external_table_deps = await self._build_conditions(filters)
|
||||
query = await self._handle_query_external_temp_tables(
|
||||
query, external_table_deps
|
||||
)
|
||||
query += f" WHERE {conditions}"
|
||||
if sorts is not None and (not isinstance(sorts, list) or len(sorts) > 0):
|
||||
query += f" ORDER BY {self._build_order_by(sorts)}"
|
||||
if take is not None:
|
||||
query += f" LIMIT {take}"
|
||||
if skip is not None:
|
||||
query += f" OFFSET {skip}"
|
||||
|
||||
if not query.endswith(";"):
|
||||
query += ";"
|
||||
return query
|
||||
|
||||
def _build_conditions(self, filters: AttributeFilters) -> str:
|
||||
def _get_external_field_key(self, field_name: str) -> Optional[str]:
|
||||
"""
|
||||
Returns the key to get the external field if found, otherwise None.
|
||||
:param str field_name: The name of the field to search for.
|
||||
:return: The key if found, otherwise None.
|
||||
:rtype: Optional[str]
|
||||
"""
|
||||
for key, builder in self._external_fields.items():
|
||||
if field_name in builder.fields and field_name not in self.__db_names:
|
||||
return key
|
||||
return None
|
||||
|
||||
async def _build_conditions(self, filters: AttributeFilters) -> (str, list[str]):
|
||||
"""
|
||||
Build SQL conditions from the given filters
|
||||
:param filters:
|
||||
:return:
|
||||
:return: SQL conditions & External field table dependencies
|
||||
"""
|
||||
external_field_table_deps = []
|
||||
if not isinstance(filters, list):
|
||||
filters = [filters]
|
||||
|
||||
conditions = []
|
||||
for f in filters:
|
||||
f_conditions = []
|
||||
|
||||
for attr, values in f.items():
|
||||
if isinstance(attr, property):
|
||||
attr = attr.fget.__name__
|
||||
|
||||
if attr in self.__foreign_tables:
|
||||
foreign_table = self.__foreign_tables[attr]
|
||||
conditions.extend(
|
||||
self._build_foreign_conditions(foreign_table, values)
|
||||
)
|
||||
cons, eftd = self._build_foreign_conditions(foreign_table, values)
|
||||
if eftd:
|
||||
external_field_table_deps.extend(eftd)
|
||||
|
||||
f_conditions.extend(cons)
|
||||
continue
|
||||
|
||||
if attr == "fuzzy":
|
||||
conditions.append(
|
||||
" OR ".join(
|
||||
self._build_fuzzy_conditions(
|
||||
[
|
||||
(
|
||||
self.__db_names[x]
|
||||
if x in self.__db_names
|
||||
else self.__db_names[camel_to_snake(x)]
|
||||
)
|
||||
for x in get_value(values, "fields", list[str])
|
||||
],
|
||||
get_value(values, "term", str),
|
||||
get_value(values, "threshold", int, 5),
|
||||
)
|
||||
)
|
||||
self._handle_fuzzy_filter_conditions(
|
||||
f_conditions, external_field_table_deps, values
|
||||
)
|
||||
continue
|
||||
|
||||
db_name = self.__db_names[attr]
|
||||
external_fields_table_name = self._get_external_field_key(attr)
|
||||
if external_fields_table_name is not None:
|
||||
external_fields_table = self._external_fields[
|
||||
external_fields_table_name
|
||||
]
|
||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
||||
external_field_table_deps.append(external_fields_table.table_name)
|
||||
elif (
|
||||
isinstance(values, dict) or isinstance(values, list)
|
||||
) and not attr in self.__foreign_tables:
|
||||
db_name = f"{self._table_name}.{self.__db_names[attr]}"
|
||||
else:
|
||||
db_name = self.__db_names[attr]
|
||||
|
||||
if isinstance(values, dict):
|
||||
for operator, value in values.items():
|
||||
conditions.append(
|
||||
self._build_condition(
|
||||
f"{self._table_name}.{db_name}", operator, value
|
||||
)
|
||||
f_conditions.append(
|
||||
self._build_condition(f"{db_name}", operator, value)
|
||||
)
|
||||
elif isinstance(values, list):
|
||||
sub_conditions = []
|
||||
@ -539,38 +590,42 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
if isinstance(value, dict):
|
||||
for operator, val in value.items():
|
||||
sub_conditions.append(
|
||||
self._build_condition(
|
||||
f"{self._table_name}.{db_name}", operator, val
|
||||
)
|
||||
self._build_condition(f"{db_name}", operator, val)
|
||||
)
|
||||
else:
|
||||
sub_conditions.append(
|
||||
self._get_value_validation_sql(db_name, value)
|
||||
)
|
||||
conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||
f_conditions.append(f"({' OR '.join(sub_conditions)})")
|
||||
else:
|
||||
conditions.append(self._get_value_validation_sql(db_name, values))
|
||||
f_conditions.append(self._get_value_validation_sql(db_name, values))
|
||||
|
||||
return " AND ".join(conditions)
|
||||
conditions.append(f"({' OR '.join(f_conditions)})")
|
||||
|
||||
return " AND ".join(conditions), external_field_table_deps
|
||||
|
||||
@staticmethod
|
||||
def _build_fuzzy_conditions(
|
||||
self, fields: list[str], term: str, threshold: int = 10
|
||||
fields: list[str], term: str, threshold: int = 10
|
||||
) -> list[str]:
|
||||
conditions = []
|
||||
for field in fields:
|
||||
conditions.append(
|
||||
f"levenshtein({field}, '{term}') <= {threshold}"
|
||||
f"levenshtein({field}::TEXT, '{term}') <= {threshold}"
|
||||
) # Adjust the threshold as needed
|
||||
|
||||
return conditions
|
||||
|
||||
def _build_foreign_conditions(self, table: str, values: dict) -> list[str]:
|
||||
def _build_foreign_conditions(
|
||||
self, table: str, values: dict
|
||||
) -> (list[str], list[str]):
|
||||
"""
|
||||
Build SQL conditions for foreign key references
|
||||
:param table: Foreign table name
|
||||
:param values: Filter values
|
||||
:return: List of conditions
|
||||
:return: List of conditions, List of external field tables
|
||||
"""
|
||||
external_field_table_deps = []
|
||||
conditions = []
|
||||
for attr, sub_values in values.items():
|
||||
if isinstance(attr, property):
|
||||
@ -578,25 +633,43 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
|
||||
if attr in self.__foreign_tables:
|
||||
foreign_table = self.__foreign_tables[attr]
|
||||
conditions.extend(
|
||||
self._build_foreign_conditions(foreign_table, sub_values)
|
||||
sub_conditions, eftd = self._build_foreign_conditions(
|
||||
foreign_table, sub_values
|
||||
)
|
||||
if len(eftd) > 0:
|
||||
external_field_table_deps.extend(eftd)
|
||||
|
||||
conditions.extend(sub_conditions)
|
||||
continue
|
||||
|
||||
if attr == "fuzzy":
|
||||
self._handle_fuzzy_filter_conditions(
|
||||
conditions, external_field_table_deps, sub_values
|
||||
)
|
||||
continue
|
||||
|
||||
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
||||
external_fields_table_name = self._get_external_field_key(attr)
|
||||
if external_fields_table_name is not None:
|
||||
external_fields_table = self._external_fields[
|
||||
external_fields_table_name
|
||||
]
|
||||
db_name = f"{external_fields_table.table_name}.{attr}"
|
||||
external_field_table_deps.append(external_fields_table.table_name)
|
||||
else:
|
||||
db_name = f"{table}.{attr.lower().replace('_', '')}"
|
||||
|
||||
if isinstance(sub_values, dict):
|
||||
for operator, value in sub_values.items():
|
||||
conditions.append(
|
||||
f"({self._build_condition(db_name, operator, value)} OR {self._build_condition(db_name, "isNull", None)})")
|
||||
f"{self._build_condition(db_name, operator, value)}"
|
||||
)
|
||||
elif isinstance(sub_values, list):
|
||||
sub_conditions = []
|
||||
for value in sub_values:
|
||||
if isinstance(value, dict):
|
||||
for operator, val in value.items():
|
||||
sub_conditions.append(
|
||||
f"({self._build_condition(db_name, operator, val)} OR {self._build_condition(db_name, "isNull", None)})"
|
||||
|
||||
f"{self._build_condition(db_name, operator, val)}"
|
||||
)
|
||||
else:
|
||||
sub_conditions.append(
|
||||
@ -606,14 +679,55 @@ class DataAccessObjectABC(ABC, Database, Generic[T_DBM]):
|
||||
else:
|
||||
conditions.append(self._get_value_validation_sql(db_name, sub_values))
|
||||
|
||||
return conditions
|
||||
return conditions, external_field_table_deps
|
||||
|
||||
def _handle_fuzzy_filter_conditions(
|
||||
self, conditions, external_field_table_deps, sub_values
|
||||
):
|
||||
fuzzy_fields = get_value(sub_values, "fields", list[str])
|
||||
fuzzy_fields_db_names = []
|
||||
for fuzzy_field in fuzzy_fields:
|
||||
external_fields_table_name = self._get_external_field_key(fuzzy_field)
|
||||
if external_fields_table_name is not None:
|
||||
external_fields_table = self._external_fields[
|
||||
external_fields_table_name
|
||||
]
|
||||
fuzzy_fields_db_names.append(
|
||||
f"{external_fields_table.table_name}.{fuzzy_field}"
|
||||
)
|
||||
external_field_table_deps.append(external_fields_table.table_name)
|
||||
elif fuzzy_field in self.__db_names:
|
||||
fuzzy_fields_db_names.append(
|
||||
f"{self._table_name}.{self.__db_names[fuzzy_field]}"
|
||||
)
|
||||
elif fuzzy_field in self.__foreign_tables:
|
||||
fuzzy_fields_db_names.append(
|
||||
f"{self._table_name}.{self.__foreign_table_keys[fuzzy_field]}"
|
||||
)
|
||||
else:
|
||||
fuzzy_fields_db_names.append(
|
||||
self.__db_names[camel_to_snake(fuzzy_field)]
|
||||
)
|
||||
conditions.append(
|
||||
f"({' OR '.join(
|
||||
self._build_fuzzy_conditions(
|
||||
[x for x in fuzzy_fields_db_names],
|
||||
get_value(sub_values, "term", str),
|
||||
get_value(sub_values, "threshold", int, 5),
|
||||
)
|
||||
)
|
||||
})"
|
||||
)
|
||||
|
||||
def _get_value_validation_sql(self, field: str, value: Any):
|
||||
value = self._get_value_sql(value)
|
||||
field_selector = f"{self._table_name}.{field}"
|
||||
if field in self.__foreign_tables:
|
||||
field_selector = self.__db_names[field]
|
||||
|
||||
if value == "NULL":
|
||||
return f"{self._table_name}.{field} IS NULL"
|
||||
return f"{self._table_name}.{field} = {value}"
|
||||
return f"{field_selector} IS NULL"
|
||||
return f"{field_selector} = {value}"
|
||||
|
||||
def _build_condition(self, db_name: str, operator: str, value: Any) -> str:
|
||||
"""
|
||||
|
72
api/src/core/database/external_data_temp_table_builder.py
Normal file
72
api/src/core/database/external_data_temp_table_builder.py
Normal file
@ -0,0 +1,72 @@
|
||||
import textwrap
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class ExternalDataTempTableBuilder:
|
||||
|
||||
def __init__(self):
|
||||
self._table_name = None
|
||||
self._fields: dict[str, str] = {}
|
||||
self._primary_key = "id"
|
||||
self._join_ref_table = None
|
||||
self._value_getter = None
|
||||
|
||||
@property
|
||||
def table_name(self) -> str:
|
||||
return self._table_name
|
||||
|
||||
@property
|
||||
def fields(self) -> dict[str, str]:
|
||||
return self._fields
|
||||
|
||||
@property
|
||||
def primary_key(self) -> str:
|
||||
return self._primary_key
|
||||
|
||||
@property
|
||||
def join_ref_table(self) -> str:
|
||||
return self._join_ref_table
|
||||
|
||||
def with_table_name(self, table_name: str) -> "ExternalDataTempTableBuilder":
|
||||
self._join_ref_table = table_name
|
||||
|
||||
if "." in table_name:
|
||||
table_name = table_name.split(".")[-1]
|
||||
|
||||
if not table_name.endswith("_temp"):
|
||||
table_name = f"{table_name}_temp"
|
||||
|
||||
self._table_name = table_name
|
||||
return self
|
||||
|
||||
def with_field(
|
||||
self, name: str, sql_type: str, primary=False
|
||||
) -> "ExternalDataTempTableBuilder":
|
||||
if primary:
|
||||
sql_type += " PRIMARY KEY"
|
||||
self._primary_key = name
|
||||
self._fields[name] = sql_type
|
||||
return self
|
||||
|
||||
def with_value_getter(
|
||||
self, value_getter: Callable
|
||||
) -> "ExternalDataTempTableBuilder":
|
||||
self._value_getter = value_getter
|
||||
return self
|
||||
|
||||
async def build(self) -> str:
|
||||
assert self._table_name is not None, "Table name is required"
|
||||
assert self._value_getter is not None, "Value getter is required"
|
||||
|
||||
values_str = ", ".join([f"{value}" for value in await self._value_getter()])
|
||||
|
||||
return textwrap.dedent(
|
||||
f"""
|
||||
DROP TABLE IF EXISTS {self._table_name};
|
||||
CREATE TEMP TABLE {self._table_name} (
|
||||
{", ".join([f"{k} {v}" for k, v in self._fields.items()])}
|
||||
);
|
||||
|
||||
INSERT INTO {self._table_name} VALUES {values_str};
|
||||
"""
|
||||
)
|
@ -1,12 +1,37 @@
|
||||
<div class="flex justify-between w-full">
|
||||
<div *ngFor="let element of elements">
|
||||
<p-button
|
||||
*ngIf="element.visible !== false"
|
||||
type="button"
|
||||
label="{{ element.label | translate }}"
|
||||
[icon]="element.icon"
|
||||
class="icon-btn btn"
|
||||
[routerLink]="element.routerLink"
|
||||
(onClick)="element.command?.()"></p-button>
|
||||
</div>
|
||||
<div *ngFor="let element of visibleElements" class="flex justify-center">
|
||||
<ng-container *ngIf="element.visible !== false && element.routerLink">
|
||||
<a
|
||||
[routerLink]="element.routerLink"
|
||||
(click)="element.command?.()"
|
||||
class="icon-btn btn flex gap-2">
|
||||
<i *ngIf="element.icon" class="{{ element.icon }}"></i>
|
||||
<b>{{ element.label | translate }}</b>
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="element.visible !== false && element.items">
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
icon="{{ element.icon }}"
|
||||
(onClick)="overlayPanel.toggle($event)"
|
||||
label="{{ element.label | translate }}">
|
||||
</p-button>
|
||||
|
||||
<p-overlayPanel [styleClass]="theme" #overlayPanel>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div *ngFor="let item of element.items" class="flex gap-1">
|
||||
<a
|
||||
*ngIf="item.visible !== false"
|
||||
[routerLink]="item.routerLink"
|
||||
(click)="overlayPanel.toggle($event); item.command?.()"
|
||||
class="icon-btn btn flex gap-2">
|
||||
<i *ngIf="item.icon" class="{{ item.icon }}"></i>
|
||||
<b>{{ item.label | translate }}</b>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</p-overlayPanel>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,32 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { MenuElement } from "src/app/model/view/menu-element";
|
||||
import { Component, EventEmitter, Input, OnDestroy } from '@angular/core';
|
||||
import { MenuElement } from 'src/app/model/view/menu-element';
|
||||
import { GuiService } from 'src/app/service/gui.service';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: "app-menu-bar",
|
||||
templateUrl: "./menu-bar.component.html",
|
||||
styleUrl: "./menu-bar.component.scss",
|
||||
selector: 'app-menu-bar',
|
||||
templateUrl: './menu-bar.component.html',
|
||||
styleUrl: './menu-bar.component.scss',
|
||||
})
|
||||
export class MenuBarComponent {
|
||||
export class MenuBarComponent implements OnDestroy {
|
||||
@Input() elements: MenuElement[] = [];
|
||||
protected theme = '';
|
||||
private unsubscribe$ = new EventEmitter<void>();
|
||||
|
||||
get visibleElements() {
|
||||
return this.elements.filter(e => e.visible !== false);
|
||||
}
|
||||
|
||||
constructor(private guiService: GuiService) {
|
||||
this.guiService.theme$
|
||||
.pipe(takeUntil(this.unsubscribe$))
|
||||
.subscribe(theme => {
|
||||
this.theme = theme;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubscribe$.next();
|
||||
this.unsubscribe$.complete();
|
||||
}
|
||||
}
|
||||
|
@ -1,147 +1,172 @@
|
||||
<p-table
|
||||
[dataKey]="dataKey"
|
||||
[value]="rows"
|
||||
[paginator]="true"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions"
|
||||
[rows]="take"
|
||||
(rowsChange)="takeChange.emit($event)"
|
||||
[first]="skip"
|
||||
(firstChange)="skipChange.emit($event)"
|
||||
[totalRecords]="totalCount"
|
||||
[lazy]="true"
|
||||
(onLazyLoad)="loadData($event)"
|
||||
[loading]="loading"
|
||||
[resizableColumns]="false"
|
||||
[reorderableColumns]="false"
|
||||
[responsiveLayout]="responsiveLayout"
|
||||
columnResizeMode="expand"
|
||||
[breakpoint]="'900px'"
|
||||
[rowHover]="true">
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div *ngIf="countHeaderTranslation" class="table-caption-table-info">
|
||||
<div class="table-caption-text">
|
||||
<ng-container *ngIf="!loading"
|
||||
>{{ skip + 1 }} {{ 'table.to' | translate }}
|
||||
{{ skip + rows.length }} {{ 'table.of' | translate }}
|
||||
{{ totalCount }}
|
||||
</ng-container>
|
||||
{{ countHeaderTranslation | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<ng-container *ngTemplateOutlet="customActions"></ng-container>
|
||||
<p-button
|
||||
*ngIf="create && hasPermissions.create"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-plus"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.create' | translate }}"
|
||||
routerLink="{{ updateBaseUrl }}create"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
[styleClass]="showDeleted ? 'highlight2' : ''"
|
||||
icon="pi pi-trash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{
|
||||
[dataKey]="dataKey"
|
||||
[value]="rows"
|
||||
[paginator]="true"
|
||||
[rowsPerPageOptions]="rowsPerPageOptions"
|
||||
[rows]="take"
|
||||
(rowsChange)="takeChange.emit($event)"
|
||||
[first]="skip"
|
||||
(firstChange)="skipChange.emit($event)"
|
||||
[totalRecords]="totalCount"
|
||||
[lazy]="true"
|
||||
(onLazyLoad)="loadData($event)"
|
||||
[loading]="loading"
|
||||
[resizableColumns]="false"
|
||||
[reorderableColumns]="false"
|
||||
[responsiveLayout]="responsiveLayout"
|
||||
columnResizeMode="expand"
|
||||
[breakpoint]="'900px'"
|
||||
[rowHover]="true">
|
||||
<ng-template pTemplate="caption">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<div *ngIf="countHeaderTranslation" class="table-caption-table-info">
|
||||
<div class="table-caption-text">
|
||||
<ng-container *ngIf="!loading"
|
||||
>{{ skip + 1 }} {{ 'table.to' | translate }}
|
||||
{{ skip + rows.length }} {{ 'table.of' | translate }}
|
||||
{{ totalCount }}
|
||||
</ng-container>
|
||||
{{ countHeaderTranslation | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<ng-container *ngTemplateOutlet="customActions"></ng-container>
|
||||
<p-button
|
||||
*ngIf="create && hasPermissions.create"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-plus"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.create' | translate }}"
|
||||
routerLink="{{ updateBaseUrl }}create"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
[styleClass]="showDeleted ? 'highlight2' : ''"
|
||||
icon="pi pi-trash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{
|
||||
(showDeleted ? 'table.hide_deleted' : 'table.show_deleted')
|
||||
| translate
|
||||
}}"
|
||||
(click)="toggleShowDeleted()"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-sort-alt-slash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.reset_sort' | translate }}"
|
||||
(click)="resetSort()"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-filter-slash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.reset_filters' | translate }}"
|
||||
(click)="resetFilters()"></p-button>
|
||||
<app-column-selector
|
||||
*ngIf="selectableColumns"
|
||||
[columns]="columns"
|
||||
(selectChange)="resolveColumns()"></app-column-selector>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th
|
||||
*ngFor="let column of visibleColumns"
|
||||
[pSortableColumn]="column.name"
|
||||
[class]="column.class ?? ''"
|
||||
[style.min-width]="
|
||||
column.minWidth ? column.minWidth + ' !important' : ''
|
||||
"
|
||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
column.maxWidth ? column.maxWidth + ' !important' : ''
|
||||
">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>{{ column.translationKey | translate }}</span>
|
||||
<p-sortIcon [field]="column.name"></p-sortIcon>
|
||||
(click)="toggleShowDeleted()"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-sort-alt-slash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.reset_sort' | translate }}"
|
||||
(click)="resetSort()"></p-button>
|
||||
<p-button
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-filter-slash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.reset_filters' | translate }}"
|
||||
(click)="resetFilters()"></p-button>
|
||||
<app-column-selector
|
||||
*ngIf="selectableColumns"
|
||||
[columns]="columns"
|
||||
(selectChange)="resolveColumns()"></app-column-selector>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th *ngIf="update || delete.observed"></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
<tr *ngIf="showFilters">
|
||||
<th
|
||||
*ngFor="let column of visibleColumns"
|
||||
[class]="column.class ?? ''"
|
||||
[style.min-width]="
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th
|
||||
*ngFor="let column of visibleColumns"
|
||||
[pSortableColumn]="column.name"
|
||||
[class]="column.class ?? ''"
|
||||
[style.min-width]="
|
||||
column.minWidth ? column.minWidth + ' !important' : ''
|
||||
"
|
||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
column.maxWidth ? column.maxWidth + ' !important' : ''
|
||||
">
|
||||
<form *ngIf="filterForm && column.filterable" [formGroup]="filterForm">
|
||||
<ng-container [ngSwitch]="column.type">
|
||||
<input
|
||||
*ngSwitchCase="'date'"
|
||||
pInputText
|
||||
type="text"
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border" />
|
||||
<p-triStateCheckbox
|
||||
*ngSwitchCase="'bool'"
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border"></p-triStateCheckbox>
|
||||
<input
|
||||
*ngSwitchDefault
|
||||
pInputText
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border" />
|
||||
</ng-container>
|
||||
</form>
|
||||
</th>
|
||||
<th *ngIf="update || delete.observed"></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-row let-i="rowIndex">
|
||||
<tr *ngIf="hasPermissions.read && resolvedColumns.length > 0">
|
||||
<ng-container *ngFor="let r of resolvedColumns[i - take * (skip / take)]">
|
||||
<td
|
||||
[ngClass]="{ deleted: row?.deleted }"
|
||||
[style.min-width]="
|
||||
<div class="flex items-center space-x-2">
|
||||
<span>{{ column.translationKey | translate }}</span>
|
||||
<p-sortIcon [field]="column.name"></p-sortIcon>
|
||||
</div>
|
||||
</th>
|
||||
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||
</tr>
|
||||
|
||||
<tr *ngIf="fuzzyFilter">
|
||||
<th [attr.colspan]="visibleColumns.length">
|
||||
<form
|
||||
*ngIf="filterForm"
|
||||
[formGroup]="filterForm"
|
||||
class="flex gap-2 justify-between items-center">
|
||||
<input
|
||||
pInputText
|
||||
type="text"
|
||||
class="w-full box-border"
|
||||
formControlName="fuzzy"
|
||||
placeholder="{{ 'table.search' | translate }}" />
|
||||
<p-button
|
||||
class="btn icon-btn"
|
||||
icon="pi pi-times"
|
||||
(onClick)="filterForm.get('fuzzy')?.setValue(undefined)"
|
||||
[disabled]="!filterForm.get('fuzzy')?.value"></p-button>
|
||||
</form>
|
||||
</th>
|
||||
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||
</tr>
|
||||
|
||||
<tr *ngIf="showFilters">
|
||||
<th
|
||||
*ngFor="let column of visibleColumns"
|
||||
[class]="column.class ?? ''"
|
||||
[style.min-width]="
|
||||
column.minWidth ? column.minWidth + ' !important' : ''
|
||||
"
|
||||
[style.width]="column.width ? column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
column.maxWidth ? column.maxWidth + ' !important' : ''
|
||||
">
|
||||
<form *ngIf="filterForm && column.filterable" [formGroup]="filterForm">
|
||||
<ng-container [ngSwitch]="column.type">
|
||||
<input
|
||||
*ngSwitchCase="'date'"
|
||||
pInputText
|
||||
type="text"
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border" />
|
||||
<p-triStateCheckbox
|
||||
*ngSwitchCase="'bool'"
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border"></p-triStateCheckbox>
|
||||
<input
|
||||
*ngSwitchDefault
|
||||
pInputText
|
||||
[formControlName]="column.name"
|
||||
class="w-full box-border" />
|
||||
</ng-container>
|
||||
</form>
|
||||
</th>
|
||||
<th *ngIf="update || delete.observed || customRowActionsVisible"></th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-row let-i="rowIndex">
|
||||
<tr *ngIf="hasPermissions.read && resolvedColumns.length > 0">
|
||||
<ng-container *ngFor="let r of resolvedColumns[i - take * (skip / take)]">
|
||||
<td
|
||||
[ngClass]="{
|
||||
deleted: row?.deleted,
|
||||
'row-disabled': rowDisabled(row),
|
||||
}"
|
||||
[style.min-width]="
|
||||
r.column.minWidth ? r.column.minWidth + ' !important' : ''
|
||||
"
|
||||
[style.width]="r.column.width ? r.column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
[style.width]="r.column.width ? r.column.width + ' !important' : ''"
|
||||
[style.max-width]="
|
||||
r.column.maxWidth ? r.column.maxWidth + ' !important' : ''
|
||||
">
|
||||
<ng-container [ngSwitch]="r.column.type">
|
||||
<ng-container [ngSwitch]="r.column.type">
|
||||
<span *ngSwitchCase="'date'">
|
||||
{{ r.value | customDate: 'dd.MM.yyyy HH:mm:ss' }}
|
||||
</span>
|
||||
<span *ngSwitchCase="'bool'">
|
||||
<span *ngSwitchCase="'bool'">
|
||||
<ng-container [ngSwitch]="r.value">
|
||||
<span *ngSwitchCase="true">
|
||||
<span class="pi pi-check-circle info"></span>
|
||||
@ -151,56 +176,57 @@
|
||||
</span>
|
||||
</ng-container>
|
||||
</span>
|
||||
<span *ngSwitchCase="'password'">
|
||||
<span *ngSwitchCase="'password'">
|
||||
{{ r.value | protect }}
|
||||
<p-button
|
||||
class="btn icon-btn"
|
||||
icon="pi pi-copy"
|
||||
(onClick)="copy(r.value)"></p-button>
|
||||
<p-button
|
||||
class="btn icon-btn"
|
||||
icon="pi pi-copy"
|
||||
(onClick)="copy(r.value)"></p-button>
|
||||
</span>
|
||||
<span *ngSwitchDefault>{{ r.value }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<td class="flex max-w-32">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
<span *ngSwitchDefault>{{ r.value }}</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<td class="flex max-w-32">
|
||||
<ng-container
|
||||
*ngTemplateOutlet="
|
||||
customRowActions;
|
||||
context: { $implicit: row }
|
||||
context: { $implicit: row, rowDisabled: rowDisabled(row) }
|
||||
"></ng-container>
|
||||
<p-button
|
||||
*ngIf="update && hasPermissions.update"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-pencil"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.update' | translate }}"
|
||||
[disabled]="row?.deleted"
|
||||
routerLink="{{ updateBaseUrl }}edit/{{ row.id }}"></p-button>
|
||||
<p-button
|
||||
*ngIf="delete.observed && hasPermissions.delete"
|
||||
class="icon-btn btn danger-icon-btn"
|
||||
icon="pi pi-trash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.delete' | translate }}"
|
||||
[disabled]="row?.deleted"
|
||||
(click)="delete.emit(row)"></p-button>
|
||||
<p-button
|
||||
*ngIf="restore.observed && row?.deleted && hasPermissions.restore"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-undo"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.restore' | translate }}"
|
||||
(click)="restore.emit(row)"></p-button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td [attr.colspan]="columns.length + 1">
|
||||
{{ 'table.no_entries_found' | translate }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</ng-template>
|
||||
<p-button
|
||||
*ngIf="update && hasPermissions.update"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-pencil"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.update' | translate }}"
|
||||
[disabled]="rowDisabled(row) || row?.deleted"
|
||||
routerLink="{{ updateBaseUrl }}edit/{{ row.id }}"></p-button>
|
||||
<p-button
|
||||
*ngIf="delete.observed && hasPermissions.delete"
|
||||
class="icon-btn btn danger-icon-btn"
|
||||
icon="pi pi-trash"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.delete' | translate }}"
|
||||
[disabled]="rowDisabled(row) || row?.deleted"
|
||||
(click)="delete.emit(row)"></p-button>
|
||||
<p-button
|
||||
*ngIf="restore.observed && row?.deleted && hasPermissions.restore"
|
||||
class="icon-btn btn"
|
||||
icon="pi pi-undo"
|
||||
tooltipPosition="left"
|
||||
pTooltip="{{ 'table.restore' | translate }}"
|
||||
(click)="restore.emit(row)"
|
||||
[disabled]="rowDisabled(row)"></p-button>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr></tr>
|
||||
<tr>
|
||||
<td [attr.colspan]="columns.length + 1">
|
||||
{{ 'table.no_entries_found' | translate }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
</ng-template>
|
||||
</p-table>
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
ResolvedTableColumn,
|
||||
TableColumn,
|
||||
TableColumnFuzzyFilter,
|
||||
TableRequireAnyPermissions,
|
||||
} from 'src/app/modules/shared/components/table/table.model';
|
||||
import { TableLazyLoadEvent } from 'primeng/table';
|
||||
@ -46,6 +47,7 @@ export class TableComponent<T> implements OnInit {
|
||||
}
|
||||
|
||||
@Input({ required: true }) columns: TableColumn<T>[] = [];
|
||||
@Input() fuzzyFilter?: TableColumnFuzzyFilter;
|
||||
|
||||
get visibleColumns() {
|
||||
return this.columns.filter(x => x.visible);
|
||||
@ -70,6 +72,8 @@ export class TableComponent<T> implements OnInit {
|
||||
@Input() sort: Sort[] = [];
|
||||
@Output() sortChange = new EventEmitter<Sort[]>();
|
||||
|
||||
@Input() rowDisabled: (row: T) => boolean = () => false;
|
||||
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||
@Output() load = new EventEmitter<void>();
|
||||
@Input() loading = true;
|
||||
@ -92,6 +96,10 @@ export class TableComponent<T> implements OnInit {
|
||||
@ContentChild(CustomRowActionsDirective, { read: TemplateRef })
|
||||
customRowActions!: TemplateRef<never>;
|
||||
|
||||
get customRowActionsVisible() {
|
||||
return !!this.customRowActions;
|
||||
}
|
||||
|
||||
protected resolvedColumns: ResolvedTableColumn<T>[][] = [];
|
||||
protected filterForm!: FormGroup;
|
||||
protected defaultFilterForm!: FormGroup;
|
||||
@ -244,6 +252,13 @@ export class TableComponent<T> implements OnInit {
|
||||
|
||||
buildDefaultFilterForm() {
|
||||
this.defaultFilterForm = new FormGroup({});
|
||||
if (this.fuzzyFilter) {
|
||||
this.defaultFilterForm.addControl(
|
||||
'fuzzy',
|
||||
new FormControl<string | undefined>(undefined)
|
||||
);
|
||||
}
|
||||
|
||||
this.columns
|
||||
.filter(x => x.filterable)
|
||||
.forEach(x => {
|
||||
@ -301,6 +316,17 @@ export class TableComponent<T> implements OnInit {
|
||||
changes[key] !== ''
|
||||
)
|
||||
.map(key => {
|
||||
if (key === 'fuzzy') {
|
||||
if (!this.fuzzyFilter) return {};
|
||||
return {
|
||||
fuzzy: {
|
||||
term: changes[key],
|
||||
fields: this.fuzzyFilter?.columns,
|
||||
threshold: this.fuzzyFilter?.threshold,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const column = this.columns.find(x => x.name === key);
|
||||
if (!column || !column.filterable) {
|
||||
return {};
|
||||
|
@ -22,6 +22,11 @@ export interface TableColumn<T> {
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export interface TableColumnFuzzyFilter {
|
||||
columns: string[];
|
||||
threshold?: number;
|
||||
}
|
||||
|
||||
export interface ResolvedTableColumn<T> {
|
||||
value: TableColumnValue<T>;
|
||||
data: T;
|
||||
|
@ -15,8 +15,8 @@ export class ConfigService {
|
||||
imprintURL: '',
|
||||
themes: [
|
||||
{
|
||||
label: 'Maxlan',
|
||||
name: 'maxlan',
|
||||
label: 'Open-redirect',
|
||||
name: 'open-redirect',
|
||||
},
|
||||
],
|
||||
loadingScreen: {
|
||||
@ -51,8 +51,8 @@ export class ConfigService {
|
||||
this.settings = settings;
|
||||
if (this.settings.themes.length === 0) {
|
||||
this.settings.themes.push({
|
||||
label: 'Maxlan',
|
||||
name: 'maxlan',
|
||||
label: 'Open-redirect',
|
||||
name: 'open-redirect',
|
||||
});
|
||||
}
|
||||
resolve();
|
||||
|
@ -29,6 +29,10 @@ export class GuiService {
|
||||
this.sidebarService.hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.theme$.subscribe(theme => {
|
||||
console.warn('theme changed', theme);
|
||||
});
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
|
Loading…
Reference in New Issue
Block a user