mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 13:48:47 +02:00
Compare commits
4 Commits
3a258537fc
...
37c2b18300
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c2b18300 | ||
|
|
ce7073cf24 | ||
|
|
5033348320 | ||
|
|
061a49f3b1 |
@@ -43,6 +43,8 @@ class DailySpending(BaseModel):
|
|||||||
def spending_by_category(
|
def spending_by_category(
|
||||||
cycle_year: Optional[int] = None,
|
cycle_year: Optional[int] = None,
|
||||||
cycle_month: Optional[int] = None,
|
cycle_month: Optional[int] = None,
|
||||||
|
start_date: Optional[str] = None,
|
||||||
|
end_date: Optional[str] = None,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
_user: str = Depends(get_current_user),
|
_user: str = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -61,13 +63,19 @@ def spending_by_category(
|
|||||||
.group_by(Transaction.category_id)
|
.group_by(Transaction.category_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
if cycle_year and cycle_month:
|
# Arbitrary date range takes precedence over the cycle filter.
|
||||||
|
if start_date and end_date:
|
||||||
|
query = query.where(
|
||||||
|
Transaction.date >= datetime.fromisoformat(start_date),
|
||||||
|
Transaction.date < datetime.fromisoformat(end_date),
|
||||||
|
)
|
||||||
|
elif cycle_year and cycle_month:
|
||||||
start, end = get_cycle_range(cycle_year, cycle_month)
|
start, end = get_cycle_range(cycle_year, cycle_month)
|
||||||
query = query.where(Transaction.date >= start, Transaction.date < end)
|
query = query.where(Transaction.date >= start, Transaction.date < end)
|
||||||
|
|
||||||
rows = session.exec(query).all()
|
rows = session.exec(query).all()
|
||||||
|
|
||||||
grand_total = sum(r[1] for r in rows) or 1
|
grand_total = float(sum(float(r[1]) for r in rows)) or 1.0
|
||||||
|
|
||||||
results = []
|
results = []
|
||||||
for category_id, total, count in rows:
|
for category_id, total, count in rows:
|
||||||
@@ -165,6 +173,8 @@ def monthly_trend(
|
|||||||
def daily_spending(
|
def daily_spending(
|
||||||
cycle_year: Optional[int] = None,
|
cycle_year: Optional[int] = None,
|
||||||
cycle_month: Optional[int] = None,
|
cycle_month: Optional[int] = None,
|
||||||
|
start_date: Optional[str] = None,
|
||||||
|
end_date: Optional[str] = None,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
_user: str = Depends(get_current_user),
|
_user: str = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
@@ -187,7 +197,12 @@ def daily_spending(
|
|||||||
.order_by(func.date(Transaction.date))
|
.order_by(func.date(Transaction.date))
|
||||||
)
|
)
|
||||||
|
|
||||||
if cycle_year and cycle_month:
|
if start_date and end_date:
|
||||||
|
query = query.where(
|
||||||
|
Transaction.date >= datetime.fromisoformat(start_date),
|
||||||
|
Transaction.date < datetime.fromisoformat(end_date),
|
||||||
|
)
|
||||||
|
elif cycle_year and cycle_month:
|
||||||
start, end = get_cycle_range(cycle_year, cycle_month)
|
start, end = get_cycle_range(cycle_year, cycle_month)
|
||||||
query = query.where(Transaction.date >= start, Transaction.date < end)
|
query = query.where(Transaction.date >= start, Transaction.date < end)
|
||||||
|
|
||||||
|
|||||||
62
backend/tests/test_analytics.py
Normal file
62
backend/tests/test_analytics.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""Analytics endpoints: category aggregation (Decimal regression) and the
|
||||||
|
arbitrary date-range filters added for the Analytics range picker."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from app.api.v1.endpoints.analytics import daily_spending, spending_by_category
|
||||||
|
from app.models.models import Category, Transaction
|
||||||
|
|
||||||
|
from tests.test_installments import make_cc_tx
|
||||||
|
|
||||||
|
|
||||||
|
def seed(session):
|
||||||
|
cat = Category(name="Groceries")
|
||||||
|
session.add(cat)
|
||||||
|
session.commit()
|
||||||
|
make_cc_tx(session, merchant="A", amount="1000.50", reference="a1",
|
||||||
|
date=datetime(2026, 6, 2), category_id=cat.id)
|
||||||
|
make_cc_tx(session, merchant="B", amount="2000.00", reference="b1",
|
||||||
|
date=datetime(2026, 6, 20))
|
||||||
|
return cat
|
||||||
|
|
||||||
|
|
||||||
|
class TestSpendingByCategory:
|
||||||
|
def test_decimal_sum_does_not_crash_and_percentages_add_up(self, session):
|
||||||
|
"""Regression: Decimal grand total vs float row total raised
|
||||||
|
TypeError, 500ing the endpoint whenever it had data."""
|
||||||
|
seed(session)
|
||||||
|
rows = spending_by_category(session=session, _user="t")
|
||||||
|
assert {r.category_name for r in rows} == {"Groceries", "Uncategorized"}
|
||||||
|
assert sum(r.percentage for r in rows) == 100.0
|
||||||
|
|
||||||
|
def test_date_range_overrides_cycle(self, session):
|
||||||
|
seed(session)
|
||||||
|
rows = spending_by_category(
|
||||||
|
start_date="2026-06-01",
|
||||||
|
end_date="2026-06-10",
|
||||||
|
cycle_year=2026,
|
||||||
|
cycle_month=1, # would match nothing; range must win
|
||||||
|
session=session,
|
||||||
|
_user="t",
|
||||||
|
)
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows[0].category_name == "Groceries"
|
||||||
|
assert rows[0].total == 1000.50
|
||||||
|
|
||||||
|
|
||||||
|
class TestDailySpending:
|
||||||
|
def test_date_range_filter(self, session):
|
||||||
|
seed(session)
|
||||||
|
rows = daily_spending(
|
||||||
|
start_date="2026-06-15", end_date="2026-07-01",
|
||||||
|
session=session, _user="t",
|
||||||
|
)
|
||||||
|
assert [r.date for r in rows] == ["2026-06-20"]
|
||||||
|
assert rows[0].total == 2000.00
|
||||||
|
|
||||||
|
def test_future_cuotas_excluded(self, session):
|
||||||
|
make_cc_tx(session, merchant="FUTURE", amount="500.00",
|
||||||
|
reference="f1", date=datetime(2030, 1, 19))
|
||||||
|
rows = daily_spending(session=session, _user="t")
|
||||||
|
assert rows == []
|
||||||
@@ -24,10 +24,13 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cmdk": "^1.1.1",
|
||||||
"concurrently": "^9.1.2",
|
"concurrently": "^9.1.2",
|
||||||
|
"date-fns": "^4.4.0",
|
||||||
"hono": "^4.12.15",
|
"hono": "^4.12.15",
|
||||||
"lucide-react": "^1.12.0",
|
"lucide-react": "^1.12.0",
|
||||||
"react": "19.2.5",
|
"react": "19.2.5",
|
||||||
|
"react-day-picker": "^10.0.1",
|
||||||
"react-dom": "19.2.5",
|
"react-dom": "19.2.5",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
"recharts": "^3.8.0",
|
"recharts": "^3.8.0",
|
||||||
|
|||||||
362
frontend/pnpm-lock.yaml
generated
362
frontend/pnpm-lock.yaml
generated
@@ -13,7 +13,7 @@ importers:
|
|||||||
version: 0.0.52
|
version: 0.0.52
|
||||||
'@base-ui/react':
|
'@base-ui/react':
|
||||||
specifier: ^1.4.1
|
specifier: ^1.4.1
|
||||||
version: 1.4.1(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
version: 1.4.1(@date-fns/tz@1.5.0)(@types/react@19.2.14)(date-fns@4.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
'@copilotkit/react-core':
|
'@copilotkit/react-core':
|
||||||
specifier: 1.56.4
|
specifier: 1.56.4
|
||||||
version: 1.56.4(@types/mdast@4.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql@16.13.2)(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6)
|
version: 1.56.4(@types/mdast@4.0.4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(graphql@16.13.2)(micromark-util-types@2.0.2)(micromark@4.0.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6)
|
||||||
@@ -44,9 +44,15 @@ importers:
|
|||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
cmdk:
|
||||||
|
specifier: ^1.1.1
|
||||||
|
version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
concurrently:
|
concurrently:
|
||||||
specifier: ^9.1.2
|
specifier: ^9.1.2
|
||||||
version: 9.2.1
|
version: 9.2.1
|
||||||
|
date-fns:
|
||||||
|
specifier: ^4.4.0
|
||||||
|
version: 4.4.0
|
||||||
hono:
|
hono:
|
||||||
specifier: ^4.12.15
|
specifier: ^4.12.15
|
||||||
version: 4.12.15
|
version: 4.12.15
|
||||||
@@ -56,6 +62,9 @@ importers:
|
|||||||
react:
|
react:
|
||||||
specifier: 19.2.5
|
specifier: 19.2.5
|
||||||
version: 19.2.5
|
version: 19.2.5
|
||||||
|
react-day-picker:
|
||||||
|
specifier: ^10.0.1
|
||||||
|
version: 10.0.1(@types/react@19.2.14)(react@19.2.5)
|
||||||
react-dom:
|
react-dom:
|
||||||
specifier: 19.2.5
|
specifier: 19.2.5
|
||||||
version: 19.2.5(react@19.2.5)
|
version: 19.2.5(react@19.2.5)
|
||||||
@@ -363,6 +372,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-0PS1AnrTHbmvctveYuRrMr+CQ1FqiRQFhUBcDhSmz2eINr00dOybqIr8xXW78LD6E/6kyuLpTfaGHQLtBftMNw==}
|
resolution: {integrity: sha512-0PS1AnrTHbmvctveYuRrMr+CQ1FqiRQFhUBcDhSmz2eINr00dOybqIr8xXW78LD6E/6kyuLpTfaGHQLtBftMNw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@date-fns/tz@1.5.0':
|
||||||
|
resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==}
|
||||||
|
|
||||||
'@envelop/core@5.5.1':
|
'@envelop/core@5.5.1':
|
||||||
resolution: {integrity: sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw==}
|
resolution: {integrity: sha512-3DQg8sFskDo386TkL5j12jyRAdip/8yzK3x7YGbZBgobZ4aKXrvDU0GppU0SnmrpQnNaiTUsxBs9LKkwQ/eyvw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@@ -911,6 +923,9 @@ packages:
|
|||||||
'@radix-ui/primitive@1.1.3':
|
'@radix-ui/primitive@1.1.3':
|
||||||
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.4':
|
||||||
|
resolution: {integrity: sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7':
|
'@radix-ui/react-arrow@1.1.7':
|
||||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -946,6 +961,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.3':
|
||||||
|
resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-context@1.1.2':
|
'@radix-ui/react-context@1.1.2':
|
||||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -955,6 +979,28 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.4':
|
||||||
|
resolution: {integrity: sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.18':
|
||||||
|
resolution: {integrity: sha512-apa28mldjMgORmE6g/w3sCcA0Y9UAVeeDVoozN4i7kOw12mLl9RBchfzK3Nn6qxOWjrZhK1Lfy7f07kyzxtnBw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-direction@1.1.1':
|
'@radix-ui/react-direction@1.1.1':
|
||||||
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -977,6 +1023,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.14':
|
||||||
|
resolution: {integrity: sha512-4lUhWTWAjbDIqFrAPWJ3WqBOpO5YchVZ88X3nh6H9Lu5AFi5nCUeTPj3D8FSDmabmFeRe9ME0BDA4MwKTha5GQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-dropdown-menu@2.1.16':
|
'@radix-ui/react-dropdown-menu@2.1.16':
|
||||||
resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
|
resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -999,6 +1058,28 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.4':
|
||||||
|
resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.11':
|
||||||
|
resolution: {integrity: sha512-Mn88Vg2whaRocGJNOH+DKFqYm6ySFPQaiwHNxZPyjn99B52KAEJWWY9NP83+nWdk2HM3rdov+STu9AG471Rt9w==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.1.7':
|
'@radix-ui/react-focus-scope@1.1.7':
|
||||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1021,6 +1102,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.2':
|
||||||
|
resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-menu@2.1.16':
|
'@radix-ui/react-menu@2.1.16':
|
||||||
resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
|
resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1047,6 +1137,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.13':
|
||||||
|
resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.9':
|
'@radix-ui/react-portal@1.1.9':
|
||||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1073,6 +1176,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.6':
|
||||||
|
resolution: {integrity: sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3':
|
'@radix-ui/react-primitive@2.1.3':
|
||||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1086,6 +1202,19 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.7':
|
||||||
|
resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
'@types/react-dom': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
'@types/react-dom':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.11':
|
'@radix-ui/react-roving-focus@1.1.11':
|
||||||
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1117,6 +1246,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.3.0':
|
||||||
|
resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.2.8':
|
'@radix-ui/react-tooltip@1.2.8':
|
||||||
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1139,6 +1277,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.2':
|
||||||
|
resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-controllable-state@1.2.2':
|
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||||
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1148,6 +1295,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.3':
|
||||||
|
resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-effect-event@0.0.2':
|
'@radix-ui/react-use-effect-event@0.0.2':
|
||||||
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1157,6 +1313,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.3':
|
||||||
|
resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-escape-keydown@1.1.1':
|
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||||
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1175,6 +1340,15 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.2':
|
||||||
|
resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '*'
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@radix-ui/react-use-rect@1.1.1':
|
'@radix-ui/react-use-rect@1.1.1':
|
||||||
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2054,6 +2228,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
cmdk@1.1.1:
|
||||||
|
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
react-dom: ^18 || ^19 || ^19.0.0-rc
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
engines: {node: '>=7.0.0'}
|
engines: {node: '>=7.0.0'}
|
||||||
@@ -2304,8 +2484,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
date-fns@4.1.0:
|
date-fns@4.4.0:
|
||||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==}
|
||||||
|
|
||||||
dateformat@4.6.3:
|
dateformat@4.6.3:
|
||||||
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
|
resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==}
|
||||||
@@ -3626,6 +3806,16 @@ packages:
|
|||||||
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
||||||
|
|
||||||
|
react-day-picker@10.0.1:
|
||||||
|
resolution: {integrity: sha512-eNh6BlwcYInWaJtRv18mXQ06Ys/H6rdTZAnTaSdOYJuTpwP1JMCHNd1FDRadA+gbeinq+psdULN5Xnowy9mV8w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=16.8.0'
|
||||||
|
react: '>=16.8.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-dom@19.2.5:
|
react-dom@19.2.5:
|
||||||
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
|
resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4390,7 +4580,7 @@ snapshots:
|
|||||||
'@a2ui/web_core@0.9.0':
|
'@a2ui/web_core@0.9.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@preact/signals-core': 1.14.1
|
'@preact/signals-core': 1.14.1
|
||||||
date-fns: 4.1.0
|
date-fns: 4.4.0
|
||||||
zod: 3.25.76
|
zod: 3.25.76
|
||||||
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
zod-to-json-schema: 3.25.2(zod@3.25.76)
|
||||||
|
|
||||||
@@ -4558,7 +4748,7 @@ snapshots:
|
|||||||
|
|
||||||
'@babel/runtime@7.29.2': {}
|
'@babel/runtime@7.29.2': {}
|
||||||
|
|
||||||
'@base-ui/react@1.4.1(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@base-ui/react@1.4.1(@date-fns/tz@1.5.0)(@types/react@19.2.14)(date-fns@4.4.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.29.2
|
'@babel/runtime': 7.29.2
|
||||||
'@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
'@base-ui/utils': 0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
@@ -4568,8 +4758,9 @@ snapshots:
|
|||||||
react-dom: 19.2.5(react@19.2.5)
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
use-sync-external-store: 1.6.0(react@19.2.5)
|
use-sync-external-store: 1.6.0(react@19.2.5)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
'@date-fns/tz': 1.5.0
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
date-fns: 4.1.0
|
date-fns: 4.4.0
|
||||||
|
|
||||||
'@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@base-ui/utils@0.2.8(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -4794,6 +4985,8 @@ snapshots:
|
|||||||
- encoding
|
- encoding
|
||||||
- zod
|
- zod
|
||||||
|
|
||||||
|
'@date-fns/tz@1.5.0': {}
|
||||||
|
|
||||||
'@envelop/core@5.5.1':
|
'@envelop/core@5.5.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@envelop/instrumentation': 1.0.0
|
'@envelop/instrumentation': 1.0.0
|
||||||
@@ -5229,6 +5422,8 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/primitive@1.1.3': {}
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
|
|
||||||
|
'@radix-ui/primitive@1.1.4': {}
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
@@ -5256,12 +5451,46 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.5
|
react: 19.2.5
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-context@1.1.4(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-dialog@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.4
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-context': 1.1.4(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-dismissable-layer': 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-focus-scope': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-id': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-presence': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-slot': 1.3.0(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
aria-hidden: 1.2.6
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.5
|
react: 19.2.5
|
||||||
@@ -5281,6 +5510,19 @@ snapshots:
|
|||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
|
'@radix-ui/react-dismissable-layer@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/primitive': 1.1.4
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@@ -5302,6 +5544,23 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-focus-scope@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
@@ -5320,6 +5579,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-id@1.1.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@@ -5364,6 +5630,16 @@ snapshots:
|
|||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
|
'@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
@@ -5384,6 +5660,15 @@ snapshots:
|
|||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
|
'@radix-ui/react-presence@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5)
|
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
@@ -5393,6 +5678,15 @@ snapshots:
|
|||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
|
'@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-slot': 1.3.0(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@@ -5424,6 +5718,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-slot@1.3.0(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
'@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@@ -5450,6 +5751,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5)
|
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
@@ -5458,6 +5765,14 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||||
@@ -5465,6 +5780,13 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||||
@@ -5478,6 +5800,12 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.14
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
|
'@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.14)(react@19.2.5)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
'@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
'@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/rect': 1.1.1
|
'@radix-ui/rect': 1.1.1
|
||||||
@@ -6299,6 +6627,18 @@ snapshots:
|
|||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
|
cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5):
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-dialog': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
'@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5)
|
||||||
|
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)
|
||||||
|
react: 19.2.5
|
||||||
|
react-dom: 19.2.5(react@19.2.5)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
|
||||||
color-convert@2.0.1:
|
color-convert@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name: 1.1.4
|
color-name: 1.1.4
|
||||||
@@ -6562,7 +6902,7 @@ snapshots:
|
|||||||
|
|
||||||
data-uri-to-buffer@4.0.1: {}
|
data-uri-to-buffer@4.0.1: {}
|
||||||
|
|
||||||
date-fns@4.1.0: {}
|
date-fns@4.4.0: {}
|
||||||
|
|
||||||
dateformat@4.6.3: {}
|
dateformat@4.6.3: {}
|
||||||
|
|
||||||
@@ -8351,6 +8691,14 @@ snapshots:
|
|||||||
react-stately: 3.46.0(react@19.2.5)
|
react-stately: 3.46.0(react@19.2.5)
|
||||||
use-sync-external-store: 1.6.0(react@19.2.5)
|
use-sync-external-store: 1.6.0(react@19.2.5)
|
||||||
|
|
||||||
|
react-day-picker@10.0.1(@types/react@19.2.14)(react@19.2.5):
|
||||||
|
dependencies:
|
||||||
|
'@date-fns/tz': 1.5.0
|
||||||
|
date-fns: 4.4.0
|
||||||
|
react: 19.2.5
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.14
|
||||||
|
|
||||||
react-dom@19.2.5(react@19.2.5):
|
react-dom@19.2.5(react@19.2.5):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.5
|
react: 19.2.5
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import { HttpAgent, Middleware, EventType } from "@ag-ui/client";
|
|||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
|
|
||||||
const AGENT_URL =
|
|
||||||
process.env.AGENT_URL ?? "http://backend:8000/api/v1/agent/agui";
|
|
||||||
const BACKEND_URL =
|
const BACKEND_URL =
|
||||||
process.env.BACKEND_URL ?? "http://localhost:8001";
|
process.env.BACKEND_URL ?? "http://localhost:8001";
|
||||||
|
// Prod sets AGENT_URL explicitly (compose network); the default derives from
|
||||||
|
// BACKEND_URL so local dev reaches the docker backend published on :8001.
|
||||||
|
const AGENT_URL =
|
||||||
|
process.env.AGENT_URL ?? `${BACKEND_URL}/api/v1/agent/agui`;
|
||||||
|
|
||||||
const isProd = process.env.NODE_ENV === "production";
|
const isProd = process.env.NODE_ENV === "production";
|
||||||
const PORT = parseInt(process.env.PORT ?? (isProd ? "3000" : "3001"));
|
const PORT = parseInt(process.env.PORT ?? (isProd ? "3000" : "3001"));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { titleFor } from "@/lib/navigation";
|
|||||||
import { useTheme } from "@/contexts/theme-context";
|
import { useTheme } from "@/contexts/theme-context";
|
||||||
import { usePrivacy } from "@/contexts/privacy-context";
|
import { usePrivacy } from "@/contexts/privacy-context";
|
||||||
import { AppSidebar } from "@/components/app-sidebar";
|
import { AppSidebar } from "@/components/app-sidebar";
|
||||||
|
import { CommandMenu } from "@/components/command-menu";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
@@ -59,6 +60,7 @@ export default function Layout() {
|
|||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
<div className="ml-auto flex items-center gap-1">
|
<div className="ml-auto flex items-center gap-1">
|
||||||
|
<CommandMenu />
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
143
frontend/src/components/command-menu.tsx
Normal file
143
frontend/src/components/command-menu.tsx
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { Eye, EyeOff, Moon, Search, Sparkles, Sun } from "lucide-react";
|
||||||
|
|
||||||
|
import { navSections } from "@/lib/navigation";
|
||||||
|
import { useTheme } from "@/contexts/theme-context";
|
||||||
|
import { usePrivacy } from "@/contexts/privacy-context";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Kbd } from "@/components/ui/kbd";
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
CommandSeparator,
|
||||||
|
} from "@/components/ui/command";
|
||||||
|
|
||||||
|
/** Global ⌘K palette: jump to any page, quick-toggle privacy/theme, or send
|
||||||
|
* what you typed straight to the Asistente (reuses the Inicio ask flow). */
|
||||||
|
export function CommandMenu() {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
const { privacyMode, togglePrivacy } = usePrivacy();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const down = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
setOpen((o) => !o);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", down);
|
||||||
|
return () => document.removeEventListener("keydown", down);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const run = (fn: () => void) => {
|
||||||
|
setOpen(false);
|
||||||
|
setQuery("");
|
||||||
|
fn();
|
||||||
|
};
|
||||||
|
|
||||||
|
const ask = query.trim();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="hidden sm:flex w-44 justify-start gap-2 text-muted-foreground font-normal"
|
||||||
|
aria-label="Abrir buscador (Cmd+K)"
|
||||||
|
>
|
||||||
|
<Search className="w-3.5 h-3.5" aria-hidden />
|
||||||
|
Buscar…
|
||||||
|
<Kbd className="ml-auto">⌘K</Kbd>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
className="sm:hidden"
|
||||||
|
title="Buscar"
|
||||||
|
aria-label="Abrir buscador"
|
||||||
|
>
|
||||||
|
<Search className="w-4 h-4" aria-hidden />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<CommandDialog
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(next) => {
|
||||||
|
setOpen(next);
|
||||||
|
if (!next) setQuery("");
|
||||||
|
}}
|
||||||
|
title="Buscador"
|
||||||
|
description="Ir a una página o preguntarle al asistente"
|
||||||
|
>
|
||||||
|
<Command>
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Ir a una página o preguntar…"
|
||||||
|
value={query}
|
||||||
|
onValueChange={setQuery}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>Sin resultados.</CommandEmpty>
|
||||||
|
{navSections.map((section) => (
|
||||||
|
<CommandGroup key={section.label} heading={section.label}>
|
||||||
|
{section.items.map(({ to, icon: Icon, label }) => (
|
||||||
|
<CommandItem
|
||||||
|
key={to}
|
||||||
|
value={`${section.label} ${label}`}
|
||||||
|
onSelect={() => run(() => navigate(to))}
|
||||||
|
>
|
||||||
|
<Icon aria-hidden />
|
||||||
|
{label}
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
))}
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup heading="Acciones">
|
||||||
|
<CommandItem
|
||||||
|
value="modo privado privacidad"
|
||||||
|
onSelect={() => run(togglePrivacy)}
|
||||||
|
>
|
||||||
|
{privacyMode ? <EyeOff aria-hidden /> : <Eye aria-hidden />}
|
||||||
|
{privacyMode ? "Desactivar modo privado" : "Activar modo privado"}
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem
|
||||||
|
value="tema oscuro claro"
|
||||||
|
onSelect={() => run(toggleTheme)}
|
||||||
|
>
|
||||||
|
{theme === "dark" ? <Sun aria-hidden /> : <Moon aria-hidden />}
|
||||||
|
{theme === "dark" ? "Tema claro" : "Tema oscuro"}
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
{ask.length > 2 && (
|
||||||
|
<>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup heading="Asistente">
|
||||||
|
{/* value includes the query so cmdk never filters it out */}
|
||||||
|
<CommandItem
|
||||||
|
value={`preguntar ${ask}`}
|
||||||
|
onSelect={() =>
|
||||||
|
run(() => navigate("/asistente", { state: { ask } }))
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Sparkles className="text-primary" aria-hidden />
|
||||||
|
Preguntar: “{ask}”
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</CommandDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,11 @@ import { Link } from "react-router-dom";
|
|||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
HoverCard,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from "@/components/ui/hover-card";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
/** Stat-tile contract (dataviz skill): label · value · optional delta ·
|
/** Stat-tile contract (dataviz skill): label · value · optional delta ·
|
||||||
@@ -21,6 +26,8 @@ export interface StatTileProps {
|
|||||||
/** Makes the whole tile a link (adds hover border + focus ring). */
|
/** Makes the whole tile a link (adds hover border + focus ring). */
|
||||||
to?: string;
|
to?: string;
|
||||||
ariaLabel?: string;
|
ariaLabel?: string;
|
||||||
|
/** Extra detail revealed on hover (HoverCard). */
|
||||||
|
hoverContent?: React.ReactNode;
|
||||||
/** Set false for non-monetary values that shouldn't blur (counts, dates). */
|
/** Set false for non-monetary values that shouldn't blur (counts, dates). */
|
||||||
sensitive?: boolean;
|
sensitive?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -35,6 +42,7 @@ export function StatTile({
|
|||||||
children,
|
children,
|
||||||
to,
|
to,
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
|
hoverContent,
|
||||||
sensitive = true,
|
sensitive = true,
|
||||||
className,
|
className,
|
||||||
}: StatTileProps) {
|
}: StatTileProps) {
|
||||||
@@ -83,8 +91,7 @@ export function StatTile({
|
|||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!to) return card;
|
const wrapped = to ? (
|
||||||
return (
|
|
||||||
<Link
|
<Link
|
||||||
to={to}
|
to={to}
|
||||||
aria-label={ariaLabel ?? label}
|
aria-label={ariaLabel ?? label}
|
||||||
@@ -92,5 +99,17 @@ export function StatTile({
|
|||||||
>
|
>
|
||||||
{card}
|
{card}
|
||||||
</Link>
|
</Link>
|
||||||
|
) : (
|
||||||
|
card
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hoverContent) return wrapped;
|
||||||
|
return (
|
||||||
|
<HoverCard>
|
||||||
|
<HoverCardTrigger render={<div className="h-full" />}>
|
||||||
|
{wrapped}
|
||||||
|
</HoverCardTrigger>
|
||||||
|
<HoverCardContent className="w-80">{hoverContent}</HoverCardContent>
|
||||||
|
</HoverCard>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
221
frontend/src/components/ui/calendar.tsx
Normal file
221
frontend/src/components/ui/calendar.tsx
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
DayPicker,
|
||||||
|
getDefaultClassNames,
|
||||||
|
type DayButton,
|
||||||
|
type Locale,
|
||||||
|
} from "react-day-picker"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Button, buttonVariants } from "@/components/ui/button"
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon, ChevronDownIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
captionLayout = "label",
|
||||||
|
buttonVariant = "ghost",
|
||||||
|
locale,
|
||||||
|
formatters,
|
||||||
|
components,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
|
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
||||||
|
}) {
|
||||||
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DayPicker
|
||||||
|
showOutsideDays={showOutsideDays}
|
||||||
|
className={cn(
|
||||||
|
"group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
|
||||||
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
captionLayout={captionLayout}
|
||||||
|
locale={locale}
|
||||||
|
formatters={{
|
||||||
|
formatMonthDropdown: (date) =>
|
||||||
|
date.toLocaleString(locale?.code, { month: "short" }),
|
||||||
|
...formatters,
|
||||||
|
}}
|
||||||
|
classNames={{
|
||||||
|
root: cn("w-fit", defaultClassNames.root),
|
||||||
|
months: cn(
|
||||||
|
"relative flex flex-col gap-4 md:flex-row",
|
||||||
|
defaultClassNames.months
|
||||||
|
),
|
||||||
|
month: cn("flex w-full flex-col gap-4", defaultClassNames.month),
|
||||||
|
nav: cn(
|
||||||
|
"absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1",
|
||||||
|
defaultClassNames.nav
|
||||||
|
),
|
||||||
|
button_previous: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
||||||
|
defaultClassNames.button_previous
|
||||||
|
),
|
||||||
|
button_next: cn(
|
||||||
|
buttonVariants({ variant: buttonVariant }),
|
||||||
|
"size-(--cell-size) p-0 select-none aria-disabled:opacity-50",
|
||||||
|
defaultClassNames.button_next
|
||||||
|
),
|
||||||
|
month_caption: cn(
|
||||||
|
"flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)",
|
||||||
|
defaultClassNames.month_caption
|
||||||
|
),
|
||||||
|
dropdowns: cn(
|
||||||
|
"flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium",
|
||||||
|
defaultClassNames.dropdowns
|
||||||
|
),
|
||||||
|
dropdown_root: cn(
|
||||||
|
"relative rounded-(--cell-radius)",
|
||||||
|
defaultClassNames.dropdown_root
|
||||||
|
),
|
||||||
|
dropdown: cn(
|
||||||
|
"absolute inset-0 bg-popover opacity-0",
|
||||||
|
defaultClassNames.dropdown
|
||||||
|
),
|
||||||
|
caption_label: cn(
|
||||||
|
"font-medium select-none",
|
||||||
|
captionLayout === "label"
|
||||||
|
? "text-sm"
|
||||||
|
: "flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground",
|
||||||
|
defaultClassNames.caption_label
|
||||||
|
),
|
||||||
|
month_grid: cn("w-full border-collapse", defaultClassNames.month_grid),
|
||||||
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
|
weekday: cn(
|
||||||
|
"flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none",
|
||||||
|
defaultClassNames.weekday
|
||||||
|
),
|
||||||
|
week: cn("mt-2 flex w-full", defaultClassNames.week),
|
||||||
|
week_number_header: cn(
|
||||||
|
"w-(--cell-size) select-none",
|
||||||
|
defaultClassNames.week_number_header
|
||||||
|
),
|
||||||
|
week_number: cn(
|
||||||
|
"text-[0.8rem] text-muted-foreground select-none",
|
||||||
|
defaultClassNames.week_number
|
||||||
|
),
|
||||||
|
day: cn(
|
||||||
|
"group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)",
|
||||||
|
props.showWeekNumber
|
||||||
|
? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)"
|
||||||
|
: "[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)",
|
||||||
|
defaultClassNames.day
|
||||||
|
),
|
||||||
|
range_start: cn(
|
||||||
|
"relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted",
|
||||||
|
defaultClassNames.range_start
|
||||||
|
),
|
||||||
|
range_middle: cn("rounded-none", defaultClassNames.range_middle),
|
||||||
|
range_end: cn(
|
||||||
|
"relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted",
|
||||||
|
defaultClassNames.range_end
|
||||||
|
),
|
||||||
|
today: cn(
|
||||||
|
"rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none",
|
||||||
|
defaultClassNames.today
|
||||||
|
),
|
||||||
|
outside: cn(
|
||||||
|
"text-muted-foreground aria-selected:text-muted-foreground",
|
||||||
|
defaultClassNames.outside
|
||||||
|
),
|
||||||
|
disabled: cn(
|
||||||
|
"text-muted-foreground opacity-50",
|
||||||
|
defaultClassNames.disabled
|
||||||
|
),
|
||||||
|
hidden: cn("invisible", defaultClassNames.hidden),
|
||||||
|
...classNames,
|
||||||
|
}}
|
||||||
|
components={{
|
||||||
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="calendar"
|
||||||
|
ref={rootRef}
|
||||||
|
className={cn(className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
|
if (orientation === "left") {
|
||||||
|
return (
|
||||||
|
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === "right") {
|
||||||
|
return (
|
||||||
|
<ChevronRightIcon className={cn("size-4", className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
DayButton: ({ ...props }) => (
|
||||||
|
<CalendarDayButton locale={locale} {...props} />
|
||||||
|
),
|
||||||
|
WeekNumber: ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<td {...props}>
|
||||||
|
<div className="flex size-(--cell-size) items-center justify-center text-center">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
...components,
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CalendarDayButton({
|
||||||
|
className,
|
||||||
|
day,
|
||||||
|
modifiers,
|
||||||
|
locale,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
|
||||||
|
const defaultClassNames = getDefaultClassNames()
|
||||||
|
|
||||||
|
const ref = React.useRef<HTMLButtonElement>(null)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (modifiers.focused) ref.current?.focus()
|
||||||
|
}, [modifiers.focused])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
data-day={day.date.toLocaleDateString(locale?.code)}
|
||||||
|
data-selected-single={
|
||||||
|
modifiers.selected &&
|
||||||
|
!modifiers.range_start &&
|
||||||
|
!modifiers.range_end &&
|
||||||
|
!modifiers.range_middle
|
||||||
|
}
|
||||||
|
data-range-start={modifiers.range_start}
|
||||||
|
data-range-end={modifiers.range_end}
|
||||||
|
data-range-middle={modifiers.range_middle}
|
||||||
|
className={cn(
|
||||||
|
"relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70",
|
||||||
|
defaultClassNames.day,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Calendar, CalendarDayButton }
|
||||||
194
frontend/src/components/ui/command.tsx
Normal file
194
frontend/src/components/ui/command.tsx
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import {
|
||||||
|
InputGroup,
|
||||||
|
InputGroupAddon,
|
||||||
|
} from "@/components/ui/input-group"
|
||||||
|
import { SearchIcon, CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function Command({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive
|
||||||
|
data-slot="command"
|
||||||
|
className={cn(
|
||||||
|
"flex size-full flex-col overflow-hidden rounded-xl! bg-popover p-1 text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandDialog({
|
||||||
|
title = "Command Palette",
|
||||||
|
description = "Search for a command to run...",
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showCloseButton = false,
|
||||||
|
...props
|
||||||
|
}: Omit<React.ComponentProps<typeof Dialog>, "children"> & {
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
className?: string
|
||||||
|
showCloseButton?: boolean
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogHeader className="sr-only">
|
||||||
|
<DialogTitle>{title}</DialogTitle>
|
||||||
|
<DialogDescription>{description}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogContent
|
||||||
|
className={cn(
|
||||||
|
"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
showCloseButton={showCloseButton}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandInput({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
||||||
|
return (
|
||||||
|
<div data-slot="command-input-wrapper" className="p-1 pb-0">
|
||||||
|
<InputGroup className="h-8! rounded-lg! border-input/30 bg-input/30 shadow-none! *:data-[slot=input-group-addon]:pl-2!">
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
data-slot="command-input"
|
||||||
|
className={cn(
|
||||||
|
"w-full text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon>
|
||||||
|
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandList({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
data-slot="command-list"
|
||||||
|
className={cn(
|
||||||
|
"no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandEmpty({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
data-slot="command-empty"
|
||||||
|
className={cn("py-6 text-center text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
data-slot="command-group"
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
data-slot="command-separator"
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
data-slot="command-item"
|
||||||
|
className={cn(
|
||||||
|
"group/command-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none in-data-[slot=dialog-content]:rounded-lg! data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-selected:bg-muted data-selected:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-selected:*:[svg]:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<CheckIcon className="ml-auto opacity-0 group-has-data-[slot=command-shortcut]/command-item:hidden group-data-[checked=true]/command-item:opacity-100" />
|
||||||
|
</CommandPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="command-shortcut"
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground group-data-selected/command-item:text-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
||||||
49
frontend/src/components/ui/hover-card.tsx
Normal file
49
frontend/src/components/ui/hover-card.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
|
||||||
|
return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
|
||||||
|
return (
|
||||||
|
<PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function HoverCardContent({
|
||||||
|
className,
|
||||||
|
side = "bottom",
|
||||||
|
sideOffset = 4,
|
||||||
|
align = "center",
|
||||||
|
alignOffset = 4,
|
||||||
|
...props
|
||||||
|
}: PreviewCardPrimitive.Popup.Props &
|
||||||
|
Pick<
|
||||||
|
PreviewCardPrimitive.Positioner.Props,
|
||||||
|
"align" | "alignOffset" | "side" | "sideOffset"
|
||||||
|
>) {
|
||||||
|
return (
|
||||||
|
<PreviewCardPrimitive.Portal data-slot="hover-card-portal">
|
||||||
|
<PreviewCardPrimitive.Positioner
|
||||||
|
align={align}
|
||||||
|
alignOffset={alignOffset}
|
||||||
|
side={side}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className="isolate z-50"
|
||||||
|
>
|
||||||
|
<PreviewCardPrimitive.Popup
|
||||||
|
data-slot="hover-card-content"
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-64 origin-(--transform-origin) rounded-lg bg-popover p-2.5 text-sm text-popover-foreground shadow-md ring-1 ring-foreground/10 outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PreviewCardPrimitive.Positioner>
|
||||||
|
</PreviewCardPrimitive.Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||||
@@ -2419,6 +2419,8 @@ export interface operations {
|
|||||||
query?: {
|
query?: {
|
||||||
cycle_year?: number | null;
|
cycle_year?: number | null;
|
||||||
cycle_month?: number | null;
|
cycle_month?: number | null;
|
||||||
|
start_date?: string | null;
|
||||||
|
end_date?: string | null;
|
||||||
};
|
};
|
||||||
header?: {
|
header?: {
|
||||||
authorization?: string | null;
|
authorization?: string | null;
|
||||||
@@ -2455,6 +2457,8 @@ export interface operations {
|
|||||||
query?: {
|
query?: {
|
||||||
cycle_year?: number | null;
|
cycle_year?: number | null;
|
||||||
cycle_month?: number | null;
|
cycle_month?: number | null;
|
||||||
|
start_date?: string | null;
|
||||||
|
end_date?: string | null;
|
||||||
};
|
};
|
||||||
header?: {
|
header?: {
|
||||||
authorization?: string | null;
|
authorization?: string | null;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { type DateRange } from 'react-day-picker';
|
||||||
import {
|
import {
|
||||||
PieChart,
|
PieChart,
|
||||||
Pie,
|
Pie,
|
||||||
@@ -11,7 +12,7 @@ import {
|
|||||||
LineChart,
|
LineChart,
|
||||||
Line,
|
Line,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import { BarChart3, ChartPie } from 'lucide-react';
|
import { BarChart3, CalendarRange, ChartPie, X } from 'lucide-react';
|
||||||
|
|
||||||
import api, { getRateHistory } from '@/lib/api';
|
import api, { getRateHistory } from '@/lib/api';
|
||||||
import { categoricalColor, MAX_SLICES } from '@/lib/charts';
|
import { categoricalColor, MAX_SLICES } from '@/lib/charts';
|
||||||
@@ -19,6 +20,13 @@ import ErrorState from '@/components/ErrorState';
|
|||||||
import BillingCycleSelector from '@/components/BillingCycleSelector';
|
import BillingCycleSelector from '@/components/BillingCycleSelector';
|
||||||
import { PageHeader } from '@/components/page-header';
|
import { PageHeader } from '@/components/page-header';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Calendar } from '@/components/ui/calendar';
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
@@ -110,15 +118,26 @@ const dailyChartConfig = {
|
|||||||
|
|
||||||
export default function Analytics() {
|
export default function Analytics() {
|
||||||
const [cycle, setCycle] = useState<{ year: number; month: number } | null>(null);
|
const [cycle, setCycle] = useState<{ year: number; month: number } | null>(null);
|
||||||
const cycleParams: Record<string, string> = cycle
|
const [range, setRange] = useState<DateRange | undefined>();
|
||||||
|
|
||||||
|
const toISODate = (d: Date) => d.toISOString().slice(0, 10);
|
||||||
|
const nextDay = (d: Date) => new Date(d.getFullYear(), d.getMonth(), d.getDate() + 1);
|
||||||
|
const rangeActive = !!(range?.from && range?.to);
|
||||||
|
// Range takes precedence over the billing-cycle filter (end is exclusive).
|
||||||
|
const periodParams: Record<string, string> = rangeActive
|
||||||
|
? {
|
||||||
|
start_date: toISODate(range!.from!),
|
||||||
|
end_date: toISODate(nextDay(range!.to!)),
|
||||||
|
}
|
||||||
|
: cycle
|
||||||
? { cycle_year: String(cycle.year), cycle_month: String(cycle.month) }
|
? { cycle_year: String(cycle.year), cycle_month: String(cycle.month) }
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
const byCategoryQ = useQuery({
|
const byCategoryQ = useQuery({
|
||||||
queryKey: ['analytics', 'by-category', cycle],
|
queryKey: ['analytics', 'by-category', cycle, range],
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
api
|
api
|
||||||
.get<CategorySpending[]>('/analytics/by-category', { params: cycleParams, signal })
|
.get<CategorySpending[]>('/analytics/by-category', { params: periodParams, signal })
|
||||||
.then((r) => r.data),
|
.then((r) => r.data),
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
});
|
});
|
||||||
@@ -130,10 +149,10 @@ export default function Analytics() {
|
|||||||
api.get<MonthlyTrend[]>('/analytics/monthly-trend', { signal }).then((r) => r.data),
|
api.get<MonthlyTrend[]>('/analytics/monthly-trend', { signal }).then((r) => r.data),
|
||||||
});
|
});
|
||||||
const dailyQ = useQuery({
|
const dailyQ = useQuery({
|
||||||
queryKey: ['analytics', 'daily', cycle],
|
queryKey: ['analytics', 'daily', cycle, range],
|
||||||
queryFn: ({ signal }) =>
|
queryFn: ({ signal }) =>
|
||||||
api
|
api
|
||||||
.get<DailySpending[]>('/analytics/daily-spending', { params: cycleParams, signal })
|
.get<DailySpending[]>('/analytics/daily-spending', { params: periodParams, signal })
|
||||||
.then((r) => r.data),
|
.then((r) => r.data),
|
||||||
placeholderData: (prev) => prev,
|
placeholderData: (prev) => prev,
|
||||||
});
|
});
|
||||||
@@ -173,7 +192,55 @@ export default function Analytics() {
|
|||||||
icon={BarChart3}
|
icon={BarChart3}
|
||||||
title="Analytics"
|
title="Analytics"
|
||||||
subtitle="Desglose y tendencias de gasto"
|
subtitle="Desglose y tendencias de gasto"
|
||||||
actions={<BillingCycleSelector value={cycle} onChange={setCycle} />}
|
actions={
|
||||||
|
<>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger
|
||||||
|
render={
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className={rangeActive ? 'text-foreground' : 'text-muted-foreground'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CalendarRange className="w-4 h-4 mr-2" aria-hidden />
|
||||||
|
{rangeActive
|
||||||
|
? `${range!.from!.toLocaleDateString('es-CR', { day: 'numeric', month: 'short' })} – ${range!.to!.toLocaleDateString('es-CR', { day: 'numeric', month: 'short' })}`
|
||||||
|
: 'Rango'}
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="end">
|
||||||
|
<Calendar
|
||||||
|
mode="range"
|
||||||
|
selected={range}
|
||||||
|
onSelect={(r) => {
|
||||||
|
setRange(r);
|
||||||
|
if (r?.from && r?.to) setCycle(null);
|
||||||
|
}}
|
||||||
|
numberOfMonths={2}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
{rangeActive && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => setRange(undefined)}
|
||||||
|
title="Quitar rango"
|
||||||
|
aria-label="Quitar rango de fechas"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" aria-hidden />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<BillingCycleSelector
|
||||||
|
value={cycle}
|
||||||
|
onChange={(c) => {
|
||||||
|
setCycle(c);
|
||||||
|
if (c) setRange(undefined);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{anyError && (
|
{anyError && (
|
||||||
|
|||||||
@@ -25,6 +25,36 @@ import { Skeleton } from "@/components/ui/skeleton";
|
|||||||
|
|
||||||
const CYCLE = currentBudgetCycle();
|
const CYCLE = currentBudgetCycle();
|
||||||
|
|
||||||
|
const SOURCE_LABELS: Record<string, string> = {
|
||||||
|
CREDIT_CARD: "Tarjeta",
|
||||||
|
CASH: "Efectivo",
|
||||||
|
TRANSFER: "Transferencias",
|
||||||
|
};
|
||||||
|
|
||||||
|
function HoverRows({
|
||||||
|
title,
|
||||||
|
rows,
|
||||||
|
}: {
|
||||||
|
title: string;
|
||||||
|
rows: { label: string; value: string }[];
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<p className="text-sm font-medium">{title}</p>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{rows.map((r) => (
|
||||||
|
<div key={r.label} className="flex justify-between gap-3 text-xs">
|
||||||
|
<span className="text-muted-foreground truncate">{r.label}</span>
|
||||||
|
<span data-sensitive className="font-mono shrink-0">
|
||||||
|
{r.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function GastoDelCicloCard() {
|
function GastoDelCicloCard() {
|
||||||
const q = useQuery({
|
const q = useQuery({
|
||||||
queryKey: ["budget", "month", CYCLE.year, CYCLE.month],
|
queryKey: ["budget", "month", CYCLE.year, CYCLE.month],
|
||||||
@@ -44,6 +74,25 @@ function GastoDelCicloCard() {
|
|||||||
loading={q.isPending}
|
loading={q.isPending}
|
||||||
to="/budget"
|
to="/budget"
|
||||||
ariaLabel="Ver presupuesto"
|
ariaLabel="Ver presupuesto"
|
||||||
|
hoverContent={
|
||||||
|
q.data ? (
|
||||||
|
<HoverRows
|
||||||
|
title="Desglose por fuente"
|
||||||
|
rows={[
|
||||||
|
...q.data.actuals_by_source
|
||||||
|
.filter((s) => s.count > 0)
|
||||||
|
.map((s) => ({
|
||||||
|
label: `${SOURCE_LABELS[s.source] ?? s.source} (${s.count})`,
|
||||||
|
value: formatAmount(s.net, "CRC"),
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
label: "Gran total egresos",
|
||||||
|
value: formatAmount(q.data.gran_total_egresos, "CRC"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{balance !== null && (
|
{balance !== null && (
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
@@ -105,6 +154,17 @@ function ProximasCuotasCard() {
|
|||||||
loading={q.isPending}
|
loading={q.isPending}
|
||||||
to="/financiamientos"
|
to="/financiamientos"
|
||||||
ariaLabel="Ver financiamientos"
|
ariaLabel="Ver financiamientos"
|
||||||
|
hoverContent={
|
||||||
|
upcoming.length > 0 ? (
|
||||||
|
<HoverRows
|
||||||
|
title="Planes activos"
|
||||||
|
rows={upcoming.map((p) => ({
|
||||||
|
label: `${p.merchant} (${p.cuotas_billed}/${p.num_installments})`,
|
||||||
|
value: `${formatShortDate(p.next_cuota_date!)} · ${formatAmount(nextCuotaOf(p), p.currency)}`,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{topThree.length > 0 && (
|
{topThree.length > 0 && (
|
||||||
<ul className="space-y-0.5 text-xs text-muted-foreground">
|
<ul className="space-y-0.5 text-xs text-muted-foreground">
|
||||||
@@ -146,6 +206,17 @@ function PensionCard() {
|
|||||||
loading={q.isPending}
|
loading={q.isPending}
|
||||||
to="/pensions"
|
to="/pensions"
|
||||||
ariaLabel="Ver pensiones"
|
ariaLabel="Ver pensiones"
|
||||||
|
hoverContent={
|
||||||
|
q.data && q.data.length > 0 ? (
|
||||||
|
<HoverRows
|
||||||
|
title="Último período por fondo"
|
||||||
|
rows={q.data.map((f) => ({
|
||||||
|
label: `${f.fund} · rendimientos`,
|
||||||
|
value: `+${formatAmount(f.rendimientos, "CRC")}`,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{q.data && q.data.length > 0 && (
|
{q.data && q.data.length > 0 && (
|
||||||
<ul className="space-y-0.5 text-xs text-muted-foreground">
|
<ul className="space-y-0.5 text-xs text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user