fad
This commit is contained in:
1 parent
2a29dc9195
commit
43204e8441
78 files changed
+904
-1235
No files matched your search
+13
-13
@@ -59,15 +59,15 @@
|
||||
line: "{{ terminus_key }}"
|
||||
when: terminuskey_test.stdout == "0"
|
||||
|
||||
- name: Check rohan public key
|
||||
shell: "grep -c \"{{ rohan_key }}\" /home/{{ user }}/.ssh/authorized_keys || true"
|
||||
register: rohankey_test
|
||||
- name: Check clair public key
|
||||
shell: "grep -c \"{{ clair_key }}\" /home/{{ user }}/.ssh/authorized_keys || true"
|
||||
register: clairkey_test
|
||||
|
||||
- name: Add Rohan public key
|
||||
- name: Add clair public key
|
||||
lineinfile:
|
||||
dest: "/home/{{ user }}/.ssh/authorized_keys"
|
||||
line: "{{ rohan_key }}"
|
||||
when: rohankey_test.stdout == "0"
|
||||
line: "{{ clair_key }}"
|
||||
when: clairkey_test.stdout == "0"
|
||||
|
||||
- name: Check Anjin public key
|
||||
shell: "grep -c \"{{ anjin_key }}\" /home/{{ user }}/.ssh/authorized_keys || true"
|
||||
@@ -146,21 +146,21 @@
|
||||
groups: lp
|
||||
append: yes
|
||||
|
||||
- name: install rohan-redis-tunnel systemd unit file
|
||||
- name: install nats-tunnel systemd unit file
|
||||
template:
|
||||
src: "files/rohan-redis-tunnel.service"
|
||||
dest: "/etc/systemd/system/rohan-redis-tunnel.service"
|
||||
src: "files/nats-tunnel.service"
|
||||
dest: "/etc/systemd/system/nats-tunnel.service"
|
||||
|
||||
- name: enable service rohan-redis-tunnel and ensure it is not masked
|
||||
- name: enable service nats-tunnel and ensure it is not masked
|
||||
systemd:
|
||||
name: rohan-redis-tunnel
|
||||
name: nats-tunnel
|
||||
enabled: yes
|
||||
masked: no
|
||||
|
||||
- name: Make sure rohan-redis-tunnel service is running
|
||||
- name: Make sure nats-tunnel service is running
|
||||
systemd:
|
||||
state: started
|
||||
name: rohan-redis-tunnel
|
||||
name: nats-tunnel
|
||||
|
||||
- name: install leardal systemd unit file
|
||||
template:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Host rohan rohan.tanshu.com
|
||||
HostName rohan.tanshu.com
|
||||
Host {{ server }} {{ server }}.tanshu.com
|
||||
HostName {{ server }}.tanshu.com
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
User tanshu
|
||||
@@ -1,9 +1,9 @@
|
||||
[Unit]
|
||||
Description=Frank container service
|
||||
After=docker.service rohan-redis-tunnel.service
|
||||
Wants=network-online.target docker.socket rohan-redis-tunnel.service
|
||||
Requires=docker.socket rohan-redis-tunnel.service
|
||||
PartOf=rohan-redis-tunnel.service
|
||||
After=docker.service {{ server }}-redis-tunnel.service
|
||||
Wants=network-online.target docker.socket {{ server }}-redis-tunnel.service
|
||||
Requires=docker.socket {{ server }}-redis-tunnel.service
|
||||
PartOf={{ server }}-redis-tunnel.service
|
||||
|
||||
[Service]
|
||||
Restart=always
|
||||
|
||||
@@ -6,7 +6,7 @@ After=network.target
|
||||
Type=simple
|
||||
User={{ user }}
|
||||
|
||||
ExecStart=/usr/bin/ssh -NTg -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -o ServerAliveCountMax=3 -o StrictHostKeyChecking=no -i /home/{{ user }}/.ssh/id_ed25519 -R {{ ssh_port }}:localhost:22 tanshu@rohan.tanshu.com
|
||||
ExecStart=/usr/bin/ssh -NTg -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -o ServerAliveCountMax=3 -o StrictHostKeyChecking=no -i /home/{{ user }}/.ssh/id_ed25519 -R {{ ssh_port }}:localhost:22 tanshu@{{ server }}.tanshu.com
|
||||
|
||||
# Restart every >2 seconds to avoid StartLimitInterval failure
|
||||
Restart=always
|
||||
|
||||
@@ -5,7 +5,7 @@ After=network.target
|
||||
[Service]
|
||||
User={{ user }}
|
||||
|
||||
ExecStart=/usr/bin/ssh -NT -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -o ServerAliveCountMax=2 -o StrictHostKeyChecking=no -i /home/{{ user }}/.ssh/id_ed25519 -L 6379:localhost:6379 tanshu@rohan.tanshu.com
|
||||
ExecStart=/usr/bin/ssh -NT -o ServerAliveInterval=30 -o ExitOnForwardFailure=yes -o ServerAliveCountMax=2 -o StrictHostKeyChecking=no -i /home/{{ user }}/.ssh/id_ed25519 -L 4222:localhost:4222 tanshu@{{ server }}.tanshu.com
|
||||
|
||||
# Restart every >2 seconds to avoid StartLimitInterval failure
|
||||
RestartSec=5
|
||||
@@ -1,39 +0,0 @@
|
||||
server {
|
||||
|
||||
listen 80;
|
||||
server_name {{ http_host }};
|
||||
|
||||
# Allow large attachments
|
||||
client_max_body_size 128M;
|
||||
|
||||
location /api {
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://localhost:{{ host_port }};
|
||||
}
|
||||
location /token {
|
||||
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://localhost:{{ host_port }};
|
||||
}
|
||||
location /refresh {
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://localhost:{{ host_port }};
|
||||
}
|
||||
location /db-image {
|
||||
proxy_set_header Host $host:$server_port;
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_pass http://localhost:{{ host_port }};
|
||||
}
|
||||
location / {
|
||||
root /var/lib/{{ host_directory }}/frontend;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ hostname: "moz2"
|
||||
ssh_port: "22542"
|
||||
user: "vari"
|
||||
terminus_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8WQHla0uCcNwmN0DUE49lbjNWa6+7A6OxrX3WEPQH0 tanshu@terminus 2023.01.08"
|
||||
rohan_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKk6HHpwGQTfciXFaKOBWg+zh09XtTYvYxFZaaW3yMln tanshu@rohan 2023.08.06"
|
||||
clair_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBeTz3weHvjJRTlBg+ovP3KfQNDvyn9mVsekAGwX1HMn tanshu@clair 2025-06-30"
|
||||
anjin_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMlvalE0JqKyjpEsGTgtf/N1d9QK2MgNFJib+e8O2h9M tanshu@anjin 2024.09.28"
|
||||
server: "clair"
|
||||
@@ -1 +1 @@
|
||||
__version__ = "14.5.0"
|
||||
__version__ = "14.5.1"
|
||||
@@ -1,6 +0,0 @@
|
||||
from arq.connections import RedisSettings
|
||||
|
||||
from .config import settings as sett
|
||||
|
||||
|
||||
settings = RedisSettings(host=sett.REDIS_HOST, port=sett.REDIS_PORT)
|
||||
@@ -18,14 +18,10 @@ class Settings(BaseSettings):
|
||||
LOG_LEVEL: str = "NOTSET"
|
||||
SQLALCHEMY_DATABASE_URI: str = ""
|
||||
|
||||
REDIS_HOST: str = "127.0.0.1"
|
||||
REDIS_PORT: int = 6379
|
||||
|
||||
ALEMBIC_LOG_LEVEL: str = "INFO"
|
||||
ALEMBIC_SQLALCHEMY_LOG_LEVEL: str = "WARN"
|
||||
|
||||
WORKER_REDIS_HOST: str = "127.0.0.1"
|
||||
WORKER_REDIS_PORT: int = 6379
|
||||
NATS_URL: str = "nats://127.0.0.1:4222"
|
||||
WORKER_QUEUE_NAME: str = "arq:queue"
|
||||
model_config = SettingsConfigDict(case_sensitive=True, env_file=".env")
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import asyncio
|
||||
|
||||
from taskiq_nats import PullBasedJetStreamBroker
|
||||
|
||||
from .config import settings
|
||||
|
||||
|
||||
broker = PullBasedJetStreamBroker(settings.NATS_URL)
|
||||
|
||||
|
||||
@broker.task(task_name="sent_to_printer")
|
||||
async def sent_to_printer(data: str, address: str, cut_code: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def enqueue_print_job_sync(printer_name: str, data: str, address: str, cut_code: str) -> None:
|
||||
async def _enqueue() -> None:
|
||||
await broker.startup()
|
||||
try:
|
||||
await (
|
||||
sent_to_printer.kicker().with_labels(queue=f"barker.print.{printer_name}").kiq(data, address, cut_code)
|
||||
)
|
||||
finally:
|
||||
await broker.shutdown()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(_enqueue())
|
||||
loop.close()
|
||||
@@ -4,7 +4,6 @@ import uvicorn
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from sqlalchemy.exc import IntegrityError, SQLAlchemyError
|
||||
from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
@@ -148,7 +147,7 @@ app.include_router(split.router, prefix="/api", tags=["voucher"])
|
||||
app.include_router(change.router, prefix="/api/voucher", tags=["voucher"])
|
||||
|
||||
app.include_router(health.router, prefix="/health", tags=["health"])
|
||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||
app.frontend("/", directory="frontend", check_dir="auto")
|
||||
|
||||
|
||||
def init() -> None:
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import asyncio
|
||||
import re
|
||||
import uuid
|
||||
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import Date, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.config import settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.bill import Bill
|
||||
from ..models.db_setting import DbSetting
|
||||
from ..models.inventory import Inventory
|
||||
@@ -49,22 +47,11 @@ def print_bill(voucher_id: uuid.UUID, db: Session) -> None:
|
||||
bill_text = design_bill(voucher, regime_id, inventories, taxes_list, db)
|
||||
bills.append(bill_text)
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
if len(bills) > 1:
|
||||
total = design_total(voucher, regimes, db)
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", total, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
enqueue_print_job_sync(printer.name, total, printer.address, printer.cut_code)
|
||||
for bill in bills:
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", bill, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, bill, printer.address, printer.cut_code)
|
||||
|
||||
|
||||
def get_inventories(voucher: Voucher) -> list[Inventory]:
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.config import settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.device import Device
|
||||
from ..models.printer import Printer
|
||||
from ..models.section_printer import SectionPrinter
|
||||
@@ -27,19 +25,8 @@ def print_cashier_report(report: CashierReport, device_id: uuid.UUID, db: Sessio
|
||||
.where(SectionPrinter.sale_category_id == None) # noqa: E711
|
||||
).scalar_one()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", summary, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", details, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, summary, printer.address, printer.cut_code)
|
||||
enqueue_print_job_sync(printer.name, details, printer.address, printer.cut_code)
|
||||
|
||||
|
||||
def design_cashier_report_summary(report: CashierReport) -> str:
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.device import Device
|
||||
from ..models.printer import Printer
|
||||
from ..models.section_printer import SectionPrinter
|
||||
@@ -23,14 +21,7 @@ def print_discount_report(report: DiscountReport, device_id: uuid.UUID, db: Sess
|
||||
.where(SectionPrinter.sale_category_id == None) # noqa: E711
|
||||
).scalar_one()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", data, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, data, printer.address, printer.cut_code)
|
||||
|
||||
|
||||
def design_discount_report(report: DiscountReport) -> str:
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import Date, func, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.config import settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.inventory import Inventory
|
||||
from ..models.kot import Kot
|
||||
from ..models.printer import Printer
|
||||
@@ -80,20 +78,9 @@ def print_kot(voucher_id: uuid.UUID, db: Session) -> None:
|
||||
if key not in my_hash:
|
||||
my_hash[key] = (printer, [])
|
||||
my_hash[key][1].append(item)
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
for key, value in my_hash.items():
|
||||
printer_id, copies = key
|
||||
printer, items = value
|
||||
for c in range(int(copies)):
|
||||
data = design_kot(voucher, kot, items, c)
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer",
|
||||
data,
|
||||
printer.address,
|
||||
printer.cut_code,
|
||||
_queue_name=f"barker:print:{printer.name}",
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, data, printer.address, printer.cut_code)
|
||||
@@ -1,14 +1,12 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.config import settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.device import Device
|
||||
from ..models.printer import Printer
|
||||
from ..models.section_printer import SectionPrinter
|
||||
@@ -25,14 +23,7 @@ def print_product_sale_report(report: ProductSaleReport, device_id: uuid.UUID, d
|
||||
.where(SectionPrinter.sale_category_id == None) # noqa: E711
|
||||
).scalar_one()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", data, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, data, printer.address, printer.cut_code)
|
||||
|
||||
|
||||
def design_product_sale_report(report: ProductSaleReport) -> str:
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from arq import ArqRedis, create_pool
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..core.arq import settings as redis_settings
|
||||
from ..core.config import settings
|
||||
from ..core.taskiq import enqueue_print_job_sync
|
||||
from ..models.device import Device
|
||||
from ..models.printer import Printer
|
||||
from ..models.section_printer import SectionPrinter
|
||||
@@ -26,14 +24,7 @@ def print_sale_report(report: SaleReport, device_id: uuid.UUID, db: Session) ->
|
||||
.where(SectionPrinter.sale_category_id == None) # noqa: E711
|
||||
).scalar_one()
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
redis: ArqRedis = loop.run_until_complete(create_pool(redis_settings))
|
||||
loop.run_until_complete(
|
||||
redis.enqueue_job(
|
||||
"sent_to_printer", data, printer.address, printer.cut_code, _queue_name=f"barker:print:{printer.name}"
|
||||
)
|
||||
)
|
||||
loop.close()
|
||||
enqueue_print_job_sync(printer.name, data, printer.address, printer.cut_code)
|
||||
|
||||
|
||||
def design_sale_report(report: SaleReport) -> str:
|
||||
|
||||
@@ -122,8 +122,8 @@ def show_term(
|
||||
q: str | None,
|
||||
p: int = 0, # Page number
|
||||
s: int = 25, # Page size
|
||||
f: str | None = None, # Sort field
|
||||
d: str | None = None, # Sort direction
|
||||
f: str = "name", # Sort field
|
||||
d: str = "asc", # Sort direction
|
||||
) -> list[schemas.Customer]:
|
||||
# Handle dynamic sorting safely
|
||||
sort_field = ALLOWED_SORT_FIELDS.get(f or "name", Customer.name) # Default to name
|
||||
@@ -139,8 +139,8 @@ def customer_list(
|
||||
filter: list[str],
|
||||
page: int | None,
|
||||
page_size: int | None,
|
||||
sort_field: InstrumentedAttribute[Any] | None,
|
||||
sort_direction: Any | None,
|
||||
sort_field: InstrumentedAttribute[Any],
|
||||
sort_direction: Any,
|
||||
db: Session,
|
||||
) -> tuple[Sequence[Customer], int]:
|
||||
# Handle dynamic sorting safely
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
[project]
|
||||
name = "barker"
|
||||
version = "14.5.0"
|
||||
version = "14.5.1"
|
||||
description = "Point of Sale for a restaurant"
|
||||
requires-python = ">=3.14"
|
||||
authors = [{ name = "tanshu", email = "git@tanshu.com" }]
|
||||
dependencies = [
|
||||
"aiohttp>=3.14.3",
|
||||
"alembic>=1.19.1",
|
||||
"arq>=0.28.0",
|
||||
"cryptography>=50.0.0",
|
||||
"fastapi[all]>=0.141.1",
|
||||
"gunicorn>=26.0.0",
|
||||
@@ -20,6 +19,8 @@ dependencies = [
|
||||
"python-dotenv>=1.2.2",
|
||||
"python-multipart>=0.0.32",
|
||||
"sqlalchemy>=2.0.51",
|
||||
"taskiq>=0.12.4",
|
||||
"taskiq-nats>=0.6.0",
|
||||
"uvicorn[standard]>=0.52.1",
|
||||
]
|
||||
|
||||
@@ -105,9 +106,7 @@ ignore = [
|
||||
"W191", # indentation contains tabs
|
||||
"B904", # Allow raising exceptions without from e, for HTTPException
|
||||
]
|
||||
fixable = [
|
||||
"ALL"
|
||||
] # Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
fixable = ["ALL"] # Allow fix for all enabled rules (when `--fix`) is provided.
|
||||
dummy-variable-rgx = "^(user|_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
|
||||
Generated
+62
-67
@@ -129,19 +129,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arq"
|
||||
version = "0.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "click" },
|
||||
{ name = "redis", extra = ["hiredis"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a4/81/7f9db65a89c29ba374000309b9dd95509500045df5c7e22f26c3731b7380/arq-0.28.0.tar.gz", hash = "sha256:a458188aefc2d7ee17d136f80d8fa8df1d6eba4ceebdead87e9f172d027dc311", size = 416141, upload-time = "2026-04-16T10:50:23.893Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/48/32/66b616976c5058d434ca2017979bfffd784888177b16b5038abcec93954a/arq-0.28.0-py3-none-any.whl", hash = "sha256:b1696bf5614d60f4172a2c0cbdc177e23ba03a5eb9acc29bd8181f4ea71fff94", size = 26061, upload-time = "2026-04-16T10:50:22.321Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ast-serialize"
|
||||
version = "0.8.0"
|
||||
@@ -244,12 +231,11 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "barker"
|
||||
version = "14.5.0"
|
||||
version = "14.5.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "alembic" },
|
||||
{ name = "arq" },
|
||||
{ name = "cryptography" },
|
||||
{ name = "fastapi", extra = ["all"] },
|
||||
{ name = "gunicorn" },
|
||||
@@ -262,6 +248,8 @@ dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "python-multipart" },
|
||||
{ name = "sqlalchemy" },
|
||||
{ name = "taskiq" },
|
||||
{ name = "taskiq-nats" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
|
||||
@@ -278,7 +266,6 @@ dev = [
|
||||
requires-dist = [
|
||||
{ name = "aiohttp", specifier = ">=3.14.3" },
|
||||
{ name = "alembic", specifier = ">=1.19.1" },
|
||||
{ name = "arq", specifier = ">=0.28.0" },
|
||||
{ name = "cryptography", specifier = ">=50.0.0" },
|
||||
{ name = "fastapi", extras = ["all"], specifier = ">=0.141.1" },
|
||||
{ name = "gunicorn", specifier = ">=26.0.0" },
|
||||
@@ -291,6 +278,8 @@ requires-dist = [
|
||||
{ name = "python-dotenv", specifier = ">=1.2.2" },
|
||||
{ name = "python-multipart", specifier = ">=0.0.32" },
|
||||
{ name = "sqlalchemy", specifier = ">=2.0.51" },
|
||||
{ name = "taskiq", specifier = ">=0.12.4" },
|
||||
{ name = "taskiq-nats", specifier = ">=0.6.0" },
|
||||
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.52.1" },
|
||||
]
|
||||
|
||||
@@ -720,40 +709,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hiredis"
|
||||
version = "3.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/65/82/d2817ce0653628e0a0cb128533f6af0dd6318a49f3f3a6a7bd1f2f2154af/hiredis-3.3.0.tar.gz", hash = "sha256:105596aad9249634361815c574351f1bd50455dc23b537c2940066c4a9dea685", size = 89048, upload-time = "2025-10-14T16:33:34.263Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/b3/b948ee76a6b2bc7e45249861646f91f29704f743b52565cf64cee9c4658b/hiredis-3.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c135bda87211f7af9e2fd4e046ab433c576cd17b69e639a0f5bb2eed5e0e71a9", size = 82105, upload-time = "2025-10-14T16:32:37.204Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/9b/4210f4ebfb3ab4ada964b8de08190f54cbac147198fb463cd3c111cc13e0/hiredis-3.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2f855c678230aed6fc29b962ce1cc67e5858a785ef3a3fd6b15dece0487a2e60", size = 46237, upload-time = "2025-10-14T16:32:38.07Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/7a/e38bfd7d04c05036b4ccc6f42b86b1032185cf6ae426e112a97551fece14/hiredis-3.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4059c78a930cbb33c391452ccce75b137d6f89e2eebf6273d75dafc5c2143c03", size = 41894, upload-time = "2025-10-14T16:32:38.929Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/d3/eae43d9609c5d9a6effef0586ee47e13a0d84b44264b688d97a75cd17ee5/hiredis-3.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:334a3f1d14c253bb092e187736c3384203bd486b244e726319bbb3f7dffa4a20", size = 170486, upload-time = "2025-10-14T16:32:40.147Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/fd/34d664554880b27741ab2916d66207357563b1639e2648685f4c84cfb755/hiredis-3.3.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd137b147235447b3d067ec952c5b9b95ca54b71837e1b38dbb2ec03b89f24fc", size = 182031, upload-time = "2025-10-14T16:32:41.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/08/a3/0c69fdde3f4155b9f7acc64ccffde46f312781469260061b3bbaa487fd34/hiredis-3.3.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8f88f4f2aceb73329ece86a1cb0794fdbc8e6d614cb5ca2d1023c9b7eb432db8", size = 180542, upload-time = "2025-10-14T16:32:42.993Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/7a/ad5da4d7bc241e57c5b0c4fe95aa75d1f2116e6e6c51577394d773216e01/hiredis-3.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:550f4d1538822fc75ebf8cf63adc396b23d4958bdbbad424521f2c0e3dfcb169", size = 172353, upload-time = "2025-10-14T16:32:43.965Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/dc/c46eace64eb047a5b31acd5e4b0dc6d2f0390a4a3f6d507442d9efa570ad/hiredis-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:54b14211fbd5930fc696f6fcd1f1f364c660970d61af065a80e48a1fa5464dd6", size = 166435, upload-time = "2025-10-14T16:32:44.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/ac/ad13a714e27883a2e4113c980c94caf46b801b810de5622c40f8d3e8335f/hiredis-3.3.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9e96f63dbc489fc86f69951e9f83dadb9582271f64f6822c47dcffa6fac7e4a", size = 177218, upload-time = "2025-10-14T16:32:45.936Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/38/268fabd85b225271fe1ba82cb4a484fcc1bf922493ff2c74b400f1a6f339/hiredis-3.3.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:106e99885d46684d62ab3ec1d6b01573cc0e0083ac295b11aaa56870b536c7ec", size = 170477, upload-time = "2025-10-14T16:32:46.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/6b/02bb8af810ea04247334ab7148acff7a61c08a8832830c6703f464be83a9/hiredis-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:087e2ef3206361281b1a658b5b4263572b6ba99465253e827796964208680459", size = 167915, upload-time = "2025-10-14T16:32:47.847Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/94/901fa817e667b2e69957626395e6dee416e31609dca738f28e6b545ca6c2/hiredis-3.3.0-cp314-cp314-win32.whl", hash = "sha256:80638ebeab1cefda9420e9fedc7920e1ec7b4f0513a6b23d58c9d13c882f8065", size = 21165, upload-time = "2025-10-14T16:32:50.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/7e/4881b9c1d0b4cdaba11bd10e600e97863f977ea9d67c5988f7ec8cd363e5/hiredis-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a68aaf9ba024f4e28cf23df9196ff4e897bd7085872f3a30644dca07fa787816", size = 22996, upload-time = "2025-10-14T16:32:51.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/b6/d7e6c17da032665a954a89c1e6ee3bd12cb51cd78c37527842b03519981d/hiredis-3.3.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f7f80442a32ce51ee5d89aeb5a84ee56189a0e0e875f1a57bbf8d462555ae48f", size = 83034, upload-time = "2025-10-14T16:32:52.395Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/6c/6751b698060cdd1b2d8427702cff367c9ed7a1705bcf3792eb5b896f149b/hiredis-3.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a1a67530da714954ed50579f4fe1ab0ddbac9c43643b1721c2cb226a50dde263", size = 46701, upload-time = "2025-10-14T16:32:53.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/8e/20a5cf2c83c7a7e08c76b9abab113f99f71cd57468a9c7909737ce6e9bf8/hiredis-3.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:616868352e47ab355559adca30f4f3859f9db895b4e7bc71e2323409a2add751", size = 42381, upload-time = "2025-10-14T16:32:54.762Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/0a/547c29c06e8c9c337d0df3eec39da0cf1aad701daf8a9658dd37f25aca66/hiredis-3.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e799b79f3150083e9702fc37e6243c0bd47a443d6eae3f3077b0b3f510d6a145", size = 180313, upload-time = "2025-10-14T16:32:55.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/8a/488de5469e3d0921a1c425045bf00e983d48b2111a90e47cf5769eaa536c/hiredis-3.3.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9ef1dfb0d2c92c3701655e2927e6bbe10c499aba632c7ea57b6392516df3864b", size = 190488, upload-time = "2025-10-14T16:32:56.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/59/8493edc3eb9ae0dbea2b2230c2041a52bc03e390b02ffa3ac0bca2af9aea/hiredis-3.3.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c290da6bc2a57e854c7da9956cd65013483ede935677e84560da3b848f253596", size = 189210, upload-time = "2025-10-14T16:32:57.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/de/8c9a653922057b32fb1e2546ecd43ef44c9aa1a7cf460c87cae507eb2bc7/hiredis-3.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd8c438d9e1728f0085bf9b3c9484d19ec31f41002311464e75b69550c32ffa8", size = 180972, upload-time = "2025-10-14T16:32:58.737Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/a3/51e6e6afaef2990986d685ca6e254ffbd191f1635a59b2d06c9e5d10c8a2/hiredis-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1bbc6b8a88bbe331e3ebf6685452cebca6dfe6d38a6d4efc5651d7e363ba28bd", size = 175315, upload-time = "2025-10-14T16:32:59.774Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/54/e436312feb97601f70f8b39263b8da5ac4a5d18305ebdfb08ad7621f6119/hiredis-3.3.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:55d8c18fe9a05496c5c04e6eccc695169d89bf358dff964bcad95696958ec05f", size = 185653, upload-time = "2025-10-14T16:33:00.749Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/a3/88e66030d066337c6c0f883a912c6d4b2d6d7173490fbbc113a6cbe414ff/hiredis-3.3.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:4ddc79afa76b805d364e202a754666cb3c4d9c85153cbfed522871ff55827838", size = 179032, upload-time = "2025-10-14T16:33:01.711Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/1f/fb7375467e9adaa371cd617c2984fefe44bdce73add4c70b8dd8cab1b33a/hiredis-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e8a4b8540581dcd1b2b25827a54cfd538e0afeaa1a0e3ca87ad7126965981cc", size = 176127, upload-time = "2025-10-14T16:33:02.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/14/0dc2b99209c400f3b8f24067273e9c3cb383d894e155830879108fb19e98/hiredis-3.3.0-cp314-cp314t-win32.whl", hash = "sha256:298593bb08487753b3afe6dc38bac2532e9bac8dcee8d992ef9977d539cc6776", size = 22024, upload-time = "2025-10-14T16:33:03.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/2f/8a0befeed8bbe142d5a6cf3b51e8cbe019c32a64a596b0ebcbc007a8f8f1/hiredis-3.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b442b6ab038a6f3b5109874d2514c4edf389d8d8b553f10f12654548808683bc", size = 23808, upload-time = "2025-10-14T16:33:04.965Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.9"
|
||||
@@ -1079,6 +1034,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nats-py"
|
||||
version = "2.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/f0/fc5e93f2b0dd14a202590ad9d30eda1955ea872039b5204357348d0f4b1e/nats_py-2.15.0.tar.gz", hash = "sha256:6622c547d9a7d2313d9c147d46c386188f4ec2c7b5c9f9a0438a4d1b55f54a93", size = 75995, upload-time = "2026-06-05T07:34:03.904Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/a8/b55606c7c621fb813c8ec78baf201d2c78bf6051091ec0c7ada572999e95/nats_py-2.15.0-py3-none-any.whl", hash = "sha256:9f8d36aa52a9926a88b8f1d70cf1fdce0ad387941479b500ee9ab3e51073cefd", size = 90334, upload-time = "2026-06-05T07:34:02.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nltk"
|
||||
version = "3.9.2"
|
||||
@@ -1258,6 +1222,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycron"
|
||||
version = "3.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8e/5d/340be12ae4a69c33102dfb6ddc1dc6e53e69b2d504fa26b5d34a472c3057/pycron-3.2.0.tar.gz", hash = "sha256:e125a28aca0295769541a40633f70b602579df48c9cb357c36c28d2628ba2b13", size = 4248, upload-time = "2025-06-05T13:24:12.636Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f0/76/caf316909f4545e7158e0e1defd8956a1da49f4af04f5d16b18c358dfeac/pycron-3.2.0-py3-none-any.whl", hash = "sha256:6d2349746270bd642b71b9f7187cf13f4d9ee2412b4710396a507b5fe4f60dac", size = 4904, upload-time = "2025-06-05T13:24:11.477Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.13.4"
|
||||
@@ -1408,23 +1381,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pyjwt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6a/cf/128b1b6d7086200c9f387bd4be9b2572a30b90745ef078bd8b235042dc9f/redis-5.3.1.tar.gz", hash = "sha256:ca49577a531ea64039b5a36db3d6cd1a0c7a60c34124d46924a45b956e8cf14c", size = 4626200, upload-time = "2025-07-25T08:06:27.778Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/26/5c5fa0e83c3621db835cfc1f1d789b37e7fa99ed54423b5f519beb931aa7/redis-5.3.1-py3-none-any.whl", hash = "sha256:dc1909bd24669cc31b5f67a039700b16ec30571096c5f1f0d9d2324bff31af97", size = 272833, upload-time = "2025-07-25T08:06:26.317Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
hiredis = [
|
||||
{ name = "hiredis" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2026.2.19"
|
||||
@@ -1679,6 +1635,45 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/06/36d260a695f383345ab5bbc3fd447249594ae2fa8dfd19c533d5ae23f46b/stevedore-5.7.0-py3-none-any.whl", hash = "sha256:fd25efbb32f1abb4c9e502f385f0018632baac11f9ee5d1b70f88cc5e22ad4ed", size = 54483, upload-time = "2026-02-20T13:27:05.561Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "taskiq"
|
||||
version = "0.12.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
{ name = "anyio" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pycron" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "taskiq-dependencies" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/68/7b3c557782af291e22b26479127f287520fdf066d4684838e1bdd146cdc0/taskiq-0.12.5.tar.gz", hash = "sha256:fbdfc949fdefe746f771195faa34769fa6dbbb109cbd41cb1d056b794f933f43", size = 407451, upload-time = "2026-08-21T14:25:50.031Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/4b/c37a3cdfaff2ff53c3ca5d5289636b42141c9964f67d16df3017a97be9c6/taskiq-0.12.5-py3-none-any.whl", hash = "sha256:3937969fcb5290d1a007e068e7af45d0a44f885e3bfe4bf1860f7e81794930f8", size = 94204, upload-time = "2026-08-21T14:25:48.948Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "taskiq-dependencies"
|
||||
version = "1.5.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/90/47a627696e53bfdcacabc3e8c05b73bf1424685bcb5f17209cb8b12da1bf/taskiq_dependencies-1.5.7.tar.gz", hash = "sha256:0d3b240872ef152b719153b9526d866d2be978aeeaea6600e878414babc2dcb4", size = 14875, upload-time = "2025-02-26T22:07:39.876Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/99/6d/4a012f2de002c2e93273f5e7d3e3feea02f7fdbb7b75ca2ca1dd10703091/taskiq_dependencies-1.5.7-py3-none-any.whl", hash = "sha256:6fcee5d159bdb035ef915d4d848826169b6f06fe57cc2297a39b62ea3e76036f", size = 13801, upload-time = "2025-02-26T22:07:38.622Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "taskiq-nats"
|
||||
version = "0.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nats-py" },
|
||||
{ name = "taskiq" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/db/4a/35a179f3bb4e41048fe68401cd3fae91c371e9b877f9266b3d8e80fb13e6/taskiq_nats-0.6.0.tar.gz", hash = "sha256:3335fc416e9872a6ed3dbb0af3b51d1d61f23aaa9983c91337c16534abe71c72", size = 7611, upload-time = "2025-11-25T11:06:03.02Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/b0/6b564579476b26c1e881e051e256c62ba3eb4a0f522fb536531e35f150d2/taskiq_nats-0.6.0-py3-none-any.whl", hash = "sha256:3d08288cbcca1da500663a45a3c33f03160721380e61cb2351e962f4805cadba", size = 8930, upload-time = "2025-11-25T11:06:02.048Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "9.1.4"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bookie",
|
||||
"version": "14.5.0",
|
||||
"version": "14.5.1",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
ACCESS_TOKEN_REFRESH_MINUTES: 10, // refresh token 10 minutes before expiry
|
||||
version: '14.5.0',
|
||||
version: '14.5.1',
|
||||
};
|
||||
|
||||
export const dateFormat = {
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Username</mat-label>
|
||||
<input matInput #nameElement [formField]="form.username" />
|
||||
<input matInput [formField]="form.username" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Password</mat-label>
|
||||
<input matInput [formField]="form.password" [type]="hide ? 'password' : 'text'" (keyup.enter)="login()" />
|
||||
<mat-icon matSuffix (click)="hide = !hide">{{ hide ? 'visibility' : 'visibility_off' }}</mat-icon>
|
||||
<input matInput [formField]="form.password" [type]="hide() ? 'password' : 'text'" (keyup.enter)="login()" />
|
||||
<mat-icon matSuffix (click)="hide.set(!hide())">{{ hide() ? 'visibility' : 'visibility_off' }}</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (unregisteredDevice()) {
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
inject,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { Component, afterNextRender, inject, input, signal } from '@angular/core';
|
||||
import { form, FormField, required } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDividerModule } from '@angular/material/divider';
|
||||
@@ -15,7 +6,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { CookieService } from '../../shared/cookie.service';
|
||||
import { AuthService } from '../auth.service';
|
||||
@@ -31,14 +22,15 @@ export interface LoginFormData {
|
||||
styleUrls: ['./login.component.css'],
|
||||
imports: [MatButtonModule, MatDividerModule, MatFormFieldModule, MatIconModule, MatInputModule, FormField],
|
||||
})
|
||||
export class LoginComponent implements OnInit, AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
private route = inject(ActivatedRoute);
|
||||
export class LoginComponent {
|
||||
private auth = inject(AuthService);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private cs = inject(CookieService);
|
||||
private cd = inject(ChangeDetectorRef);
|
||||
|
||||
returnUrl = input<string | null | undefined, string>('/', {
|
||||
transform: (v: string | null | undefined): string => v ?? '/',
|
||||
});
|
||||
|
||||
formModel = signal<LoginFormData>({
|
||||
username: '',
|
||||
@@ -50,28 +42,14 @@ export class LoginComponent implements OnInit, AfterViewInit {
|
||||
required(schemaPath.password);
|
||||
});
|
||||
|
||||
hide: boolean;
|
||||
unregisteredDevice = signal<boolean | undefined>(undefined);
|
||||
deviceName = signal<string | undefined>(undefined);
|
||||
private returnUrl: string;
|
||||
hide = signal<boolean>(true);
|
||||
unregisteredDevice = signal<boolean>(false);
|
||||
deviceName = signal<string>('');
|
||||
|
||||
constructor() {
|
||||
this.hide = true;
|
||||
this.unregisteredDevice.set(false);
|
||||
this.deviceName.set('');
|
||||
this.returnUrl = '';
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] ?? '/';
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
login(): void {
|
||||
@@ -80,13 +58,12 @@ export class LoginComponent implements OnInit, AfterViewInit {
|
||||
const password = formModel.password ?? '';
|
||||
this.auth.login(username.trim(), password.trim()).subscribe({
|
||||
next: () => {
|
||||
this.router.navigateByUrl(this.returnUrl);
|
||||
this.router.navigateByUrl(this.returnUrl() ?? '/');
|
||||
},
|
||||
error: (error) => {
|
||||
if (error.status === 401 && error.error.detail === 'Device is not registered') {
|
||||
this.unregisteredDevice.set(true);
|
||||
this.deviceName.set(this.cs.getCookie('device'));
|
||||
this.cd.detectChanges();
|
||||
}
|
||||
this.snackBar.open(error.error.detail, 'Error');
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@@ -7,10 +7,10 @@ import { AuthService } from '../auth.service';
|
||||
template: '',
|
||||
standalone: true,
|
||||
})
|
||||
export class LogoutComponent implements OnInit {
|
||||
export class LogoutComponent {
|
||||
private auth = inject(AuthService);
|
||||
|
||||
ngOnInit() {
|
||||
constructor() {
|
||||
this.auth.logout();
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<mat-form-field class="w-full">
|
||||
<mat-label>Item</mat-label>
|
||||
<input matInput type="text" [formField]="form.product" [matAutocomplete]="autoP" />
|
||||
<mat-autocomplete #autoP="matAutocomplete" [displayWith]="displayFn" (optionSelected)="productSelected($event)">
|
||||
<mat-autocomplete #autoP="matAutocomplete" [displayWith]="displayFn">
|
||||
@for (p of products(); track p) {
|
||||
<mat-option [value]="p">{{ p.name }}</mat-option>
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnInit, inject, signal, computed, effect, debounced } from '@angular/core';
|
||||
import { Component, inject, computed, debounced, linkedSignal } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
@@ -13,7 +13,7 @@ import { ProductService } from '../../product/product.service';
|
||||
import { BundleItem } from '../bundle';
|
||||
|
||||
export interface BundleDetailDialogFormData {
|
||||
product: ProductQuery | string;
|
||||
product: BundleItem | ProductQuery | string;
|
||||
quantity: number;
|
||||
salePrice: number;
|
||||
printInBill: boolean;
|
||||
@@ -35,19 +35,20 @@ export interface BundleDetailDialogFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class BundleDetailDialogComponent implements OnInit {
|
||||
export class BundleDetailDialogComponent {
|
||||
private readonly dialogRef = inject<MatDialogRef<BundleDetailDialogComponent>>(MatDialogRef);
|
||||
private readonly productSer = inject(ProductService);
|
||||
|
||||
data = inject<{ item: BundleItem }>(MAT_DIALOG_DATA);
|
||||
|
||||
selectedProduct = signal<ProductQuery | null>(null);
|
||||
|
||||
formModel = signal<BundleDetailDialogFormData>({
|
||||
product: '',
|
||||
quantity: 1,
|
||||
salePrice: 0,
|
||||
printInBill: true,
|
||||
formModel = linkedSignal<BundleItem, BundleDetailDialogFormData>({
|
||||
source: () => this.data.item,
|
||||
computation: (item) => ({
|
||||
product: item,
|
||||
quantity: Number(item.quantity ?? 1),
|
||||
salePrice: Number(item.salePrice ?? 0),
|
||||
printInBill: item.printInBill ?? false,
|
||||
}),
|
||||
});
|
||||
|
||||
form = form(this.formModel);
|
||||
@@ -59,62 +60,35 @@ export class BundleDetailDialogComponent implements OnInit {
|
||||
|
||||
debouncedProduct = debounced(this.productSignal, 150);
|
||||
|
||||
productsResource = this.productSer.query(computed(() => this.debouncedProduct.value()));
|
||||
productsResource = this.productSer.query(this.debouncedProduct.value);
|
||||
products = computed(() => {
|
||||
if (!this.debouncedProduct.value()) return [];
|
||||
return this.productsResource.value() ?? [];
|
||||
});
|
||||
|
||||
constructor() {
|
||||
// typing after selecting invalidates prior selection
|
||||
effect(() => {
|
||||
const x = this.formModel().product;
|
||||
if (typeof x === 'string') this.selectedProduct.set(null);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// preload display text using existing name
|
||||
this.formModel.set({
|
||||
product: this.data.item.name ?? '',
|
||||
quantity: Number(this.data.item.quantity ?? 1),
|
||||
salePrice: Number(this.data.item.salePrice ?? 0),
|
||||
printInBill: this.data.item.printInBill,
|
||||
});
|
||||
|
||||
this.selectedProduct.set(null);
|
||||
}
|
||||
|
||||
displayFn(product?: ProductQuery | string): string {
|
||||
return !product ? '' : typeof product === 'string' ? product : product.name;
|
||||
}
|
||||
|
||||
productSelected(event: MatAutocompleteSelectedEvent): void {
|
||||
const p = event.option.value as ProductQuery;
|
||||
this.selectedProduct.set(p);
|
||||
}
|
||||
|
||||
accept(): void {
|
||||
const v = this.formModel();
|
||||
const formValue = this.form().value();
|
||||
|
||||
const quantity = Number(v.quantity ?? 0);
|
||||
const quantity = Number(formValue.quantity ?? 0);
|
||||
if (Number.isNaN(quantity) || quantity <= 0) return;
|
||||
|
||||
const salePrice = Number(v.salePrice ?? 0);
|
||||
const salePrice = Number(formValue.salePrice ?? 0);
|
||||
if (Number.isNaN(salePrice) || salePrice < 0) return;
|
||||
|
||||
// If a new product was selected, update itemId + name
|
||||
if (this.selectedProduct()?.id) {
|
||||
this.data.item.itemId = this.selectedProduct()?.id ?? '';
|
||||
this.data.item.name = this.selectedProduct()?.name ?? '';
|
||||
} else {
|
||||
// If no selection, don't allow changing SKU silently
|
||||
// but still allow quantity/price edit
|
||||
const product = typeof formValue.product === 'string' ? null : formValue.product;
|
||||
if (!product || !product.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data.item.itemId = product.id ?? '';
|
||||
this.data.item.name = product.name ?? '';
|
||||
this.data.item.quantity = quantity;
|
||||
this.data.item.salePrice = salePrice;
|
||||
this.data.item.printInBill = v.printInBill || false;
|
||||
this.data.item.printInBill = formValue.printInBill ?? false;
|
||||
|
||||
this.dialogRef.close(this.data.item);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="basis-3-4">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #name [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="basis-1-4">
|
||||
@@ -65,7 +65,7 @@
|
||||
[matAutocomplete]="autoItem"
|
||||
[value]="displayFn(formModel().addRow.itemId)"
|
||||
/>
|
||||
<mat-autocomplete #autoItem="matAutocomplete" [displayWith]="displayFn" (optionSelected)="bundleSelected($event)">
|
||||
<mat-autocomplete #autoItem="matAutocomplete" [displayWith]="displayFn">
|
||||
@for (p of itemProducts(); track p) {
|
||||
<mat-option [value]="p">{{ p.name }}</mat-option>
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
</form>
|
||||
|
||||
<div class="row-container wrapped">
|
||||
<mat-table [dataSource]="dataSource()" aria-label="Bundle Items" class="flex-auto">
|
||||
<mat-table [dataSource]="formModel().items" aria-label="Bundle Items" class="flex-auto">
|
||||
<!-- Name Column (name is "name (units)") -->
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef>Item</mat-header-cell>
|
||||
@@ -143,7 +143,7 @@
|
||||
|
||||
<div class="row-container">
|
||||
<button type="button" mat-raised-button color="primary" (click)="save()">Save</button>
|
||||
@if (!!item.id) {
|
||||
@if (!!item().id) {
|
||||
<button type="button" mat-raised-button color="warn" (click)="confirmDelete()">Delete</button>
|
||||
}
|
||||
</div>
|
||||
@@ -1,19 +1,7 @@
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
inject,
|
||||
computed,
|
||||
signal,
|
||||
effect,
|
||||
debounced,
|
||||
linkedSignal,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { Component, inject, computed, effect, debounced, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -24,7 +12,7 @@ import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { MenuCategory } from '../../core/menu-category';
|
||||
import { ProductQuery } from '../../core/product-query';
|
||||
@@ -41,8 +29,8 @@ export interface BundleDetailFormData {
|
||||
name: string;
|
||||
units: string;
|
||||
salePrice: number;
|
||||
menuCategory: string;
|
||||
saleCategory: string;
|
||||
menuCategory: MenuCategory | null;
|
||||
saleCategory: SaleCategory | null;
|
||||
hasHappyHour: boolean;
|
||||
isNotAvailable: boolean;
|
||||
addRow: {
|
||||
@@ -51,6 +39,7 @@ export interface BundleDetailFormData {
|
||||
salePrice: number;
|
||||
printInBill: boolean;
|
||||
};
|
||||
items: BundleItem[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -71,53 +60,34 @@ export interface BundleDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class BundleDetailComponent implements AfterViewInit {
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
export class BundleDetailComponent {
|
||||
private menuCategoryService = inject(MenuCategoryService);
|
||||
private saleCategoryService = inject(SaleCategoryService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private ser = inject(BundleService);
|
||||
private productSer = inject(ProductService);
|
||||
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||
|
||||
saleCategoriesResource = this.saleCategoryService.list();
|
||||
|
||||
menuCategoriesResource = this.menuCategoryService.list(undefined);
|
||||
|
||||
menuCategories = computed(() => this.menuCategoriesResource.value() ?? []);
|
||||
saleCategoriesResource = this.saleCategoryService.list();
|
||||
saleCategories = computed(() => this.saleCategoriesResource.value() ?? []);
|
||||
id = input(null, { transform: (v: string | null | undefined) => v ?? null });
|
||||
itemResource = this.ser.get(this.id);
|
||||
|
||||
item = computed(() => this.itemResource.value() ?? new Bundle());
|
||||
formModel = linkedSignal({
|
||||
source: () => this.menuCategoriesResource.value(),
|
||||
computation: (menuCategoriesVal): BundleDetailFormData => {
|
||||
if (menuCategoriesVal) {
|
||||
return {
|
||||
name: this.item.name ?? '',
|
||||
units: this.item.units ?? '',
|
||||
salePrice: Number(this.item.salePrice ?? 0),
|
||||
menuCategory: this.item.menuCategory?.id ?? '',
|
||||
saleCategory: this.item.saleCategory?.id ?? '',
|
||||
hasHappyHour: this.item.hasHappyHour ?? false,
|
||||
isNotAvailable: this.item.isNotAvailable ?? false,
|
||||
|
||||
addRow: {
|
||||
itemId: '',
|
||||
quantity: 1,
|
||||
salePrice: 0,
|
||||
printInBill: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
source: this.item,
|
||||
computation: (itemVal): BundleDetailFormData => {
|
||||
const items = [...(itemVal.items ?? [])];
|
||||
return {
|
||||
name: '',
|
||||
units: '',
|
||||
salePrice: 0,
|
||||
menuCategory: '',
|
||||
saleCategory: '',
|
||||
hasHappyHour: false,
|
||||
isNotAvailable: false,
|
||||
name: itemVal.name ?? '',
|
||||
units: itemVal.units ?? '',
|
||||
salePrice: Number(itemVal.salePrice ?? 0),
|
||||
menuCategory: itemVal.menuCategory ?? null,
|
||||
saleCategory: itemVal.saleCategory ?? null,
|
||||
hasHappyHour: itemVal.hasHappyHour ?? false,
|
||||
isNotAvailable: itemVal.isNotAvailable ?? false,
|
||||
items,
|
||||
addRow: {
|
||||
itemId: '',
|
||||
quantity: 1,
|
||||
@@ -130,25 +100,15 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
menuCategories = signal<MenuCategory[]>([]);
|
||||
saleCategories = signal<SaleCategory[]>([]);
|
||||
items = signal<BundleItem[]>([]);
|
||||
dataSource = computed(() => this.items());
|
||||
|
||||
item: Bundle = new Bundle();
|
||||
|
||||
itemProduct = signal<ProductQuery | null>(null);
|
||||
|
||||
itemIdSignal = computed(() => {
|
||||
const x = this.formModel().addRow?.itemId;
|
||||
return typeof x === 'string' ? x.trim() : '';
|
||||
private itemProductSearch = computed(() => {
|
||||
const v = this.form.addRow.itemId().value();
|
||||
return typeof v === 'string' ? v : null;
|
||||
});
|
||||
|
||||
debouncedItemId = debounced(this.itemIdSignal, 150);
|
||||
debouncedItemProduct = debounced(this.itemProductSearch, 150);
|
||||
|
||||
itemProductsResource = this.productSer.query(computed(() => this.debouncedItemId.value()));
|
||||
itemProductsResource = this.productSer.query(this.debouncedItemProduct.value);
|
||||
itemProducts = computed(() => {
|
||||
if (!this.debouncedItemId.value()) return [];
|
||||
return this.itemProductsResource.value() ?? [];
|
||||
});
|
||||
|
||||
@@ -156,25 +116,26 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const x = this.formModel().addRow?.itemId;
|
||||
if (typeof x === 'string') {
|
||||
this.itemProduct.set(null);
|
||||
const v = this.form.addRow.itemId().value();
|
||||
if (typeof v === 'object' && v !== null) {
|
||||
this.formModel.update((f) => ({
|
||||
...f,
|
||||
addRow: {
|
||||
...f.addRow,
|
||||
salePrice: v.price ?? 0,
|
||||
},
|
||||
}));
|
||||
}
|
||||
});
|
||||
effect(() => {
|
||||
const menuCategoriesVal = this.menuCategoriesResource.value();
|
||||
if (menuCategoriesVal) this.menuCategories.set(menuCategoriesVal);
|
||||
const saleCategoriesVal = this.saleCategoriesResource.value();
|
||||
if (saleCategoriesVal) this.saleCategories.set(saleCategoriesVal);
|
||||
const itemVal = this.itemResource.value();
|
||||
if (itemVal) {
|
||||
this.item = itemVal;
|
||||
|
||||
this.itemProduct.set(null);
|
||||
|
||||
this.items.set(this.item.items ?? []);
|
||||
this.recalcBundleSalePrice();
|
||||
}
|
||||
const items = this.form.items().value();
|
||||
this.formModel.update((f) => ({
|
||||
...f,
|
||||
salePrice: Number(items.reduce((sum, x) => sum + x.quantity * x.salePrice, 0).toFixed(2)),
|
||||
}));
|
||||
});
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -182,40 +143,6 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
return !product ? '' : typeof product === 'string' ? product : product.name;
|
||||
}
|
||||
|
||||
bundleSelected(event: MatAutocompleteSelectedEvent): void {
|
||||
const product = event.option.value as ProductQuery;
|
||||
this.itemProduct.set(product);
|
||||
this.formModel.update((f) => ({
|
||||
...f,
|
||||
addRow: {
|
||||
...f.addRow,
|
||||
salePrice: product.price ?? 0,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
private recalcBundleSalePrice(): void {
|
||||
const total = (this.item.items ?? []).reduce((sum, x) => {
|
||||
const qty = Number(x.quantity ?? 0);
|
||||
const price = Number(x.salePrice ?? 0);
|
||||
if (Number.isNaN(qty) || Number.isNaN(price)) return sum;
|
||||
return sum + qty * price;
|
||||
}, 0);
|
||||
|
||||
// keep model + form in sync
|
||||
this.item.salePrice = Number(total.toFixed(2));
|
||||
|
||||
this.formModel.update((f) => ({ ...f, salePrice: this.item.salePrice }));
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
resetAddRow() {
|
||||
this.formModel.update((f) => ({
|
||||
...f,
|
||||
@@ -226,47 +153,43 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
printInBill: true,
|
||||
},
|
||||
}));
|
||||
this.itemProduct.set(null);
|
||||
}
|
||||
|
||||
addRow() {
|
||||
const v = this.formModel().addRow;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const formValue = this.form().value().addRow;
|
||||
if (!formValue) return;
|
||||
const itemProduct = formValue.itemId as ProductQuery;
|
||||
|
||||
if (!this.itemProduct()?.id) {
|
||||
if (!itemProduct || typeof itemProduct === 'string') {
|
||||
this.snackBar.open('Please select a product', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
const quantity = Number(v.quantity ?? 0);
|
||||
const quantity = Number(formValue.quantity ?? 0);
|
||||
if (Number.isNaN(quantity) || quantity <= 0) {
|
||||
this.snackBar.open('Quantity has to be > 0', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
const salePrice = Number(v.salePrice ?? 0);
|
||||
const salePrice = Number(formValue.salePrice ?? 0);
|
||||
if (Number.isNaN(salePrice) || salePrice < 0) {
|
||||
this.snackBar.open('Sale Price has to be >= 0', 'Error');
|
||||
return;
|
||||
}
|
||||
|
||||
// name is expected from backend as "name (units)" on GET;
|
||||
// for new rows we keep it blank (or you can set placeholder)
|
||||
const bi = new BundleItem({
|
||||
itemId: this.itemProduct()?.id,
|
||||
name: this.itemProduct()?.name ?? '',
|
||||
itemId: itemProduct.id,
|
||||
name: itemProduct.name ?? '',
|
||||
quantity,
|
||||
salePrice,
|
||||
printInBill: v.printInBill || false,
|
||||
printInBill: formValue.printInBill || false,
|
||||
});
|
||||
|
||||
this.item.items.push(bi);
|
||||
this.items.set(this.item.items);
|
||||
this.formModel.update((m) => ({
|
||||
...m,
|
||||
items: [...m.items, bi],
|
||||
}));
|
||||
this.resetAddRow();
|
||||
|
||||
this.recalcBundleSalePrice();
|
||||
}
|
||||
|
||||
editRow(row: BundleItem) {
|
||||
@@ -281,25 +204,28 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
Object.assign(row, result as BundleItem);
|
||||
this.items.set(this.item.items);
|
||||
const updated = result as BundleItem;
|
||||
this.formModel.update((m) => ({
|
||||
...m,
|
||||
items: m.items.map((item) => (item === row ? Object.assign(new BundleItem(item), updated) : item)),
|
||||
}));
|
||||
this.resetAddRow();
|
||||
this.recalcBundleSalePrice();
|
||||
});
|
||||
}
|
||||
|
||||
deleteRow(row: BundleItem) {
|
||||
this.item.items.splice(this.item.items.indexOf(row), 1);
|
||||
this.items.set(this.item.items);
|
||||
this.recalcBundleSalePrice();
|
||||
this.formModel.update((m) => ({
|
||||
...m,
|
||||
items: m.items.filter((item) => item !== row),
|
||||
}));
|
||||
}
|
||||
|
||||
save() {
|
||||
if (!this.item.items || this.item.items.length === 0) {
|
||||
this.snackBar.open('Bundle must contain at least one item', 'Error');
|
||||
const item = this.getItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
this.ser.saveOrUpdate(this.getItem()).subscribe({
|
||||
this.ser.saveOrUpdate(item).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('', 'Success');
|
||||
this.router.navigateByUrl('/bundles');
|
||||
@@ -311,7 +237,7 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.ser.delete(this.item.id as string).subscribe({
|
||||
this.ser.delete(this.item().id as string).subscribe({
|
||||
next: () => {
|
||||
this.snackBar.open('', 'Success');
|
||||
this.router.navigateByUrl('/bundles');
|
||||
@@ -335,47 +261,33 @@ export class BundleDetailComponent implements AfterViewInit {
|
||||
});
|
||||
}
|
||||
|
||||
getItem(): Bundle {
|
||||
getItem(): Bundle | null {
|
||||
const v = this.formModel();
|
||||
|
||||
this.item.name = v.name ?? '';
|
||||
this.item.units = v.units ?? '';
|
||||
this.item.salePrice = Number(v.salePrice ?? 0);
|
||||
this.item.hasHappyHour = v.hasHappyHour ?? false;
|
||||
this.item.isNotAvailable = v.isNotAvailable ?? false;
|
||||
this.item.sortOrder = this.item.sortOrder ?? 0;
|
||||
|
||||
// menu category
|
||||
const menuCategoryId = v.menuCategory ?? '';
|
||||
if (!menuCategoryId) {
|
||||
// keep it as-is; backend will 422 anyway, but we can show UI error too
|
||||
if (!v.menuCategory) {
|
||||
this.snackBar.open('Menu Category is required', 'Error');
|
||||
return this.item;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.item.menuCategory === null || this.item.menuCategory === undefined) {
|
||||
this.item.menuCategory = new MenuCategory();
|
||||
}
|
||||
this.item.menuCategory.id = menuCategoryId;
|
||||
|
||||
// sale category
|
||||
const saleCategoryId = v.saleCategory ?? '';
|
||||
if (!saleCategoryId) {
|
||||
// keep it as-is; backend will 422 anyway, but we can show UI error too
|
||||
if (!v.saleCategory) {
|
||||
this.snackBar.open('Menu Category is required', 'Error');
|
||||
return this.item;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.item.saleCategory === null || this.item.saleCategory === undefined) {
|
||||
this.item.saleCategory = new SaleCategory();
|
||||
if (!v.items.length) {
|
||||
this.snackBar.open('Bundle must contain at least one item', 'Error');
|
||||
return null;
|
||||
}
|
||||
this.item.saleCategory.id = saleCategoryId;
|
||||
|
||||
// ensure items array exists
|
||||
if (!this.item.items) {
|
||||
this.item.items = [];
|
||||
}
|
||||
|
||||
return this.item;
|
||||
return new Bundle({
|
||||
...this.item(),
|
||||
name: v.name ?? '',
|
||||
units: v.units ?? '',
|
||||
salePrice: Number(v.salePrice ?? 0),
|
||||
hasHappyHour: v.hasHappyHour ?? false,
|
||||
isNotAvailable: v.isNotAvailable ?? false,
|
||||
sortOrder: this.item().sortOrder ?? 0,
|
||||
menuCategory: v.menuCategory,
|
||||
saleCategory: v.saleCategory,
|
||||
items: v.items ?? [],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PercentPipe } from '@angular/common';
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -39,8 +39,7 @@ export interface CustomerDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class CustomerDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class CustomerDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
@@ -68,12 +67,10 @@ export class CustomerDetailComponent implements AfterViewInit {
|
||||
|
||||
hide = true;
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Filter</mat-label>
|
||||
<input type="text" matInput #filterElement [formField]="form.filter" autocomplete="off" />
|
||||
<input type="text" matInput [formField]="form.filter" autocomplete="off" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
<mat-table #table [dataSource]="dataSource().items" matSort aria-label="Elements">
|
||||
<mat-table #table [dataSource]="dataSource().items" matSort (matSortChange)="sortData($event)" aria-label="Elements">
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
|
||||
|
||||
@@ -1,21 +1,12 @@
|
||||
/* eslint-disable @angular-eslint/no-input-rename */
|
||||
import { PercentPipe } from '@angular/common';
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
inject,
|
||||
signal,
|
||||
effect,
|
||||
computed,
|
||||
debounced,
|
||||
} from '@angular/core';
|
||||
import { Component, inject, signal, effect, computed, debounced, afterNextRender, input } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
||||
import { MatSortModule, Sort } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
|
||||
@@ -42,13 +33,23 @@ export interface CustomerListFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class CustomerListComponent implements AfterViewInit {
|
||||
@ViewChild('filterElement', { static: true }) filterElement!: ElementRef<HTMLInputElement>;
|
||||
@ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort!: MatSort;
|
||||
export class CustomerListComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private customerService = inject(CustomerService);
|
||||
q = input('', { transform: (v: string | null | undefined) => v ?? '' });
|
||||
pageSize = input(50, {
|
||||
transform: (v: string | number | null | undefined): number => (v ? Number(v) : 50),
|
||||
alias: 's',
|
||||
});
|
||||
|
||||
pageIndex = input(0, {
|
||||
transform: (v: string | number | null | undefined): number => (v ? Number(v) : 0),
|
||||
alias: 'i',
|
||||
});
|
||||
|
||||
sortInput = input<string>('name', { alias: 'f' });
|
||||
direction = input<string>('asc', { alias: 'd' });
|
||||
|
||||
formModel = signal<CustomerListFormData>({
|
||||
filter: this.route.snapshot.queryParamMap.get('q') ?? '',
|
||||
@@ -56,23 +57,10 @@ export class CustomerListComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
filterSignal = computed(() => this.formModel().filter);
|
||||
filterSignal = computed(() => this.formModel().filter ?? null);
|
||||
debouncedFilter = debounced(this.filterSignal, 150);
|
||||
|
||||
pageIndex = signal(+(this.route.snapshot.queryParamMap.get('p') ?? '0'));
|
||||
pageSize = signal(+(this.route.snapshot.queryParamMap.get('s') ?? '50'));
|
||||
sortField = signal(this.route.snapshot.queryParamMap.get('f') ?? 'name');
|
||||
sortDirection = signal(this.route.snapshot.queryParamMap.get('d') ?? 'asc');
|
||||
|
||||
queryParams = computed(() => ({
|
||||
q: this.debouncedFilter.value() ?? '',
|
||||
p: this.pageIndex(),
|
||||
s: this.pageSize(),
|
||||
f: this.sortField(),
|
||||
d: this.sortDirection(),
|
||||
}));
|
||||
|
||||
listResource = this.customerService.query(this.queryParams);
|
||||
listResource = this.customerService.query(this.q, this.pageIndex, this.pageSize, this.sortInput, this.direction);
|
||||
|
||||
dataSource = computed(() => {
|
||||
const data = this.listResource.value();
|
||||
@@ -84,32 +72,37 @@ export class CustomerListComponent implements AfterViewInit {
|
||||
displayedColumns = ['name', 'phone', 'address', 'printInBill', 'discounts'];
|
||||
|
||||
constructor() {
|
||||
// Sync query string whenever params change
|
||||
effect(() => {
|
||||
const p = this.queryParams();
|
||||
const q = this.debouncedFilter.value();
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { q: p.q, p: p.p, s: p.s, f: p.f, d: p.d },
|
||||
queryParams: { q: q },
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true,
|
||||
});
|
||||
});
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.sort.sortChange.subscribe(() => {
|
||||
this.sortField.set(this.sort.active);
|
||||
this.sortDirection.set(this.sort.direction);
|
||||
this.pageIndex.set(0);
|
||||
});
|
||||
handlePageEvent(e: PageEvent) {
|
||||
if (e.pageSize !== this.pageSize() || e.pageIndex !== this.pageIndex()) {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { s: e.pageSize, p: e.pageIndex },
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.paginator.page.subscribe(() => {
|
||||
this.pageSize.set(this.paginator.pageSize);
|
||||
this.pageIndex.set(this.paginator.pageIndex);
|
||||
sortData(sort: Sort) {
|
||||
this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: { f: sort.active || null, d: sort.direction || null },
|
||||
queryParamsHandling: 'merge',
|
||||
replaceUrl: true,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.filterElement.nativeElement.focus();
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
@@ -41,18 +41,21 @@ export class CustomerService {
|
||||
}
|
||||
|
||||
query(
|
||||
params: Signal<{ q: string; p: number; s: number; f: string; d: string }>,
|
||||
q: Signal<string>,
|
||||
p: Signal<number>,
|
||||
s: Signal<number>,
|
||||
f: Signal<string>,
|
||||
d: Signal<string>,
|
||||
): HttpResourceRef<PagedResult<Customer> | undefined> {
|
||||
return httpResource<PagedResult<Customer>>(() => {
|
||||
const p = params();
|
||||
return {
|
||||
url: `${url}/list`,
|
||||
params: {
|
||||
...(p.q ? { q: p.q } : {}),
|
||||
p: p.p,
|
||||
s: p.s,
|
||||
f: p.f,
|
||||
d: p.d,
|
||||
q: q(),
|
||||
p: p(),
|
||||
s: s(),
|
||||
f: f(),
|
||||
d: d(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" (keyup.enter)="save()" />
|
||||
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -38,8 +38,7 @@ export interface DeviceDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class DeviceDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class DeviceDetailComponent {
|
||||
private sectionService = inject(SectionService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
@@ -68,12 +67,10 @@ export class DeviceDetailComponent implements AfterViewInit {
|
||||
|
||||
sections = computed(() => this.sectionsResource.value() ?? []);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #name [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Phone</mat-label>
|
||||
<input matInput #phone type="text" [formField]="form.phone" [matAutocomplete]="auto" autocomplete="off" />
|
||||
<input matInput type="text" [formField]="form.phone" [matAutocomplete]="auto" autocomplete="off" />
|
||||
</mat-form-field>
|
||||
<mat-autocomplete
|
||||
#auto="matAutocomplete"
|
||||
|
||||
@@ -1,14 +1,4 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
computed,
|
||||
inject,
|
||||
debounced,
|
||||
linkedSignal,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { Component, computed, inject, debounced, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatAutocompleteSelectedEvent, MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -53,8 +43,7 @@ export interface GuestBookDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class GuestBookDetailComponent implements AfterViewInit {
|
||||
@ViewChild('phone', { static: true }) phoneElement?: ElementRef;
|
||||
export class GuestBookDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -111,12 +100,10 @@ export class GuestBookDetailComponent implements AfterViewInit {
|
||||
return this.customersResource.value() ?? [];
|
||||
});
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.phoneElement !== undefined) {
|
||||
this.phoneElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -33,7 +33,6 @@ export interface HeaderFooterFormData {
|
||||
],
|
||||
})
|
||||
export class HeaderFooterComponent {
|
||||
@ViewChild('section', { static: true }) sectionElement?: ElementRef;
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -23,8 +23,7 @@ export interface MenuCategoryDetailFormData {
|
||||
styleUrls: ['./menu-category-detail.component.css'],
|
||||
imports: [MatButtonModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, FormField],
|
||||
})
|
||||
export class MenuCategoryDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class MenuCategoryDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -47,12 +46,10 @@ export class MenuCategoryDetailComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Filter</mat-label>
|
||||
<input type="text" matInput #filterElement [formField]="form.filter" autocomplete="off" />
|
||||
<input type="text" matInput [formField]="form.filter" autocomplete="off" />
|
||||
<mat-hint
|
||||
>n: Name, sc: Sale Category, mc: Menu Category, q:(=/<=/>=/</>)Quantity,
|
||||
s:(=/<=/>=/</>)Sales</mat-hint
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DecimalPipe, PercentPipe } from '@angular/common';
|
||||
import { Component, ViewChild, ElementRef, inject, computed, linkedSignal, input } from '@angular/core';
|
||||
import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { toObservable } from '@angular/core/rxjs-interop';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
@@ -7,9 +7,9 @@ import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import moment from 'moment';
|
||||
@@ -45,9 +45,6 @@ export interface MenuEngineeringReportFormData {
|
||||
],
|
||||
})
|
||||
export class MenuEngineeringReportComponent {
|
||||
@ViewChild('filterElement', { static: true }) filterElement?: ElementRef;
|
||||
@ViewChild(MatPaginator, { static: true }) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort, { static: true }) sort?: MatSort;
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private toCsv = inject(ToCsvService);
|
||||
@@ -89,6 +86,9 @@ export class MenuEngineeringReportComponent {
|
||||
// Listen to Filter Change
|
||||
const filterSignal = computed(() => this.formModel().filter);
|
||||
this.filter = toObservable(filterSignal).pipe(debounceTime(150), distinctUntilChanged());
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
show() {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
+5
-11
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, effect, inject, input, signal } from '@angular/core';
|
||||
import { Component, effect, inject, input, signal, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -48,8 +48,7 @@ export interface ModifierCategoryDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class ModifierCategoryDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class ModifierCategoryDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
@@ -108,14 +107,9 @@ export class ModifierCategoryDetailComponent implements AfterViewInit {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #name [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField, required } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -38,8 +38,7 @@ export interface ModifierDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class ModifierDetailComponent implements AfterViewInit {
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
export class ModifierDetailComponent {
|
||||
private modifierCategoryService = inject(ModifierCategoryService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
@@ -71,12 +70,10 @@ export class ModifierDetailComponent implements AfterViewInit {
|
||||
required(schema.modifierCategory, { message: 'Modifier Category is required' });
|
||||
});
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@@ -23,8 +23,7 @@ export interface PrinterDetailFormData {
|
||||
styleUrls: ['./printer-detail.component.css'],
|
||||
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormField],
|
||||
})
|
||||
export class PrinterDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class PrinterDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -48,12 +47,10 @@ export class PrinterDetailComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</button>
|
||||
</h2>
|
||||
|
||||
<form class="flex-col">
|
||||
<form class="flex-col" [formRoot]="form">
|
||||
<div class="row-container sm:max-lg:flex-col">
|
||||
<mat-form-field class="flex-auto basis-1-3">
|
||||
<mat-label>Start Date</mat-label>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { Component, inject, computed, linkedSignal, input } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { form, FormField, FormRoot } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
@@ -37,6 +37,7 @@ export interface ProductSaleReportFormData {
|
||||
MatSelectModule,
|
||||
MatTableModule,
|
||||
FormField,
|
||||
FormRoot,
|
||||
],
|
||||
})
|
||||
export class ProductSaleReportComponent {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="basis-3-4">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #name [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="basis-1-4">
|
||||
<mat-label>Fraction Units</mat-label>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, inject, computed, linkedSignal, input } from '@angular/core';
|
||||
import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField, required, min, max } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -57,8 +57,7 @@ export interface ProductDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class ProductDetailComponent implements AfterViewInit {
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
export class ProductDetailComponent {
|
||||
private menuCategoryService = inject(MenuCategoryService);
|
||||
private saleCategoryService = inject(SaleCategoryService);
|
||||
private route = inject(ActivatedRoute);
|
||||
@@ -123,12 +122,10 @@ export class ProductDetailComponent implements AfterViewInit {
|
||||
'action',
|
||||
];
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
addRow() {
|
||||
|
||||
@@ -44,9 +44,9 @@ export class ProductService {
|
||||
});
|
||||
}
|
||||
|
||||
query(query: Signal<string | null> | string | null): HttpResourceRef<ProductQuery[] | undefined> {
|
||||
query(query: Signal<string | null>): HttpResourceRef<ProductQuery[] | undefined> {
|
||||
return httpResource<ProductQuery[]>(() => {
|
||||
const qVal = typeof query === 'function' ? query() : query;
|
||||
const qVal = query();
|
||||
return `${url}/query?q=${qVal ?? ''}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" (keyup.enter)="save()" />
|
||||
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@@ -23,8 +23,7 @@ export interface RegimeDetailFormData {
|
||||
styleUrls: ['./regime-detail.component.css'],
|
||||
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormField],
|
||||
})
|
||||
export class RegimeDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class RegimeDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -48,12 +47,10 @@ export class RegimeDetailComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -24,8 +24,7 @@ export interface RoleDetailFormData {
|
||||
styleUrls: ['./role-detail.component.css'],
|
||||
imports: [MatButtonModule, MatCheckboxModule, MatFormFieldModule, MatInputModule, FormField],
|
||||
})
|
||||
export class RoleDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class RoleDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
@@ -60,12 +59,10 @@ export class RoleDetailComponent implements AfterViewInit {
|
||||
return implied;
|
||||
});
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, inject, computed, linkedSignal, input } from '@angular/core';
|
||||
import { Component, inject, computed, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -43,8 +43,7 @@ export interface SaleCategoryDetailFormData {
|
||||
RouterLink,
|
||||
],
|
||||
})
|
||||
export class SaleCategoryDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class SaleCategoryDetailComponent {
|
||||
private taxService = inject(TaxService);
|
||||
private productService = inject(ProductService);
|
||||
private route = inject(ActivatedRoute);
|
||||
@@ -80,12 +79,10 @@ export class SaleCategoryDetailComponent implements AfterViewInit {
|
||||
dataSource = computed(() => this.products());
|
||||
displayedColumns = ['name', 'price', 'menuCategory'];
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -20,7 +20,6 @@ export class SaleCategoryListComponent {
|
||||
private ser = inject(SaleCategoryService);
|
||||
listResource = this.ser.list();
|
||||
|
||||
// @ViewChild('table', { static: true }) table?: MatTable<SaleCategory>;
|
||||
dataSource = computed(() => this.listResource.value() ?? []);
|
||||
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CdkScrollableModule } from '@angular/cdk/scrolling';
|
||||
import { Component, ElementRef, ViewChild, inject, computed, signal } from '@angular/core';
|
||||
import { Component, inject, computed, signal } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||
@@ -26,7 +26,6 @@ export interface ReasonFormData {
|
||||
],
|
||||
})
|
||||
export class ReasonComponent {
|
||||
@ViewChild('son', { static: true }) son?: ElementRef;
|
||||
dialogRef = inject<MatDialogRef<ReasonComponent>>(MatDialogRef);
|
||||
private data = inject<{
|
||||
title: string;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CdkScrollableModule } from '@angular/cdk/scrolling';
|
||||
import { CurrencyPipe } from '@angular/common';
|
||||
import { Component, ElementRef, ViewChild, inject, signal, computed } from '@angular/core';
|
||||
import { Component, inject, signal, computed } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog';
|
||||
@@ -38,7 +38,6 @@ export interface ReceivePaymentFormData {
|
||||
],
|
||||
})
|
||||
export class ReceivePaymentComponent {
|
||||
@ViewChild('son', { static: true }) son?: ElementRef;
|
||||
private ser = inject(SettleOptionService);
|
||||
dialogRef = inject<MatDialogRef<ReceivePaymentComponent>>(MatDialogRef);
|
||||
data = inject<{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -43,7 +43,6 @@ export interface SectionPrinterFormData {
|
||||
],
|
||||
})
|
||||
export class SectionPrinterComponent {
|
||||
@ViewChild('section', { static: true }) sectionElement?: ElementRef;
|
||||
private printerService = inject(PrinterService);
|
||||
private sectionService = inject(SectionService);
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" (keyup.enter)="save()" />
|
||||
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
@@ -21,8 +21,7 @@ export interface SectionDetailFormData {
|
||||
styleUrls: ['./section-detail.component.css'],
|
||||
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, FormField],
|
||||
})
|
||||
export class SectionDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class SectionDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -44,12 +43,10 @@ export class SectionDetailComponent implements AfterViewInit {
|
||||
|
||||
form = form(this.formModel);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
+5
-11
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -37,8 +37,7 @@ export interface SettleOptionDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class SettleOptionDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class SettleOptionDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private dialog = inject(MatDialog);
|
||||
@@ -73,14 +72,9 @@ export class SettleOptionDetailComponent implements AfterViewInit {
|
||||
this.reportingLevel = Object.keys(ReportingLevel)
|
||||
.filter((e) => !isNaN(+e))
|
||||
.map((o) => ({ id: +o, name: ReportingLevel[+o] as string }));
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" (keyup.enter)="save()" />
|
||||
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -37,8 +37,7 @@ export interface TableDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class TableDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class TableDetailComponent {
|
||||
private sectionService = inject(SectionService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
@@ -68,12 +67,10 @@ export class TableDetailComponent implements AfterViewInit {
|
||||
|
||||
sections = computed(() => this.sectionsResource.value() ?? []);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" (keyup.enter)="save()" />
|
||||
<input matInput [formField]="form.name" (keyup.enter)="save()" />
|
||||
<mat-hint> Format for GST: ST GST @ x% (1/2) ; CGST @ x% (1/2) </mat-hint>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatOptionModule } from '@angular/material/core';
|
||||
@@ -28,8 +28,7 @@ export interface TaxDetailFormData {
|
||||
styleUrls: ['./tax-detail.component.css'],
|
||||
imports: [MatButtonModule, MatFormFieldModule, MatInputModule, MatOptionModule, MatSelectModule, FormField],
|
||||
})
|
||||
export class TaxDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class TaxDetailComponent {
|
||||
private regimeService = inject(RegimeService);
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
@@ -58,12 +57,10 @@ export class TaxDetailComponent implements AfterViewInit {
|
||||
|
||||
regimes = computed(() => this.regimesResource.value() ?? []);
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto basis-3-4">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #name [formField]="productForm.name" />
|
||||
<input matInput [formField]="productForm.name" />
|
||||
</mat-form-field>
|
||||
<mat-form-field class="flex-auto basis-1-4">
|
||||
<mat-label>Fraction Units</mat-label>
|
||||
|
||||
+7
-20
@@ -1,14 +1,4 @@
|
||||
import {
|
||||
AfterViewInit,
|
||||
Component,
|
||||
ElementRef,
|
||||
ViewChild,
|
||||
computed,
|
||||
inject,
|
||||
signal,
|
||||
linkedSignal,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { Component, computed, inject, signal, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
@@ -74,8 +64,7 @@ export interface TemporalSkuFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class TemporalProductDetailComponent implements AfterViewInit {
|
||||
@ViewChild('name', { static: true }) nameElement?: ElementRef;
|
||||
export class TemporalProductDetailComponent {
|
||||
private menuCategoryService = inject(MenuCategoryService);
|
||||
private saleCategoryService = inject(SaleCategoryService);
|
||||
private route = inject(ActivatedRoute);
|
||||
@@ -175,12 +164,10 @@ export class TemporalProductDetailComponent implements AfterViewInit {
|
||||
//
|
||||
// // }
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.productForm().focusBoundControl();
|
||||
});
|
||||
}
|
||||
|
||||
editProduct(p: Product) {
|
||||
@@ -188,7 +175,7 @@ export class TemporalProductDetailComponent implements AfterViewInit {
|
||||
|
||||
console.log(p);
|
||||
|
||||
setTimeout(() => this.nameElement?.nativeElement?.focus?.(), 0);
|
||||
this.productForm().focusBoundControl();
|
||||
}
|
||||
|
||||
updateProduct() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row-container">
|
||||
<mat-form-field class="flex-auto">
|
||||
<mat-label>Name</mat-label>
|
||||
<input matInput #nameElement [formField]="form.name" />
|
||||
<input matInput [formField]="form.name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row-container">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AfterViewInit, Component, ElementRef, ViewChild, computed, inject, linkedSignal, input } from '@angular/core';
|
||||
import { Component, computed, inject, linkedSignal, input, afterNextRender } from '@angular/core';
|
||||
import { form, FormField } from '@angular/forms/signals';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
@@ -35,8 +35,7 @@ export interface UserDetailFormData {
|
||||
FormField,
|
||||
],
|
||||
})
|
||||
export class UserDetailComponent implements AfterViewInit {
|
||||
@ViewChild('nameElement', { static: true }) nameElement?: ElementRef;
|
||||
export class UserDetailComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
@@ -64,15 +63,10 @@ export class UserDetailComponent implements AfterViewInit {
|
||||
hide = true;
|
||||
|
||||
constructor() {
|
||||
this.hide = true;
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
setTimeout(() => {
|
||||
if (this.nameElement !== undefined) {
|
||||
this.nameElement.nativeElement.focus();
|
||||
}
|
||||
}, 0);
|
||||
afterNextRender(() => {
|
||||
this.form().focusBoundControl();
|
||||
// this.formModel().root.focus("name");
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
|
||||
+438
-451
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user