feat(cost): track Claude Code token usage and cost per project
- cc-collector.py: extract input/output/cache tokens from JSONL usage fields and calculate cost_usd using model-based pricing table - Session model: add input_tokens, output_tokens, cost_usd columns - Migration 0010: ALTER TABLE sessions ADD cost columns - Ingest: persist cost fields on upsert (updated on every sync) - Dashboard /projects: aggregate total_cost_usd per project from sessions - ProjectHours schema + ProjectSummary TS type: expose total_cost_usd - ProjectsView: replace Budget% column with "Cost $" showing total spend; Grid cards show CC Cost row when cost > 0 - backfill_session_costs.py: one-time script to populate cost for all historical sessions from local JSONL files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c3022c0c66
commit
fde9b61465
57 changed files with 390 additions and 131 deletions
25
alembic/versions/0010_session_cost.py
Normal file
25
alembic/versions/0010_session_cost.py
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"""Add input_tokens, output_tokens, cost_usd to sessions
|
||||
|
||||
Revision ID: 0010
|
||||
Revises: 0009
|
||||
Create Date: 2026-05-13
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "0010"
|
||||
down_revision = "0009"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("sessions", sa.Column("input_tokens", sa.Integer(), nullable=False, server_default="0"))
|
||||
op.add_column("sessions", sa.Column("output_tokens", sa.Integer(), nullable=False, server_default="0"))
|
||||
op.add_column("sessions", sa.Column("cost_usd", sa.Float(), nullable=False, server_default="0"))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("sessions", "cost_usd")
|
||||
op.drop_column("sessions", "output_tokens")
|
||||
op.drop_column("sessions", "input_tokens")
|
||||
221
scripts/backfill_session_costs.py
Normal file
221
scripts/backfill_session_costs.py
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Backfill cost_usd / token counts for all existing sessions.
|
||||
|
||||
Reads ALL JSONL files in ~/.claude/projects/ (no lookback limit),
|
||||
computes input_tokens / output_tokens / cost_usd per session-day bucket,
|
||||
and re-POSTs them to /api/ingest (ON CONFLICT DO UPDATE updates cost fields).
|
||||
|
||||
Usage:
|
||||
CC_API_KEY=cc_xxx CC_SERVER=https://optical-dev.oliver.solutions/cc-dashboard \
|
||||
python3 scripts/backfill_session_costs.py
|
||||
|
||||
Optional env vars (same as cc-collector.py):
|
||||
CC_ROOT_PATH — comma-separated project roots (default: $HOME)
|
||||
CC_SERVER — dashboard base URL
|
||||
CC_API_KEY — API key
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
SERVER = os.environ.get("CC_SERVER", "https://optical-dev.oliver.solutions/cc-dashboard").rstrip("/")
|
||||
API_KEY = os.environ.get("CC_API_KEY", "")
|
||||
_raw_root = os.environ.get("CC_ROOT_PATH", str(Path.home()))
|
||||
ROOT_PATHS = [p.strip() for p in _raw_root.split(",") if p.strip()]
|
||||
ROOT_PATH = ROOT_PATHS[0]
|
||||
|
||||
CLAUDE_PROJECTS = Path.home() / ".claude" / "projects"
|
||||
|
||||
# Pricing per million tokens: (input, output, cache_read, cache_creation)
|
||||
_MODEL_PRICING: dict[str, tuple[float, float, float, float]] = {
|
||||
"claude-opus-4": (15.0, 75.0, 1.50, 18.75),
|
||||
"claude-sonnet-4": (3.0, 15.0, 0.30, 3.75),
|
||||
"claude-haiku-4": (0.80, 4.0, 0.08, 1.00),
|
||||
"claude-opus-3": (15.0, 75.0, 1.50, 18.75),
|
||||
"claude-sonnet-3": (3.0, 15.0, 0.30, 3.75),
|
||||
"claude-haiku-3": (0.25, 1.25, 0.03, 0.30),
|
||||
}
|
||||
_DEFAULT_PRICING = (15.0, 75.0, 1.50, 18.75)
|
||||
|
||||
|
||||
def _get_pricing(model: str) -> tuple[float, float, float, float]:
|
||||
model = (model or "").lower()
|
||||
for key, price in _MODEL_PRICING.items():
|
||||
if key in model:
|
||||
return price
|
||||
return _DEFAULT_PRICING
|
||||
|
||||
|
||||
def _root_prefix(root_path: str) -> str:
|
||||
return root_path.rstrip("/").replace("/", "-").replace("_", "-")
|
||||
|
||||
|
||||
def _match_root(folder_key: str) -> str | None:
|
||||
for rp in ROOT_PATHS:
|
||||
prefix = _root_prefix(rp)
|
||||
if folder_key == prefix or folder_key.startswith(prefix + "-"):
|
||||
return rp
|
||||
return None
|
||||
|
||||
|
||||
def _infer_slug(folder_name: str, root_path: str) -> str:
|
||||
prefix = _root_prefix(root_path).lstrip("-")
|
||||
name = folder_name.lstrip("-")
|
||||
if name == prefix:
|
||||
return "general"
|
||||
if name.startswith(prefix + "-"):
|
||||
return name[len(prefix) + 1:]
|
||||
return name.split("-")[-1] or name
|
||||
|
||||
|
||||
def collect_all() -> list[dict]:
|
||||
if not CLAUDE_PROJECTS.exists():
|
||||
print("~/.claude/projects/ not found", file=sys.stderr)
|
||||
return []
|
||||
|
||||
sessions_to_send: list[dict] = []
|
||||
total_files = 0
|
||||
|
||||
for folder in sorted(CLAUDE_PROJECTS.iterdir()):
|
||||
if not folder.is_dir():
|
||||
continue
|
||||
matched_root = _match_root(folder.name)
|
||||
if matched_root is None:
|
||||
continue
|
||||
|
||||
slug = _infer_slug(folder.name, matched_root)
|
||||
raw_sessions: dict = defaultdict(lambda: {"timestamps": [], "messages": []})
|
||||
|
||||
for jf in sorted(folder.glob("*.jsonl")):
|
||||
total_files += 1
|
||||
try:
|
||||
with open(jf, encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
obj = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
ts = obj.get("timestamp")
|
||||
sid = obj.get("sessionId")
|
||||
if not ts or not sid:
|
||||
continue
|
||||
try:
|
||||
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
continue
|
||||
raw_sessions[sid]["timestamps"].append(dt)
|
||||
raw_sessions[sid]["messages"].append(obj)
|
||||
except Exception as e:
|
||||
print(f" Warning: could not read {jf}: {e}", file=sys.stderr)
|
||||
|
||||
for sid, data in raw_sessions.items():
|
||||
if not data["timestamps"]:
|
||||
continue
|
||||
|
||||
paired = sorted(zip(data["timestamps"], data["messages"]), key=lambda x: x[0])
|
||||
day_buckets: dict = defaultdict(lambda: {"timestamps": [], "messages": []})
|
||||
for dt, obj in paired:
|
||||
day_buckets[dt.strftime("%Y-%m-%d")]["timestamps"].append(dt)
|
||||
day_buckets[dt.strftime("%Y-%m-%d")]["messages"].append(obj)
|
||||
|
||||
for date_str, bucket in day_buckets.items():
|
||||
ts_sorted = bucket["timestamps"]
|
||||
start = ts_sorted[0]
|
||||
end = ts_sorted[-1]
|
||||
|
||||
# Count tokens
|
||||
input_tokens = output_tokens = 0
|
||||
cost_usd = 0.0
|
||||
for obj in bucket["messages"]:
|
||||
msg = obj.get("message", {})
|
||||
if not isinstance(msg, dict) or msg.get("role") != "assistant":
|
||||
continue
|
||||
usage = msg.get("usage")
|
||||
if not isinstance(usage, dict):
|
||||
continue
|
||||
model = msg.get("model", "")
|
||||
inp_p, out_p, cr_p, cc_p = _get_pricing(model)
|
||||
m = 1_000_000
|
||||
i = usage.get("input_tokens", 0)
|
||||
o = usage.get("output_tokens", 0)
|
||||
cr = usage.get("cache_read_input_tokens", 0)
|
||||
cc = usage.get("cache_creation_input_tokens", 0)
|
||||
input_tokens += i
|
||||
output_tokens += o
|
||||
cost_usd += i * inp_p / m + o * out_p / m + cr * cr_p / m + cc * cc_p / m
|
||||
|
||||
if input_tokens == 0 and output_tokens == 0:
|
||||
continue # skip sessions with no usage data
|
||||
|
||||
sessions_to_send.append({
|
||||
"session_id": sid,
|
||||
"project_slug": slug,
|
||||
"date": date_str,
|
||||
"start_at": start.isoformat(),
|
||||
"end_at": end.isoformat(),
|
||||
"message_count": len(ts_sorted),
|
||||
"active_hours": 0.0, # not updating hours in backfill
|
||||
"work_summary": "",
|
||||
"commits": [],
|
||||
"tools_used": {},
|
||||
"files_changed": [],
|
||||
"repo_url": "",
|
||||
"raw_stats": {},
|
||||
"input_tokens": input_tokens,
|
||||
"output_tokens": output_tokens,
|
||||
"cost_usd": round(cost_usd, 6),
|
||||
})
|
||||
|
||||
print(f"Scanned {total_files} JSONL files, found {len(sessions_to_send)} session-days with token data")
|
||||
return sessions_to_send
|
||||
|
||||
|
||||
def send_batch(sessions: list[dict]) -> None:
|
||||
import urllib.request
|
||||
BATCH = 50
|
||||
total_accepted = total_skipped = 0
|
||||
|
||||
for i in range(0, len(sessions), BATCH):
|
||||
batch = sessions[i:i + BATCH]
|
||||
payload = json.dumps({"root_path": ROOT_PATH, "sessions": batch}).encode()
|
||||
req = urllib.request.Request(
|
||||
f"{SERVER}/api/ingest",
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json", "X-API-Key": API_KEY},
|
||||
method="POST",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
result = json.loads(resp.read())
|
||||
total_accepted += result.get("accepted", 0)
|
||||
total_skipped += result.get("skipped", 0)
|
||||
print(f" Batch {i // BATCH + 1}: accepted={result.get('accepted', 0)}, skipped={result.get('skipped', 0)}")
|
||||
except Exception as e:
|
||||
print(f" Batch {i // BATCH + 1} failed: {e}", file=sys.stderr)
|
||||
|
||||
print(f"\nDone. Total accepted={total_accepted}, skipped={total_skipped}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if not API_KEY:
|
||||
raise SystemExit("CC_API_KEY not set")
|
||||
|
||||
print(f"Server: {SERVER}")
|
||||
print(f"Root paths: {ROOT_PATHS}\n")
|
||||
|
||||
sessions = collect_all()
|
||||
if not sessions:
|
||||
print("No sessions with token data found — nothing to send.")
|
||||
sys.exit(0)
|
||||
|
||||
total_cost = sum(s["cost_usd"] for s in sessions)
|
||||
print(f"Total estimated cost across all sessions: ${total_cost:.4f}\n")
|
||||
|
||||
send_batch(sessions)
|
||||
|
|
@ -97,6 +97,9 @@ class Session(Base):
|
|||
tools_used: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||
files_changed: Mapped[list] = mapped_column(JSONB, default=list)
|
||||
raw_stats: Mapped[dict] = mapped_column(JSONB, default=dict)
|
||||
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
||||
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
|
||||
cost_usd: Mapped[float] = mapped_column(Float, default=0.0)
|
||||
task_id: Mapped[str | None] = mapped_column(UUID(as_uuid=False), ForeignKey("tasks.id", ondelete="SET NULL"), nullable=True, index=True)
|
||||
category: Mapped[str | None] = mapped_column(String(20), nullable=True)
|
||||
ai_title: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||
|
|
|
|||
|
|
@ -160,8 +160,23 @@ async def projects_overview(
|
|||
if not proj_data[pid]["last_active"] or stat.date > proj_data[pid]["last_active"]:
|
||||
proj_data[pid]["last_active"] = stat.date
|
||||
|
||||
# Batch-load project budgets
|
||||
# Aggregate cost_usd per project from sessions
|
||||
project_ids = list(proj_data.keys())
|
||||
cost_map: dict[str, float] = {}
|
||||
if project_ids:
|
||||
cost_filter = [Session.project_id.in_(project_ids), Session.user_id == user.id]
|
||||
if date_clauses:
|
||||
cost_filter.append(Session.date >= from_date)
|
||||
cost_filter.append(Session.date <= to_date)
|
||||
cost_result = await db.execute(
|
||||
select(Session.project_id, func.sum(Session.cost_usd))
|
||||
.where(*cost_filter)
|
||||
.group_by(Session.project_id)
|
||||
)
|
||||
for pid, total_cost in cost_result.all():
|
||||
cost_map[pid] = float(total_cost or 0)
|
||||
|
||||
# Batch-load project budgets
|
||||
budget_map: dict[str, float] = {}
|
||||
if project_ids:
|
||||
budget_result = await db.execute(
|
||||
|
|
@ -193,6 +208,7 @@ async def projects_overview(
|
|||
if v["project_id"] in budget_map and budget_map[v["project_id"]] > 0
|
||||
else None
|
||||
),
|
||||
total_cost_usd=round(cost_map.get(v["project_id"], 0.0), 4),
|
||||
)
|
||||
for v in proj_data.values()
|
||||
],
|
||||
|
|
|
|||
|
|
@ -66,6 +66,9 @@ async def ingest(
|
|||
tools_used=s.tools_used,
|
||||
files_changed=s.files_changed,
|
||||
raw_stats=s.raw_stats,
|
||||
input_tokens=s.input_tokens,
|
||||
output_tokens=s.output_tokens,
|
||||
cost_usd=s.cost_usd,
|
||||
).on_conflict_do_update(
|
||||
constraint="uq_session_user_date",
|
||||
set_=dict(
|
||||
|
|
@ -77,6 +80,9 @@ async def ingest(
|
|||
tools_used=s.tools_used,
|
||||
files_changed=s.files_changed,
|
||||
raw_stats=s.raw_stats,
|
||||
input_tokens=s.input_tokens,
|
||||
output_tokens=s.output_tokens,
|
||||
cost_usd=s.cost_usd,
|
||||
),
|
||||
)
|
||||
result = await db.execute(stmt)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,9 @@ class SessionPayload(BaseModel):
|
|||
files_changed: list[str] = []
|
||||
repo_url: str = ""
|
||||
raw_stats: dict[str, Any] = {}
|
||||
input_tokens: int = 0
|
||||
output_tokens: int = 0
|
||||
cost_usd: float = 0.0
|
||||
|
||||
|
||||
class IngestPayload(BaseModel):
|
||||
|
|
@ -123,6 +126,7 @@ class ProjectHours(BaseModel):
|
|||
repo_url: str = ""
|
||||
budget_hours: float | None = None
|
||||
progress_pct: float | None = None
|
||||
total_cost_usd: float = 0.0
|
||||
|
||||
|
||||
class DailyPoint(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
import{d as _,u as y,A as h,c as r,a as t,e as n,k as v,w as d,f as g,s as m,o as s,F as b,r as k,t as a,q as u,h as A}from"./index-u75TGs9I.js";import{a as w}from"./admin-DEtYu2mm.js";import{_ as B,a as S}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import{_ as f}from"./Badge.vue_vue_type_script_setup_true_lang-CYzAkUY0.js";import{_ as V}from"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";import{a as $}from"./utils-7WVCegLb.js";const N={class:"p-6 space-y-8"},C={key:0,class:"flex items-center justify-center h-20"},D={class:"w-full"},E={class:"px-4 py-3"},F={class:"text-sm font-medium text-foreground"},R={class:"px-4 py-3 text-sm text-muted-foreground"},U={class:"px-4 py-3"},j={class:"px-4 py-3"},q={class:"px-4 py-3 text-xs text-muted-foreground"},H=_({__name:"AdminView",setup(I){const x=y(),p=g(),i=m([]),l=m(!1);return h(async()=>{if(!x.isAdmin){p.push("/");return}l.value=!0;try{const c=await w.users();i.value=c.data}finally{l.value=!1}}),(c,o)=>(s(),r("div",N,[o[1]||(o[1]=t("h2",{class:"text-lg font-semibold text-foreground"},"Admin — Users",-1)),l.value?(s(),r("div",C,[n(V,{class:"text-primary"})])):(s(),v(B,{key:1},{default:d(()=>[n(S,{class:"p-0"},{default:d(()=>[t("table",D,[o[0]||(o[0]=t("thead",null,[t("tr",{class:"border-b border-border"},[t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"User"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Email"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Role"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Status"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Joined")])],-1)),t("tbody",null,[(s(!0),r(b,null,k(i.value,e=>(s(),r("tr",{key:e.id,class:"border-b border-border last:border-0 hover:bg-muted/30"},[t("td",E,[t("p",F,a(e.username),1)]),t("td",R,a(e.email),1),t("td",U,[n(f,{variant:e.role==="admin"?"default":"secondary",class:"text-xs"},{default:d(()=>[u(a(e.role),1)]),_:2},1032,["variant"])]),t("td",j,[n(f,{variant:e.is_active?"success":"outline",class:"text-xs"},{default:d(()=>[u(a(e.is_active?"Active":"Inactive"),1)]),_:2},1032,["variant"])]),t("td",q,a(A($)(e.created_at)),1)]))),128))])])]),_:1})]),_:1}))]))}});export{H as default};
|
||||
import{d as _,u as y,A as h,c as r,a as t,e as n,k as v,w as d,f as g,s as m,o as s,F as b,r as k,t as a,q as u,h as A}from"./index-rN5A-UTe.js";import{a as w}from"./admin-Bt3HG9mf.js";import{_ as B,a as S}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import{_ as f}from"./Badge.vue_vue_type_script_setup_true_lang-igExkyOK.js";import{_ as V}from"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";import{a as $}from"./utils-7WVCegLb.js";const N={class:"p-6 space-y-8"},C={key:0,class:"flex items-center justify-center h-20"},D={class:"w-full"},E={class:"px-4 py-3"},F={class:"text-sm font-medium text-foreground"},R={class:"px-4 py-3 text-sm text-muted-foreground"},U={class:"px-4 py-3"},j={class:"px-4 py-3"},q={class:"px-4 py-3 text-xs text-muted-foreground"},H=_({__name:"AdminView",setup(I){const x=y(),p=g(),i=m([]),l=m(!1);return h(async()=>{if(!x.isAdmin){p.push("/");return}l.value=!0;try{const c=await w.users();i.value=c.data}finally{l.value=!1}}),(c,o)=>(s(),r("div",N,[o[1]||(o[1]=t("h2",{class:"text-lg font-semibold text-foreground"},"Admin — Users",-1)),l.value?(s(),r("div",C,[n(V,{class:"text-primary"})])):(s(),v(B,{key:1},{default:d(()=>[n(S,{class:"p-0"},{default:d(()=>[t("table",D,[o[0]||(o[0]=t("thead",null,[t("tr",{class:"border-b border-border"},[t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"User"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Email"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Role"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Status"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Joined")])],-1)),t("tbody",null,[(s(!0),r(b,null,k(i.value,e=>(s(),r("tr",{key:e.id,class:"border-b border-border last:border-0 hover:bg-muted/30"},[t("td",E,[t("p",F,a(e.username),1)]),t("td",R,a(e.email),1),t("td",U,[n(f,{variant:e.role==="admin"?"default":"secondary",class:"text-xs"},{default:d(()=>[u(a(e.role),1)]),_:2},1032,["variant"])]),t("td",j,[n(f,{variant:e.is_active?"success":"outline",class:"text-xs"},{default:d(()=>[u(a(e.is_active?"Active":"Inactive"),1)]),_:2},1032,["variant"])]),t("td",q,a(A($)(e.created_at)),1)]))),128))])])]),_:1})]),_:1}))]))}});export{H as default};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{c as a}from"./utils-7WVCegLb.js";import{d as n,o,c as s,n as d,h as i,p as c}from"./index-u75TGs9I.js";const f=n({__name:"Badge",props:{variant:{default:"default"},class:{}},setup(r){const e=r;return(t,l)=>(o(),s("span",{class:d(i(a)("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors",{"bg-primary text-primary-foreground":e.variant==="default","bg-secondary text-secondary-foreground":e.variant==="secondary","bg-destructive text-destructive-foreground":e.variant==="destructive","border border-border text-foreground":e.variant==="outline","bg-emerald-500/20 text-emerald-400":e.variant==="success","bg-amber-500/20 text-amber-400":e.variant==="warning"},e.class))},[c(t.$slots,"default")],2))}});export{f as _};
|
||||
import{c as a}from"./utils-7WVCegLb.js";import{d as n,o,c as s,n as d,h as i,p as c}from"./index-rN5A-UTe.js";const f=n({__name:"Badge",props:{variant:{default:"default"},class:{}},setup(r){const e=r;return(t,l)=>(o(),s("span",{class:d(i(a)("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors",{"bg-primary text-primary-foreground":e.variant==="default","bg-secondary text-secondary-foreground":e.variant==="secondary","bg-destructive text-destructive-foreground":e.variant==="destructive","border border-border text-foreground":e.variant==="outline","bg-emerald-500/20 text-emerald-400":e.variant==="success","bg-amber-500/20 text-amber-400":e.variant==="warning"},e.class))},[c(t.$slots,"default")],2))}});export{f as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{_ as c}from"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";import{c as l}from"./utils-7WVCegLb.js";import{d as u,c as f,n as m,k as b,i as v,p as g,j as p,o as n}from"./index-u75TGs9I.js";const y=["type","disabled"],z=u({__name:"Button",props:{variant:{default:"default"},size:{default:"md"},loading:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},type:{default:"button"},class:{}},emits:["click"],setup(t,{emit:s}){const e=t,a=s,r=p(()=>l("inline-flex items-center justify-center rounded-md font-medium transition-colors","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:pointer-events-none disabled:opacity-50",{"bg-primary text-primary-foreground hover:bg-primary/90":e.variant==="default","border border-input bg-background hover:bg-accent hover:text-accent-foreground":e.variant==="outline","hover:bg-accent hover:text-accent-foreground":e.variant==="ghost","bg-destructive text-destructive-foreground hover:bg-destructive/90":e.variant==="destructive","bg-secondary text-secondary-foreground hover:bg-secondary/80":e.variant==="secondary","underline-offset-4 hover:underline text-primary":e.variant==="link","h-8 px-3 text-xs":e.size==="sm","h-10 px-4 py-2 text-sm":e.size==="md","h-11 px-8 text-base":e.size==="lg","h-10 w-10 p-0":e.size==="icon"},e.class));return(i,o)=>(n(),f("button",{class:m(r.value),type:t.type,disabled:t.disabled||t.loading,onClick:o[0]||(o[0]=d=>a("click",d))},[t.loading?(n(),b(c,{key:0,size:"sm",class:"mr-2"})):v("",!0),g(i.$slots,"default")],10,y))}});export{z as _};
|
||||
import{_ as c}from"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";import{c as l}from"./utils-7WVCegLb.js";import{d as u,c as f,n as m,k as b,i as v,p as g,j as p,o as n}from"./index-rN5A-UTe.js";const y=["type","disabled"],z=u({__name:"Button",props:{variant:{default:"default"},size:{default:"md"},loading:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},type:{default:"button"},class:{}},emits:["click"],setup(t,{emit:s}){const e=t,a=s,r=p(()=>l("inline-flex items-center justify-center rounded-md font-medium transition-colors","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:pointer-events-none disabled:opacity-50",{"bg-primary text-primary-foreground hover:bg-primary/90":e.variant==="default","border border-input bg-background hover:bg-accent hover:text-accent-foreground":e.variant==="outline","hover:bg-accent hover:text-accent-foreground":e.variant==="ghost","bg-destructive text-destructive-foreground hover:bg-destructive/90":e.variant==="destructive","bg-secondary text-secondary-foreground hover:bg-secondary/80":e.variant==="secondary","underline-offset-4 hover:underline text-primary":e.variant==="link","h-8 px-3 text-xs":e.size==="sm","h-10 px-4 py-2 text-sm":e.size==="md","h-11 px-8 text-base":e.size==="lg","h-10 w-10 p-0":e.size==="icon"},e.class));return(i,o)=>(n(),f("button",{class:m(r.value),type:t.type,disabled:t.disabled||t.loading,onClick:o[0]||(o[0]=d=>a("click",d))},[t.loading?(n(),b(c,{key:0,size:"sm",class:"mr-2"})):v("",!0),g(i.$slots,"default")],10,y))}});export{z as _};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{c as e}from"./utils-7WVCegLb.js";import{d as o,c as n,n as t,h as c,p,o as l}from"./index-u75TGs9I.js";const _=o({__name:"Card",props:{class:{}},setup(s){const a=s;return(r,d)=>(l(),n("div",{class:t(c(e)("rounded-lg border bg-card text-card-foreground shadow-sm",a.class))},[p(r.$slots,"default")],2))}}),f=o({__name:"CardContent",props:{class:{}},setup(s){const a=s;return(r,d)=>(l(),n("div",{class:t(c(e)("p-6 pt-0",a.class))},[p(r.$slots,"default")],2))}});export{_,f as a};
|
||||
import{c as e}from"./utils-7WVCegLb.js";import{d as o,c as n,n as t,h as c,p,o as l}from"./index-rN5A-UTe.js";const _=o({__name:"Card",props:{class:{}},setup(s){const a=s;return(r,d)=>(l(),n("div",{class:t(c(e)("rounded-lg border bg-card text-card-foreground shadow-sm",a.class))},[p(r.$slots,"default")],2))}}),f=o({__name:"CardContent",props:{class:{}},setup(s){const a=s;return(r,d)=>(l(),n("div",{class:t(c(e)("p-6 pt-0",a.class))},[p(r.$slots,"default")],2))}});export{_,f as a};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as t}from"./utils-7WVCegLb.js";import{d as n,o,c as r,n as c,h as l,p}from"./index-u75TGs9I.js";const f=n({__name:"CardHeader",props:{class:{}},setup(s){const e=s;return(a,i)=>(o(),r("div",{class:c(l(t)("flex flex-col space-y-1.5 p-6",e.class))},[p(a.$slots,"default")],2))}}),_=n({__name:"CardTitle",props:{class:{}},setup(s){const e=s;return(a,i)=>(o(),r("h3",{class:c(l(t)("text-lg font-semibold leading-none tracking-tight",e.class))},[p(a.$slots,"default")],2))}});export{f as _,_ as a};
|
||||
import{c as t}from"./utils-7WVCegLb.js";import{d as n,o,c as r,n as c,h as l,p}from"./index-rN5A-UTe.js";const f=n({__name:"CardHeader",props:{class:{}},setup(s){const e=s;return(a,i)=>(o(),r("div",{class:c(l(t)("flex flex-col space-y-1.5 p-6",e.class))},[p(a.$slots,"default")],2))}}),_=n({__name:"CardTitle",props:{class:{}},setup(s){const e=s;return(a,i)=>(o(),r("h3",{class:c(l(t)("text-lg font-semibold leading-none tracking-tight",e.class))},[p(a.$slots,"default")],2))}});export{f as _,_ as a};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{d as b,k,w as a,h as o,X as y,o as u,e as t,Y as g,Z as h,a as i,$ as C,q as s,t as c,a0 as T,c as w,i as V,a1 as B,a2 as L,a3 as N,s as z}from"./index-u75TGs9I.js";import{_ as m}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_}from"./Input.vue_vue_type_script_setup_true_lang-C5UKCg8E.js";import{c as A}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{d as b,k,w as a,h as o,X as y,o as u,e as t,Y as g,Z as h,a as i,$ as C,q as s,t as c,a0 as T,c as w,i as V,a1 as B,a2 as L,a3 as N,s as z}from"./index-rN5A-UTe.js";import{_ as m}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_}from"./Input.vue_vue_type_script_setup_true_lang-D00Gss2e.js";import{c as A}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
31
src/static/assets/DashboardView-CmARPWJW.js
Normal file
31
src/static/assets/DashboardView-CmARPWJW.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{u as b}from"./devops-B4bZeW4f.js";import{_ as y}from"./Input.vue_vue_type_script_setup_true_lang-C5UKCg8E.js";import{_ as k}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as j}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-VHSjeswj.js";import{d as z,s as i,o as d,c as p,F,a as o,h as n,q as v,t as u,i as g,e as c,w,k as U,K as m}from"./index-u75TGs9I.js";const A={class:"space-y-4"},B={key:0,class:"text-xs text-muted-foreground space-y-1"},I={class:"text-foreground"},N={class:"text-foreground"},O={key:0},P={key:1,class:"text-red-400"},S={class:"grid grid-cols-2 gap-3"},T={class:"space-y-1.5"},$={class:"space-y-1.5"},q={class:"space-y-1.5"},E={class:"flex items-center gap-2"},Q=z({__name:"DevopsConnectForm",setup(K){var x,V;const t=b(),r=i(((x=t.integration)==null?void 0:x.organization)??""),l=i(((V=t.integration)==null?void 0:V.project)??""),s=i(""),f=i(!1),_=i(!1);async function C(){if(!r.value||!l.value||!s.value){m.error("All fields are required");return}f.value=!0;try{await t.saveIntegration({organization:r.value,project:l.value,pat:s.value}),s.value="",m.success("Integration saved")}catch{m.error("Failed to save integration")}finally{f.value=!1}}async function D(){try{await t.deleteIntegration(),r.value="",l.value="",s.value="",m.success("Integration deleted")}catch{m.error("Failed to delete integration")}}return(L,e)=>(d(),p(F,null,[o("div",A,[n(t).integration?(d(),p("div",B,[o("p",null,[e[5]||(e[5]=v(" Connected to ",-1)),o("strong",I,u(n(t).integration.organization),1),e[6]||(e[6]=v(" / ",-1)),o("strong",N,u(n(t).integration.project),1)]),n(t).integration.last_synced_at?(d(),p("p",O," Last synced: "+u(new Date(n(t).integration.last_synced_at).toLocaleString()),1)):g("",!0),n(t).integration.last_sync_error?(d(),p("p",P," Error: "+u(n(t).integration.last_sync_error),1)):g("",!0)])):g("",!0),o("div",S,[o("div",T,[e[7]||(e[7]=o("label",{class:"text-sm font-medium text-foreground"},"Organization",-1)),c(y,{modelValue:r.value,"onUpdate:modelValue":e[0]||(e[0]=a=>r.value=a),placeholder:"myorg"},null,8,["modelValue"])]),o("div",$,[e[8]||(e[8]=o("label",{class:"text-sm font-medium text-foreground"},"Project",-1)),c(y,{modelValue:l.value,"onUpdate:modelValue":e[1]||(e[1]=a=>l.value=a),placeholder:"myproject"},null,8,["modelValue"])])]),o("div",q,[e[9]||(e[9]=o("label",{class:"text-sm font-medium text-foreground"}," Personal Access Token ",-1)),c(y,{modelValue:s.value,"onUpdate:modelValue":e[2]||(e[2]=a=>s.value=a),type:"password",placeholder:"••••••••",autocomplete:"new-password"},null,8,["modelValue"])]),o("div",E,[c(k,{loading:f.value,onClick:C},{default:w(()=>[v(u(n(t).integration?"Update":"Connect"),1)]),_:1},8,["loading"]),n(t).integration?(d(),U(k,{key:0,variant:"destructive",size:"sm",onClick:e[3]||(e[3]=a=>_.value=!0)},{default:w(()=>[...e[10]||(e[10]=[v(" Disconnect ",-1)])]),_:1})):g("",!0)])]),c(j,{open:_.value,"onUpdate:open":e[4]||(e[4]=a=>_.value=a),title:"Disconnect Azure DevOps",description:"This will remove the ADO integration and all synced work items. This action cannot be undone.","confirm-label":"Disconnect","cancel-label":"Cancel",variant:"destructive",onConfirm:D},null,8,["open"])],64))}});export{Q as _};
|
||||
import{u as b}from"./devops-DzkXDmpg.js";import{_ as y}from"./Input.vue_vue_type_script_setup_true_lang-D00Gss2e.js";import{_ as k}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as j}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-NFERUoaY.js";import{d as z,s as i,o as d,c as p,F,a as o,h as n,q as v,t as u,i as g,e as c,w,k as U,K as m}from"./index-rN5A-UTe.js";const A={class:"space-y-4"},B={key:0,class:"text-xs text-muted-foreground space-y-1"},I={class:"text-foreground"},N={class:"text-foreground"},O={key:0},P={key:1,class:"text-red-400"},S={class:"grid grid-cols-2 gap-3"},T={class:"space-y-1.5"},$={class:"space-y-1.5"},q={class:"space-y-1.5"},E={class:"flex items-center gap-2"},Q=z({__name:"DevopsConnectForm",setup(K){var x,V;const t=b(),r=i(((x=t.integration)==null?void 0:x.organization)??""),l=i(((V=t.integration)==null?void 0:V.project)??""),s=i(""),f=i(!1),_=i(!1);async function C(){if(!r.value||!l.value||!s.value){m.error("All fields are required");return}f.value=!0;try{await t.saveIntegration({organization:r.value,project:l.value,pat:s.value}),s.value="",m.success("Integration saved")}catch{m.error("Failed to save integration")}finally{f.value=!1}}async function D(){try{await t.deleteIntegration(),r.value="",l.value="",s.value="",m.success("Integration deleted")}catch{m.error("Failed to delete integration")}}return(L,e)=>(d(),p(F,null,[o("div",A,[n(t).integration?(d(),p("div",B,[o("p",null,[e[5]||(e[5]=v(" Connected to ",-1)),o("strong",I,u(n(t).integration.organization),1),e[6]||(e[6]=v(" / ",-1)),o("strong",N,u(n(t).integration.project),1)]),n(t).integration.last_synced_at?(d(),p("p",O," Last synced: "+u(new Date(n(t).integration.last_synced_at).toLocaleString()),1)):g("",!0),n(t).integration.last_sync_error?(d(),p("p",P," Error: "+u(n(t).integration.last_sync_error),1)):g("",!0)])):g("",!0),o("div",S,[o("div",T,[e[7]||(e[7]=o("label",{class:"text-sm font-medium text-foreground"},"Organization",-1)),c(y,{modelValue:r.value,"onUpdate:modelValue":e[0]||(e[0]=a=>r.value=a),placeholder:"myorg"},null,8,["modelValue"])]),o("div",$,[e[8]||(e[8]=o("label",{class:"text-sm font-medium text-foreground"},"Project",-1)),c(y,{modelValue:l.value,"onUpdate:modelValue":e[1]||(e[1]=a=>l.value=a),placeholder:"myproject"},null,8,["modelValue"])])]),o("div",q,[e[9]||(e[9]=o("label",{class:"text-sm font-medium text-foreground"}," Personal Access Token ",-1)),c(y,{modelValue:s.value,"onUpdate:modelValue":e[2]||(e[2]=a=>s.value=a),type:"password",placeholder:"••••••••",autocomplete:"new-password"},null,8,["modelValue"])]),o("div",E,[c(k,{loading:f.value,onClick:C},{default:w(()=>[v(u(n(t).integration?"Update":"Connect"),1)]),_:1},8,["loading"]),n(t).integration?(d(),U(k,{key:0,variant:"destructive",size:"sm",onClick:e[3]||(e[3]=a=>_.value=!0)},{default:w(()=>[...e[10]||(e[10]=[v(" Disconnect ",-1)])]),_:1})):g("",!0)])]),c(j,{open:_.value,"onUpdate:open":e[4]||(e[4]=a=>_.value=a),title:"Disconnect Azure DevOps",description:"This will remove the ADO integration and all synced work items. This action cannot be undone.","confirm-label":"Disconnect","cancel-label":"Cancel",variant:"destructive",onConfirm:D},null,8,["open"])],64))}});export{Q as _};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{x as h,y as b,d as w,A as x,k as g,L as E,e as v,T as C,w as p,o as c,c as u,a,p as m,i as f,n as L,s as $,t as y,M as B}from"./index-u75TGs9I.js";import{_ as T}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";const j=["[autofocus]","button:not([disabled])","[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");function D(t,d){let r=null;function i(){return t.value?Array.from(t.value.querySelectorAll(j)):[]}function l(s){if(!d.value||!t.value||s.key!=="Tab")return;const e=i();if(!e.length){s.preventDefault();return}const o=e[0],n=e[e.length-1];s.shiftKey?document.activeElement===o&&(s.preventDefault(),n.focus()):document.activeElement===n&&(s.preventDefault(),o.focus())}h(d,s=>{if(s)r=document.activeElement,document.addEventListener("keydown",l),setTimeout(()=>{const e=i();e.length&&e[0].focus()},50);else{document.removeEventListener("keydown",l);const e=r;setTimeout(()=>{e&&"focus"in e&&e.focus()},150)}}),b(()=>{document.removeEventListener("keydown",l)})}const z={key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4"},A=["aria-label"],F={key:0,class:"flex items-center justify-between p-6 pb-4"},M={class:"text-lg font-semibold text-foreground"},S={key:0,class:"text-sm text-muted-foreground mt-1"},K={class:"px-6 pb-4 max-h-[85vh] overflow-y-auto"},N={key:1,class:"flex justify-end gap-2 px-6 pb-6"},W=w({__name:"Dialog",props:{open:{type:Boolean},title:{},description:{},maxWidth:{default:"max-w-lg"}},emits:["close"],setup(t,{emit:d}){const r=t,i=d,l=$(null),s=B(r,"open");D(l,s);function e(o){o.key==="Escape"&&r.open&&i("close")}return x(()=>document.addEventListener("keydown",e)),b(()=>document.removeEventListener("keydown",e)),(o,n)=>(c(),g(E,{to:"body"},[v(C,{"enter-active-class":"transition-opacity duration-200","enter-from-class":"opacity-0","enter-to-class":"opacity-100","leave-active-class":"transition-opacity duration-200","leave-from-class":"opacity-100","leave-to-class":"opacity-0"},{default:p(()=>[t.open?(c(),u("div",z,[a("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:n[0]||(n[0]=k=>i("close"))}),a("div",{ref_key:"contentRef",ref:l,class:L(["relative w-full bg-card border border-border rounded-lg shadow-xl z-10",t.maxWidth]),role:"dialog","aria-modal":"true","aria-label":t.title},[t.title||o.$slots.header?(c(),u("div",F,[a("div",null,[m(o.$slots,"header",{},()=>[a("h2",M,y(t.title),1),t.description?(c(),u("p",S,y(t.description),1)):f("",!0)])]),v(T,{variant:"ghost",size:"icon",class:"shrink-0",onClick:n[1]||(n[1]=k=>i("close"))},{default:p(()=>[...n[2]||(n[2]=[a("svg",{class:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[a("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])]),_:1})])):f("",!0),a("div",K,[m(o.$slots,"default")]),o.$slots.footer?(c(),u("div",N,[m(o.$slots,"footer")])):f("",!0)],10,A)])):f("",!0)]),_:3})]))}});export{W as _};
|
||||
import{x as h,y as b,d as w,A as x,k as g,L as E,e as v,T as C,w as p,o as c,c as u,a,p as m,i as f,n as L,s as $,t as y,M as B}from"./index-rN5A-UTe.js";import{_ as T}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";const j=["[autofocus]","button:not([disabled])","[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");function D(t,d){let r=null;function i(){return t.value?Array.from(t.value.querySelectorAll(j)):[]}function l(s){if(!d.value||!t.value||s.key!=="Tab")return;const e=i();if(!e.length){s.preventDefault();return}const o=e[0],n=e[e.length-1];s.shiftKey?document.activeElement===o&&(s.preventDefault(),n.focus()):document.activeElement===n&&(s.preventDefault(),o.focus())}h(d,s=>{if(s)r=document.activeElement,document.addEventListener("keydown",l),setTimeout(()=>{const e=i();e.length&&e[0].focus()},50);else{document.removeEventListener("keydown",l);const e=r;setTimeout(()=>{e&&"focus"in e&&e.focus()},150)}}),b(()=>{document.removeEventListener("keydown",l)})}const z={key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4"},A=["aria-label"],F={key:0,class:"flex items-center justify-between p-6 pb-4"},M={class:"text-lg font-semibold text-foreground"},S={key:0,class:"text-sm text-muted-foreground mt-1"},K={class:"px-6 pb-4 max-h-[85vh] overflow-y-auto"},N={key:1,class:"flex justify-end gap-2 px-6 pb-6"},W=w({__name:"Dialog",props:{open:{type:Boolean},title:{},description:{},maxWidth:{default:"max-w-lg"}},emits:["close"],setup(t,{emit:d}){const r=t,i=d,l=$(null),s=B(r,"open");D(l,s);function e(o){o.key==="Escape"&&r.open&&i("close")}return x(()=>document.addEventListener("keydown",e)),b(()=>document.removeEventListener("keydown",e)),(o,n)=>(c(),g(E,{to:"body"},[v(C,{"enter-active-class":"transition-opacity duration-200","enter-from-class":"opacity-0","enter-to-class":"opacity-100","leave-active-class":"transition-opacity duration-200","leave-from-class":"opacity-100","leave-to-class":"opacity-0"},{default:p(()=>[t.open?(c(),u("div",z,[a("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:n[0]||(n[0]=k=>i("close"))}),a("div",{ref_key:"contentRef",ref:l,class:L(["relative w-full bg-card border border-border rounded-lg shadow-xl z-10",t.maxWidth]),role:"dialog","aria-modal":"true","aria-label":t.title},[t.title||o.$slots.header?(c(),u("div",F,[a("div",null,[m(o.$slots,"header",{},()=>[a("h2",M,y(t.title),1),t.description?(c(),u("p",S,y(t.description),1)):f("",!0)])]),v(T,{variant:"ghost",size:"icon",class:"shrink-0",onClick:n[1]||(n[1]=k=>i("close"))},{default:p(()=>[...n[2]||(n[2]=[a("svg",{class:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[a("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])]),_:1})])):f("",!0),a("div",K,[m(o.$slots,"default")]),o.$slots.footer?(c(),u("div",N,[m(o.$slots,"footer")])):f("",!0)],10,A)])):f("",!0)]),_:3})]))}});export{W as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as s}from"./utils-7WVCegLb.js";import{_ as k}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{d as w,c as a,n as o,h as r,F as S,r as B,i as n,a as m,t as i,k as l,w as L,o as t,l as f,q as j}from"./index-u75TGs9I.js";const I=["aria-label"],N={key:0,class:"relative mb-6 flex items-end justify-center gap-2"},V={class:"space-y-2"},$=w({__name:"EmptyState",props:{title:{},description:{},icons:{},actionLabel:{},actionIcon:{},size:{default:"default"}},emits:["action"],setup(e,{emit:x}){const g=x,h={sm:"p-6",default:"p-8",lg:"p-12"},b={sm:"h-9 w-9",default:"h-11 w-11",lg:"h-14 w-14"},v=["z-10 translate-y-1 -rotate-6 group-hover:-translate-x-3 group-hover:-translate-y-1 group-hover:-rotate-12","z-20 group-hover:-translate-y-3","z-10 translate-y-1 rotate-6 group-hover:translate-x-3 group-hover:-translate-y-1 group-hover:rotate-12"],y={sm:"text-sm",default:"text-base",lg:"text-lg"},z={sm:"text-xs",default:"text-sm",lg:"text-base"},C={sm:"h-7 text-xs px-3",default:"",lg:"h-11 text-base px-6"};return(p,c)=>(t(),a("div",{class:o(r(s)("group flex flex-col items-center justify-center text-center","rounded-xl border-2 border-dashed border-border bg-card","transition-all duration-300 hover:border-foreground/30",h[e.size])),role:"status","aria-label":e.title},[e.icons&&e.icons.length?(t(),a("div",N,[(t(!0),a(S,null,B(e.icons.slice(0,3),(d,u)=>(t(),a("div",{key:u,class:o(r(s)("flex items-center justify-center rounded-xl border border-border bg-background shadow-sm","text-muted-foreground transition-all duration-300",b[e.size],v[u]??"z-20"))},[(t(),l(f(d),{class:"h-5 w-5"}))],2))),128))])):n("",!0),m("div",V,[m("h3",{class:o(r(s)("font-semibold text-foreground",y[e.size]))},i(e.title),3),e.description?(t(),a("p",{key:0,class:o(r(s)("text-muted-foreground",z[e.size]))},i(e.description),3)):n("",!0)]),e.actionLabel?(t(),l(k,{key:1,variant:"outline",class:o(r(s)("mt-6",C[e.size])),onClick:c[0]||(c[0]=d=>g("action"))},{default:L(()=>[e.actionIcon?(t(),l(f(e.actionIcon),{key:0,class:"mr-2 h-4 w-4 transition-transform duration-200 group-hover/btn:rotate-90"})):n("",!0),j(" "+i(e.actionLabel),1)]),_:1},8,["class"])):n("",!0)],10,I))}});export{$ as _};
|
||||
import{c as s}from"./utils-7WVCegLb.js";import{_ as k}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{d as w,c as a,n as o,h as r,F as S,r as B,i as n,a as m,t as i,k as l,w as L,o as t,l as f,q as j}from"./index-rN5A-UTe.js";const I=["aria-label"],N={key:0,class:"relative mb-6 flex items-end justify-center gap-2"},V={class:"space-y-2"},$=w({__name:"EmptyState",props:{title:{},description:{},icons:{},actionLabel:{},actionIcon:{},size:{default:"default"}},emits:["action"],setup(e,{emit:x}){const g=x,h={sm:"p-6",default:"p-8",lg:"p-12"},b={sm:"h-9 w-9",default:"h-11 w-11",lg:"h-14 w-14"},v=["z-10 translate-y-1 -rotate-6 group-hover:-translate-x-3 group-hover:-translate-y-1 group-hover:-rotate-12","z-20 group-hover:-translate-y-3","z-10 translate-y-1 rotate-6 group-hover:translate-x-3 group-hover:-translate-y-1 group-hover:rotate-12"],y={sm:"text-sm",default:"text-base",lg:"text-lg"},z={sm:"text-xs",default:"text-sm",lg:"text-base"},C={sm:"h-7 text-xs px-3",default:"",lg:"h-11 text-base px-6"};return(p,c)=>(t(),a("div",{class:o(r(s)("group flex flex-col items-center justify-center text-center","rounded-xl border-2 border-dashed border-border bg-card","transition-all duration-300 hover:border-foreground/30",h[e.size])),role:"status","aria-label":e.title},[e.icons&&e.icons.length?(t(),a("div",N,[(t(!0),a(S,null,B(e.icons.slice(0,3),(d,u)=>(t(),a("div",{key:u,class:o(r(s)("flex items-center justify-center rounded-xl border border-border bg-background shadow-sm","text-muted-foreground transition-all duration-300",b[e.size],v[u]??"z-20"))},[(t(),l(f(d),{class:"h-5 w-5"}))],2))),128))])):n("",!0),m("div",V,[m("h3",{class:o(r(s)("font-semibold text-foreground",y[e.size]))},i(e.title),3),e.description?(t(),a("p",{key:0,class:o(r(s)("text-muted-foreground",z[e.size]))},i(e.description),3)):n("",!0)]),e.actionLabel?(t(),l(k,{key:1,variant:"outline",class:o(r(s)("mt-6",C[e.size])),onClick:c[0]||(c[0]=d=>g("action"))},{default:L(()=>[e.actionIcon?(t(),l(f(e.actionIcon),{key:0,class:"mr-2 h-4 w-4 transition-transform duration-200 group-hover/btn:rotate-90"})):n("",!0),j(" "+i(e.actionLabel),1)]),_:1},8,["class"])):n("",!0)],10,I))}});export{$ as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as i}from"./utils-7WVCegLb.js";import{d,c as s,n as u,h as m,o as r}from"./index-u75TGs9I.js";const c=["id","name","type","value","placeholder","disabled","autocomplete","min","max","step"],g=d({__name:"Input",props:{modelValue:{},type:{},placeholder:{},disabled:{type:Boolean},class:{},id:{},name:{},autocomplete:{},min:{},max:{},step:{}},emits:["update:modelValue","change","blur","focus"],setup(e,{emit:n}){const a=e,o=n;return(f,t)=>(r(),s("input",{id:e.id,name:e.name,type:e.type??"text",value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,autocomplete:e.autocomplete,min:e.min,max:e.max,step:e.step,class:u(m(i)("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm","ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium","placeholder:text-muted-foreground","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:cursor-not-allowed disabled:opacity-50",a.class)),onInput:t[0]||(t[0]=l=>o("update:modelValue",l.target.value)),onChange:t[1]||(t[1]=l=>o("change",l.target.value)),onBlur:t[2]||(t[2]=l=>o("blur",l)),onFocus:t[3]||(t[3]=l=>o("focus",l))},null,42,c))}});export{g as _};
|
||||
import{c as i}from"./utils-7WVCegLb.js";import{d,c as s,n as u,h as m,o as r}from"./index-rN5A-UTe.js";const c=["id","name","type","value","placeholder","disabled","autocomplete","min","max","step"],g=d({__name:"Input",props:{modelValue:{},type:{},placeholder:{},disabled:{type:Boolean},class:{},id:{},name:{},autocomplete:{},min:{},max:{},step:{}},emits:["update:modelValue","change","blur","focus"],setup(e,{emit:n}){const a=e,o=n;return(f,t)=>(r(),s("input",{id:e.id,name:e.name,type:e.type??"text",value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,autocomplete:e.autocomplete,min:e.min,max:e.max,step:e.step,class:u(m(i)("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm","ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium","placeholder:text-muted-foreground","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:cursor-not-allowed disabled:opacity-50",a.class)),onInput:t[0]||(t[0]=l=>o("update:modelValue",l.target.value)),onChange:t[1]||(t[1]=l=>o("change",l.target.value)),onBlur:t[2]||(t[2]=l=>o("blur",l)),onFocus:t[3]||(t[3]=l=>o("focus",l))},null,42,c))}});export{g as _};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{a as h}from"./admin-DEtYu2mm.js";import{_ as N,a as R}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import{_ as y}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as B}from"./Dialog.vue_vue_type_script_setup_true_lang-Bqyk3z93.js";import{_ as j}from"./Input.vue_vue_type_script_setup_true_lang-C5UKCg8E.js";import{_ as D}from"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";import{_ as F}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-VHSjeswj.js";import{_ as S}from"./EmptyState.vue_vue_type_script_setup_true_lang-DRkX1TTt.js";import{d as q,A as z,c as n,a as t,e as o,w as r,s as i,o as l,q as _,h as p,F as M,r as U,t as c,k as T,i as E,K as k}from"./index-u75TGs9I.js";import{a as V}from"./utils-7WVCegLb.js";import{c as A}from"./createLucideIcon-RaI43e07.js";import{P as G}from"./plus-1gCrPI5p.js";/**
|
||||
import{a as h}from"./admin-Bt3HG9mf.js";import{_ as N,a as R}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import{_ as y}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as B}from"./Dialog.vue_vue_type_script_setup_true_lang-5tIAaGh4.js";import{_ as j}from"./Input.vue_vue_type_script_setup_true_lang-D00Gss2e.js";import{_ as D}from"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";import{_ as F}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-NFERUoaY.js";import{_ as S}from"./EmptyState.vue_vue_type_script_setup_true_lang-Q76FMbX_.js";import{d as q,A as z,c as n,a as t,e as o,w as r,s as i,o as l,q as _,h as p,F as M,r as U,t as c,k as T,i as E,K as k}from"./index-rN5A-UTe.js";import{a as V}from"./utils-7WVCegLb.js";import{c as A}from"./createLucideIcon-BQqEiwZb.js";import{P as G}from"./plus-8OWFLnn5.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{y as J,s as b,d as M,u as O,A as V,c as f,a as i,n as k,h as o,t as v,k as N,w as g,i as C,e as _,o as l,q as j,F as B,r as F,j as I}from"./index-u75TGs9I.js";import{_ as R,a as z}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import{_ as L}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as S}from"./Skeleton.vue_vue_type_script_setup_true_lang-C4U7HVwG.js";import{_ as A}from"./EmptyState.vue_vue_type_script_setup_true_lang-DRkX1TTt.js";import{c as D}from"./createLucideIcon-RaI43e07.js";import{Z as U}from"./zap-DXlBOq5S.js";import"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";/**
|
||||
import{y as J,s as b,d as M,u as O,A as V,c as f,a as i,n as k,h as o,t as v,k as N,w as g,i as C,e as _,o as l,q as j,F as B,r as F,j as I}from"./index-rN5A-UTe.js";import{_ as R,a as z}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import{_ as L}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as S}from"./Skeleton.vue_vue_type_script_setup_true_lang-DvqtZljC.js";import{_ as A}from"./EmptyState.vue_vue_type_script_setup_true_lang-Q76FMbX_.js";import{c as D}from"./createLucideIcon-BQqEiwZb.js";import{Z as U}from"./zap-B0jfADxc.js";import"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as h,u as f,c as o,a as e,b as g,e as a,w as d,o as i,f as m,g as p,h as r,t as x,i as b}from"./index-u75TGs9I.js";import{_ as v,a as w}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import"./utils-7WVCegLb.js";const y={class:"min-h-screen flex items-center justify-center bg-background p-4"},_={class:"w-full max-w-sm"},k={class:"space-y-4"},C={key:0,class:"rounded-md bg-destructive/10 border border-destructive/30 px-3 py-2 text-sm text-destructive"},B=["disabled"],V={key:0},S={key:1},M=h({__name:"LoginView",setup(F){const l=m(),c=p(),s=f();async function u(){try{await s.loginWithMicrosoft();const n=c.query.redirect;l.push(n??"/")}catch{}}return(n,t)=>(i(),o("div",y,[e("div",_,[t[2]||(t[2]=g('<div class="text-center mb-8"><div class="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-primary mb-3"><svg class="h-7 w-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg></div><h1 class="text-2xl font-bold text-foreground">CC Dashboard</h1><p class="text-sm text-muted-foreground mt-1">Corporate Planning Hub</p></div>',1)),a(v,null,{default:d(()=>[a(w,{class:"pt-6"},{default:d(()=>[e("div",k,[r(s).error?(i(),o("div",C,x(r(s).error),1)):b("",!0),e("button",{type:"button",disabled:r(s).loading,class:"w-full flex items-center justify-center gap-3 rounded-md border border-border bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",onClick:u},[t[0]||(t[0]=e("svg",{class:"h-5 w-5 shrink-0",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg"},[e("rect",{x:"1",y:"1",width:"9",height:"9",fill:"#F25022"}),e("rect",{x:"11",y:"1",width:"9",height:"9",fill:"#7FBA00"}),e("rect",{x:"1",y:"11",width:"9",height:"9",fill:"#00A4EF"}),e("rect",{x:"11",y:"11",width:"9",height:"9",fill:"#FFB900"})],-1)),r(s).loading?(i(),o("span",V,"Signing in…")):(i(),o("span",S,"Sign in with Microsoft"))],8,B),t[1]||(t[1]=e("p",{class:"text-center text-xs text-muted-foreground"}," Use your @oliver.agency account ",-1))])]),_:1})]),_:1})])]))}});export{M as default};
|
||||
import{d as h,u as f,c as o,a as e,b as g,e as a,w as d,o as i,f as m,g as p,h as r,t as x,i as b}from"./index-rN5A-UTe.js";import{_ as v,a as w}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import"./utils-7WVCegLb.js";const y={class:"min-h-screen flex items-center justify-center bg-background p-4"},_={class:"w-full max-w-sm"},k={class:"space-y-4"},C={key:0,class:"rounded-md bg-destructive/10 border border-destructive/30 px-3 py-2 text-sm text-destructive"},B=["disabled"],V={key:0},S={key:1},M=h({__name:"LoginView",setup(F){const l=m(),c=p(),s=f();async function u(){try{await s.loginWithMicrosoft();const n=c.query.redirect;l.push(n??"/")}catch{}}return(n,t)=>(i(),o("div",y,[e("div",_,[t[2]||(t[2]=g('<div class="text-center mb-8"><div class="inline-flex h-12 w-12 items-center justify-center rounded-xl bg-primary mb-3"><svg class="h-7 w-7 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg></div><h1 class="text-2xl font-bold text-foreground">CC Dashboard</h1><p class="text-sm text-muted-foreground mt-1">Corporate Planning Hub</p></div>',1)),a(v,null,{default:d(()=>[a(w,{class:"pt-6"},{default:d(()=>[e("div",k,[r(s).error?(i(),o("div",C,x(r(s).error),1)):b("",!0),e("button",{type:"button",disabled:r(s).loading,class:"w-full flex items-center justify-center gap-3 rounded-md border border-border bg-white px-4 py-2.5 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",onClick:u},[t[0]||(t[0]=e("svg",{class:"h-5 w-5 shrink-0",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg"},[e("rect",{x:"1",y:"1",width:"9",height:"9",fill:"#F25022"}),e("rect",{x:"11",y:"1",width:"9",height:"9",fill:"#7FBA00"}),e("rect",{x:"1",y:"11",width:"9",height:"9",fill:"#00A4EF"}),e("rect",{x:"11",y:"11",width:"9",height:"9",fill:"#FFB900"})],-1)),r(s).loading?(i(),o("span",V,"Signing in…")):(i(),o("span",S,"Sign in with Microsoft"))],8,B),t[1]||(t[1]=e("p",{class:"text-center text-xs text-muted-foreground"}," Use your @oliver.agency account ",-1))])]),_:1})]),_:1})])]))}});export{M as default};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{D as U,d as oe,A as le,E as ne,K as _,c as v,a as l,e as i,w as r,k as g,h as $,F as R,r as G,g as ae,s as u,o as a,q as A,n as q,G as h,t as x,R as se,H as ie,I as re,i as ue,C as de,j as ce}from"./index-u75TGs9I.js";import{p as me}from"./projects-DmG6PSJf.js";import{_ as ve}from"./Dialog.vue_vue_type_script_setup_true_lang-Bqyk3z93.js";import{_ as j}from"./Input.vue_vue_type_script_setup_true_lang-C5UKCg8E.js";import{_ as fe}from"./Textarea.vue_vue_type_script_setup_true_lang-Ci2XSdxx.js";import{_ as V}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as pe}from"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";import{_ as be}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-VHSjeswj.js";import{_ as D}from"./Tooltip.vue_vue_type_script_setup_true_lang-YGe428Ab.js";import{_ as ge}from"./EmptyState.vue_vue_type_script_setup_true_lang-DRkX1TTt.js";import{F as xe}from"./file-text-WfdNhT8H.js";import{P as _e}from"./plus-1gCrPI5p.js";import{c as Q}from"./createLucideIcon-RaI43e07.js";import"./utils-7WVCegLb.js";/**
|
||||
import{D as U,d as oe,A as le,E as ne,K as _,c as v,a as l,e as i,w as r,k as g,h as $,F as R,r as G,g as ae,s as u,o as a,q as A,n as q,G as h,t as x,R as se,H as ie,I as re,i as ue,C as de,j as ce}from"./index-rN5A-UTe.js";import{p as me}from"./projects-COzvhtq2.js";import{_ as ve}from"./Dialog.vue_vue_type_script_setup_true_lang-5tIAaGh4.js";import{_ as j}from"./Input.vue_vue_type_script_setup_true_lang-D00Gss2e.js";import{_ as fe}from"./Textarea.vue_vue_type_script_setup_true_lang-MCF8JR3D.js";import{_ as V}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as pe}from"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";import{_ as be}from"./ConfirmDialog.vue_vue_type_script_setup_true_lang-NFERUoaY.js";import{_ as D}from"./Tooltip.vue_vue_type_script_setup_true_lang-CcvjvjWJ.js";import{_ as ge}from"./EmptyState.vue_vue_type_script_setup_true_lang-Q76FMbX_.js";import{F as xe}from"./file-text-DKL7lELr.js";import{P as _e}from"./plus-8OWFLnn5.js";import{c as Q}from"./createLucideIcon-BQqEiwZb.js";import"./utils-7WVCegLb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import{c as r}from"./createLucideIcon-RaI43e07.js";import{c as s}from"./utils-7WVCegLb.js";import{d as n,o as c,c as t,n as l,h as d,a as u,z as m}from"./index-u75TGs9I.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const v=r("FolderOpenIcon",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]),b=n({__name:"Progress",props:{value:{},max:{default:100},class:{},color:{default:"default"}},setup(a){const e=a,o=()=>Math.min(100,Math.max(0,e.value/e.max*100));return(i,f)=>(c(),t("div",{class:l(d(s)("relative h-2 w-full overflow-hidden rounded-full bg-secondary",e.class))},[u("div",{class:l(["h-full rounded-full transition-all duration-300",{"bg-primary":a.color==="default","bg-emerald-500":a.color==="success","bg-amber-500":a.color==="warning","bg-red-500":a.color==="danger"}]),style:m({width:`${o()}%`})},null,6)],2))}});export{v as F,b as _};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{d as W,u as q,A as G,c as r,e as a,a as s,F as f,t as d,h as n,i as x,w as i,k as V,g as Q,f as X,s as h,J as Y,o as l,q as _,r as g,z as F,n as T,j as Z,K as E}from"./index-u75TGs9I.js";import{d as ee}from"./dashboard-BFudnIDr.js";import{_ as y,a as b}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import{_ as k,a as w}from"./CardTitle.vue_vue_type_script_setup_true_lang-DjMy7CQR.js";import{_ as z}from"./Skeleton.vue_vue_type_script_setup_true_lang-C4U7HVwG.js";import{_ as S}from"./Input.vue_vue_type_script_setup_true_lang-C5UKCg8E.js";import{_ as H}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as D}from"./EmptyState.vue_vue_type_script_setup_true_lang-DRkX1TTt.js";import{_ as J}from"./Tooltip.vue_vue_type_script_setup_true_lang-YGe428Ab.js";import{f as B,b as I}from"./utils-7WVCegLb.js";import{F as te}from"./file-text-WfdNhT8H.js";import{c as se}from"./createLucideIcon-RaI43e07.js";import{C as oe}from"./calendar-days-BzQjbQ2l.js";import"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";/**
|
||||
import{d as W,u as q,A as G,c as r,e as a,a as s,F as f,t as d,h as n,i as x,w as i,k as V,g as Q,f as X,s as h,J as Y,o as l,q as _,r as g,z as F,n as T,j as Z,K as E}from"./index-rN5A-UTe.js";import{d as ee}from"./dashboard-DAarnFBr.js";import{_ as y,a as b}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import{_ as k,a as w}from"./CardTitle.vue_vue_type_script_setup_true_lang-CugNHrdd.js";import{_ as z}from"./Skeleton.vue_vue_type_script_setup_true_lang-DvqtZljC.js";import{_ as S}from"./Input.vue_vue_type_script_setup_true_lang-D00Gss2e.js";import{_ as H}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as D}from"./EmptyState.vue_vue_type_script_setup_true_lang-Q76FMbX_.js";import{_ as J}from"./Tooltip.vue_vue_type_script_setup_true_lang-CcvjvjWJ.js";import{f as B,b as I}from"./utils-7WVCegLb.js";import{F as te}from"./file-text-DKL7lELr.js";import{c as se}from"./createLucideIcon-BQqEiwZb.js";import{C as oe}from"./calendar-days-OJQzkSiF.js";import"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
21
src/static/assets/ProjectsView-DYSEwPaV.js
Normal file
21
src/static/assets/ProjectsView-DYSEwPaV.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
var Ee=Object.defineProperty;var pe=a=>{throw TypeError(a)};var Le=(a,t,e)=>t in a?Ee(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var x=(a,t,e)=>Le(a,typeof t!="symbol"?t+"":t,e),Be=(a,t,e)=>t.has(a)||pe("Cannot "+e);var he=(a,t,e)=>t.has(a)?pe("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(a):t.set(a,e);var D=(a,t,e)=>(Be(a,t,"access private method"),e);import{D as G,d as qe,u as Ze,A as Pe,c as v,a as w,e as z,w as S,h as W,F as Me,r as De,s as E,o as T,q as L,k as ue,t as X,i as fe,n as Qe,K as Q}from"./index-u75TGs9I.js";import{a as je,_ as Oe}from"./CardContent.vue_vue_type_script_setup_true_lang-fzwtKe8_.js";import{_ as de}from"./Badge.vue_vue_type_script_setup_true_lang-CYzAkUY0.js";import{_ as K}from"./Button.vue_vue_type_script_setup_true_lang--Tj7Fwvd.js";import{_ as Ne}from"./Spinner.vue_vue_type_script_setup_true_lang-Dzd1YlKR.js";import{_ as Fe}from"./SegmentedControl.vue_vue_type_script_setup_true_lang-b4hAozvT.js";import{_ as He}from"./EmptyState.vue_vue_type_script_setup_true_lang-DRkX1TTt.js";import{a as Ue,i as Ve}from"./utils-7WVCegLb.js";import{F as Ge}from"./file-text-WfdNhT8H.js";import{C as We}from"./calendar-Bnuvd7dQ.js";import{_ as Xe}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./createLucideIcon-RaI43e07.js";const ge={list:()=>G.get("/api/reports"),get:a=>G.get(`/api/reports/${a}`),generate:a=>G.post("/api/reports/generate",a)};function ee(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let C=ee();function ye(a){C=a}const $e=/[&<>"']/,Ke=new RegExp($e.source,"g"),_e=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Je=new RegExp(_e.source,"g"),Ye={"&":"&","<":"<",">":">",'"':""","'":"'"},ke=a=>Ye[a];function b(a,t){if(t){if($e.test(a))return a.replace(Ke,ke)}else if(_e.test(a))return a.replace(Je,ke);return a}const et=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;function tt(a){return a.replace(et,(t,e)=>(e=e.toLowerCase(),e==="colon"?":":e.charAt(0)==="#"?e.charAt(1)==="x"?String.fromCharCode(parseInt(e.substring(2),16)):String.fromCharCode(+e.substring(1)):""))}const nt=/(^|[^\[])\^/g;function g(a,t){let e=typeof a=="string"?a:a.source;t=t||"";const n={replace:(i,r)=>{let s=typeof r=="string"?r:r.source;return s=s.replace(nt,"$1"),e=e.replace(i,s),n},getRegex:()=>new RegExp(e,t)};return n}function xe(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const q={exec:()=>null};function me(a,t){const e=a.replace(/\|/g,(r,s,l)=>{let o=!1,u=s;for(;--u>=0&&l[u]==="\\";)o=!o;return o?"|":" |"}),n=e.split(/ \|/);let i=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;i<n.length;i++)n[i]=n[i].trim().replace(/\\\|/g,"|");return n}function j(a,t,e){const n=a.length;if(n===0)return"";let i=0;for(;i<n&&a.charAt(n-i-1)===t;)i++;return a.slice(0,n-i)}function st(a,t){if(a.indexOf(t[1])===-1)return-1;let e=0;for(let n=0;n<a.length;n++)if(a[n]==="\\")n++;else if(a[n]===t[0])e++;else if(a[n]===t[1]&&(e--,e<0))return n;return-1}function be(a,t,e,n){const i=t.href,r=t.title?b(t.title):null,s=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){n.state.inLink=!0;const l={type:"link",raw:e,href:i,title:r,text:s,tokens:n.inlineTokens(s)};return n.state.inLink=!1,l}return{type:"image",raw:e,href:i,title:r,text:b(s)}}function it(a,t){const e=a.match(/^(\s+)(?:```)/);if(e===null)return t;const n=e[1];return t.split(`
|
||||
var Ee=Object.defineProperty;var pe=a=>{throw TypeError(a)};var Le=(a,t,e)=>t in a?Ee(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var x=(a,t,e)=>Le(a,typeof t!="symbol"?t+"":t,e),Be=(a,t,e)=>t.has(a)||pe("Cannot "+e);var he=(a,t,e)=>t.has(a)?pe("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(a):t.set(a,e);var D=(a,t,e)=>(Be(a,t,"access private method"),e);import{D as G,d as qe,u as Ze,A as Pe,c as v,a as w,e as z,w as S,h as W,F as Me,r as De,s as E,o as T,q as L,k as ue,t as X,i as fe,n as Qe,K as Q}from"./index-rN5A-UTe.js";import{a as je,_ as Oe}from"./CardContent.vue_vue_type_script_setup_true_lang-BnzU0LF2.js";import{_ as de}from"./Badge.vue_vue_type_script_setup_true_lang-igExkyOK.js";import{_ as K}from"./Button.vue_vue_type_script_setup_true_lang-66r0PpW6.js";import{_ as Ne}from"./Spinner.vue_vue_type_script_setup_true_lang-DTeJRqO4.js";import{_ as Fe}from"./SegmentedControl.vue_vue_type_script_setup_true_lang-CAqylCqr.js";import{_ as He}from"./EmptyState.vue_vue_type_script_setup_true_lang-Q76FMbX_.js";import{a as Ue,i as Ve}from"./utils-7WVCegLb.js";import{F as Ge}from"./file-text-DKL7lELr.js";import{C as We}from"./calendar-GhZPhKWb.js";import{_ as Xe}from"./_plugin-vue_export-helper-DlAUqK2U.js";import"./createLucideIcon-BQqEiwZb.js";const ge={list:()=>G.get("/api/reports"),get:a=>G.get(`/api/reports/${a}`),generate:a=>G.post("/api/reports/generate",a)};function ee(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}let C=ee();function ye(a){C=a}const $e=/[&<>"']/,Ke=new RegExp($e.source,"g"),_e=/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,Je=new RegExp(_e.source,"g"),Ye={"&":"&","<":"<",">":">",'"':""","'":"'"},ke=a=>Ye[a];function b(a,t){if(t){if($e.test(a))return a.replace(Ke,ke)}else if(_e.test(a))return a.replace(Je,ke);return a}const et=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;function tt(a){return a.replace(et,(t,e)=>(e=e.toLowerCase(),e==="colon"?":":e.charAt(0)==="#"?e.charAt(1)==="x"?String.fromCharCode(parseInt(e.substring(2),16)):String.fromCharCode(+e.substring(1)):""))}const nt=/(^|[^\[])\^/g;function g(a,t){let e=typeof a=="string"?a:a.source;t=t||"";const n={replace:(i,r)=>{let s=typeof r=="string"?r:r.source;return s=s.replace(nt,"$1"),e=e.replace(i,s),n},getRegex:()=>new RegExp(e,t)};return n}function xe(a){try{a=encodeURI(a).replace(/%25/g,"%")}catch{return null}return a}const q={exec:()=>null};function me(a,t){const e=a.replace(/\|/g,(r,s,l)=>{let o=!1,u=s;for(;--u>=0&&l[u]==="\\";)o=!o;return o?"|":" |"}),n=e.split(/ \|/);let i=0;if(n[0].trim()||n.shift(),n.length>0&&!n[n.length-1].trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length<t;)n.push("");for(;i<n.length;i++)n[i]=n[i].trim().replace(/\\\|/g,"|");return n}function j(a,t,e){const n=a.length;if(n===0)return"";let i=0;for(;i<n&&a.charAt(n-i-1)===t;)i++;return a.slice(0,n-i)}function st(a,t){if(a.indexOf(t[1])===-1)return-1;let e=0;for(let n=0;n<a.length;n++)if(a[n]==="\\")n++;else if(a[n]===t[0])e++;else if(a[n]===t[1]&&(e--,e<0))return n;return-1}function be(a,t,e,n){const i=t.href,r=t.title?b(t.title):null,s=a[1].replace(/\\([\[\]])/g,"$1");if(a[0].charAt(0)!=="!"){n.state.inLink=!0;const l={type:"link",raw:e,href:i,title:r,text:s,tokens:n.inlineTokens(s)};return n.state.inLink=!1,l}return{type:"image",raw:e,href:i,title:r,text:b(s)}}function it(a,t){const e=a.match(/^(\s+)(?:```)/);if(e===null)return t;const n=e[1];return t.split(`
|
||||
`).map(i=>{const r=i.match(/^\s+/);if(r===null)return i;const[s]=r;return s.length>=n.length?i.slice(n.length):i}).join(`
|
||||
`)}class N{constructor(t){x(this,"options");x(this,"rules");x(this,"lexer");this.options=t||C}space(t){const e=this.rules.block.newline.exec(t);if(e&&e[0].length>0)return{type:"space",raw:e[0]}}code(t){const e=this.rules.block.code.exec(t);if(e){const n=e[0].replace(/^ {1,4}/gm,"");return{type:"code",raw:e[0],codeBlockStyle:"indented",text:this.options.pedantic?n:j(n,`
|
||||
`)}}}fences(t){const e=this.rules.block.fences.exec(t);if(e){const n=e[0],i=it(n,e[3]||"");return{type:"code",raw:n,lang:e[2]?e[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):e[2],text:i}}}heading(t){const e=this.rules.block.heading.exec(t);if(e){let n=e[2].trim();if(/#$/.test(n)){const i=j(n,"#");(this.options.pedantic||!i||/ $/.test(i))&&(n=i.trim())}return{type:"heading",raw:e[0],depth:e[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(t){const e=this.rules.block.hr.exec(t);if(e)return{type:"hr",raw:e[0]}}blockquote(t){const e=this.rules.block.blockquote.exec(t);if(e){let n=e[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,`
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as d}from"./utils-7WVCegLb.js";import{d as c,c as s,F as m,r as f,o as a,n as p,h as g,k as v,l as b,i as x,q as h,t as k}from"./index-u75TGs9I.js";const y=["aria-label"],V=["aria-pressed","onClick"],A=c({__name:"SegmentedControl",props:{modelValue:{},options:{},ariaLabel:{}},emits:["update:modelValue"],setup(t,{emit:i}){const o=t,l=i;function u(n){const r=o.options.findIndex(e=>e.value===o.modelValue);if(n.key==="ArrowRight"||n.key==="ArrowDown"){n.preventDefault();const e=o.options[(r+1)%o.options.length];l("update:modelValue",e.value)}else if(n.key==="ArrowLeft"||n.key==="ArrowUp"){n.preventDefault();const e=o.options[(r-1+o.options.length)%o.options.length];l("update:modelValue",e.value)}}return(n,r)=>(a(),s("div",{class:"inline-flex items-center rounded-lg border border-border bg-muted/40 p-1",role:"group","aria-label":t.ariaLabel,onKeydown:u},[(a(!0),s(m,null,f(t.options,e=>(a(),s("button",{key:e.value,type:"button","aria-pressed":t.modelValue===e.value,class:p(g(d)("inline-flex items-center gap-1.5 rounded-md px-3 h-8 text-xs font-medium transition-all","focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",t.modelValue===e.value?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground")),onClick:w=>l("update:modelValue",e.value)},[e.icon?(a(),v(b(e.icon),{key:0,class:"h-3.5 w-3.5"})):x("",!0),h(" "+k(e.label),1)],10,V))),128))],40,y))}});export{A as _};
|
||||
import{c as d}from"./utils-7WVCegLb.js";import{d as c,c as s,F as m,r as f,o as a,n as p,h as g,k as v,l as b,i as x,q as h,t as k}from"./index-rN5A-UTe.js";const y=["aria-label"],V=["aria-pressed","onClick"],A=c({__name:"SegmentedControl",props:{modelValue:{},options:{},ariaLabel:{}},emits:["update:modelValue"],setup(t,{emit:i}){const o=t,l=i;function u(n){const r=o.options.findIndex(e=>e.value===o.modelValue);if(n.key==="ArrowRight"||n.key==="ArrowDown"){n.preventDefault();const e=o.options[(r+1)%o.options.length];l("update:modelValue",e.value)}else if(n.key==="ArrowLeft"||n.key==="ArrowUp"){n.preventDefault();const e=o.options[(r-1+o.options.length)%o.options.length];l("update:modelValue",e.value)}}return(n,r)=>(a(),s("div",{class:"inline-flex items-center rounded-lg border border-border bg-muted/40 p-1",role:"group","aria-label":t.ariaLabel,onKeydown:u},[(a(!0),s(m,null,f(t.options,e=>(a(),s("button",{key:e.value,type:"button","aria-pressed":t.modelValue===e.value,class:p(g(d)("inline-flex items-center gap-1.5 rounded-md px-3 h-8 text-xs font-medium transition-all","focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",t.modelValue===e.value?"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground")),onClick:w=>l("update:modelValue",e.value)},[e.icon?(a(),v(b(e.icon),{key:0,class:"h-3.5 w-3.5"})):x("",!0),h(" "+k(e.label),1)],10,V))),128))],40,y))}});export{A as _};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{c as s}from"./utils-7WVCegLb.js";import{d as r,o as t,c as n,m as o,h as a}from"./index-u75TGs9I.js";const i=r({inheritAttrs:!1,__name:"Skeleton",setup(m){return(e,c)=>(t(),n("div",o(e.$attrs,{class:a(s)("skeleton-shimmer rounded-md",e.$attrs.class)}),null,16))}});export{i as _};
|
||||
import{c as s}from"./utils-7WVCegLb.js";import{d as r,o as t,c as n,m as o,h as a}from"./index-rN5A-UTe.js";const i=r({inheritAttrs:!1,__name:"Skeleton",setup(m){return(e,c)=>(t(),n("div",o(e.$attrs,{class:a(s)("skeleton-shimmer rounded-md",e.$attrs.class)}),null,16))}});export{i as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as l,o as n,c as o,n as t,a as r}from"./index-u75TGs9I.js";const i=l({__name:"Spinner",props:{size:{},class:{}},setup(s){return(a,e)=>(n(),o("svg",{class:t(["animate-spin text-current",s.size==="sm"?"h-3 w-3":s.size==="lg"?"h-6 w-6":"h-4 w-4",a.$props.class]),xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24"},[...e[0]||(e[0]=[r("circle",{class:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor","stroke-width":"4"},null,-1),r("path",{class:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"},null,-1)])],2))}});export{i as _};
|
||||
import{d as l,o as n,c as o,n as t,a as r}from"./index-rN5A-UTe.js";const i=l({__name:"Spinner",props:{size:{},class:{}},setup(s){return(a,e)=>(n(),o("svg",{class:t(["animate-spin text-current",s.size==="sm"?"h-3 w-3":s.size==="lg"?"h-6 w-6":"h-4 w-4",a.$props.class]),xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24"},[...e[0]||(e[0]=[r("circle",{class:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor","stroke-width":"4"},null,-1),r("path",{class:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"},null,-1)])],2))}});export{i as _};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{c as r}from"./utils-7WVCegLb.js";import{d as t,o as n,c as i,n as u,h as c}from"./index-u75TGs9I.js";const m=["id","value","placeholder","disabled","rows"],g=t({__name:"Textarea",props:{modelValue:{},placeholder:{},disabled:{type:Boolean},rows:{},class:{},id:{}},emits:["update:modelValue"],setup(e,{emit:l}){const a=e,s=l;return(f,o)=>(n(),i("textarea",{id:e.id,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,rows:e.rows??3,class:u(c(r)("flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm","ring-offset-background placeholder:text-muted-foreground","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:cursor-not-allowed disabled:opacity-50 resize-none",a.class)),onInput:o[0]||(o[0]=d=>s("update:modelValue",d.target.value))},null,42,m))}});export{g as _};
|
||||
import{c as r}from"./utils-7WVCegLb.js";import{d as t,o as n,c as i,n as u,h as c}from"./index-rN5A-UTe.js";const m=["id","value","placeholder","disabled","rows"],g=t({__name:"Textarea",props:{modelValue:{},placeholder:{},disabled:{type:Boolean},rows:{},class:{},id:{}},emits:["update:modelValue"],setup(e,{emit:l}){const a=e,s=l;return(f,o)=>(n(),i("textarea",{id:e.id,value:e.modelValue,placeholder:e.placeholder,disabled:e.disabled,rows:e.rows??3,class:u(c(r)("flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm","ring-offset-background placeholder:text-muted-foreground","focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2","disabled:cursor-not-allowed disabled:opacity-50 resize-none",a.class)),onInput:o[0]||(o[0]=d=>s("update:modelValue",d.target.value))},null,42,m))}});export{g as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{d,k as i,w as t,h as e,W as r,o as l,e as s,p as n,N as f,U as m,n as c,q as u,t as p,O as g}from"./index-u75TGs9I.js";import{c as x}from"./utils-7WVCegLb.js";const w=d({__name:"Tooltip",props:{content:{},side:{default:"top"},sideOffset:{default:6}},setup(a){return(o,h)=>(l(),i(e(r),null,{default:t(()=>[s(e(f),{"as-child":""},{default:t(()=>[n(o.$slots,"default")]),_:3}),s(e(g),null,{default:t(()=>[s(e(m),{side:a.side,"side-offset":a.sideOffset,class:c(e(x)("z-50 max-w-[280px] rounded-lg border border-border bg-popover px-3 py-1.5","text-xs text-popover-foreground shadow-md","animate-in fade-in-0 zoom-in-95","data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95","data-[side=bottom]:slide-in-from-top-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"))},{default:t(()=>[u(p(a.content),1)]),_:1},8,["side","side-offset","class"])]),_:1})]),_:3}))}});export{w as _};
|
||||
import{d,k as i,w as t,h as e,W as r,o as l,e as s,p as n,N as f,U as m,n as c,q as u,t as p,O as g}from"./index-rN5A-UTe.js";import{c as x}from"./utils-7WVCegLb.js";const w=d({__name:"Tooltip",props:{content:{},side:{default:"top"},sideOffset:{default:6}},setup(a){return(o,h)=>(l(),i(e(r),null,{default:t(()=>[s(e(f),{"as-child":""},{default:t(()=>[n(o.$slots,"default")]),_:3}),s(e(g),null,{default:t(()=>[s(e(m),{side:a.side,"side-offset":a.sideOffset,class:c(e(x)("z-50 max-w-[280px] rounded-lg border border-border bg-popover px-3 py-1.5","text-xs text-popover-foreground shadow-md","animate-in fade-in-0 zoom-in-95","data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95","data-[side=bottom]:slide-in-from-top-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"))},{default:t(()=>[u(p(a.content),1)]),_:1},8,["side","side-offset","class"])]),_:1})]),_:3}))}});export{w as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{D as e}from"./index-u75TGs9I.js";const i={users:()=>e.get("/api/admin/users"),keys:()=>e.get("/api/keys"),createKey:s=>e.post("/api/keys",s),revokeKey:s=>e.delete(`/api/keys/${s}`)};export{i as a};
|
||||
import{D as e}from"./index-rN5A-UTe.js";const i={users:()=>e.get("/api/admin/users"),keys:()=>e.get("/api/keys"),createKey:s=>e.post("/api/keys",s),revokeKey:s=>e.delete(`/api/keys/${s}`)};export{i as a};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{c as e}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as e}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{c as h}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as h}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{a4 as a}from"./index-u75TGs9I.js";/**
|
||||
import{a4 as a}from"./index-rN5A-UTe.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -1 +1 @@
|
|||
import{D as t}from"./index-u75TGs9I.js";const e={summary:a=>t.get("/api/dashboard/summary",{params:a}),projects:a=>t.get("/api/dashboard/projects",{params:a}),timeline:a=>t.get("/api/dashboard/timeline",{params:a}),monthly:a=>t.get("/api/dashboard/monthly",{params:a}),dow:a=>t.get("/api/dashboard/dow",{params:a}),tools:a=>t.get("/api/dashboard/tools",{params:a}),activity:a=>t.get("/api/dashboard/activity",{params:a}),calendar:a=>t.get("/api/dashboard/calendar",{params:a}),project:(a,o)=>t.get("/api/dashboard/project/"+a,{params:o})};export{e as d};
|
||||
import{D as t}from"./index-rN5A-UTe.js";const e={summary:a=>t.get("/api/dashboard/summary",{params:a}),projects:a=>t.get("/api/dashboard/projects",{params:a}),timeline:a=>t.get("/api/dashboard/timeline",{params:a}),monthly:a=>t.get("/api/dashboard/monthly",{params:a}),dow:a=>t.get("/api/dashboard/dow",{params:a}),tools:a=>t.get("/api/dashboard/tools",{params:a}),activity:a=>t.get("/api/dashboard/activity",{params:a}),calendar:a=>t.get("/api/dashboard/calendar",{params:a}),project:(a,o)=>t.get("/api/dashboard/project/"+a,{params:o})};export{e as d};
|
||||
|
|
@ -1 +1 @@
|
|||
import{D as n,B as I,s as o}from"./index-u75TGs9I.js";const i={getIntegration:()=>n.get("/api/devops/integration"),saveIntegration:e=>n.put("/api/devops/integration",e),deleteIntegration:()=>n.delete("/api/devops/integration"),sync:()=>n.post("/api/devops/sync"),workItems:e=>n.get("/api/devops/work-items",{params:e?{state:e}:void 0}),cloneWorkItem:e=>n.post(`/api/devops/work-items/${e}/clone`)},m=I("devops",()=>{const e=o(null),l=o([]),r=o(!1),s=o(!1),c=o(null);async function u(){s.value=!0;try{const t=await i.getIntegration();e.value=t.data}catch{e.value=null}finally{s.value=!1}}async function d(t){const a=await i.saveIntegration(t);e.value=a.data}async function g(){await i.deleteIntegration(),e.value=null}async function f(){var t,a;r.value=!0,c.value=null;try{await i.sync(),await u()}catch(v){const p=v;throw c.value=((a=(t=p.response)==null?void 0:t.data)==null?void 0:a.detail)??p.message??"Sync failed",v}finally{r.value=!1}}async function y(t){s.value=!0;try{const a=await i.workItems(t);l.value=a.data}catch{l.value=[]}finally{s.value=!1}}return{integration:e,workItems:l,syncing:r,loading:s,error:c,fetchIntegration:u,saveIntegration:d,deleteIntegration:g,sync:f,fetchWorkItems:y}});export{i as d,m as u};
|
||||
import{D as n,B as I,s as o}from"./index-rN5A-UTe.js";const i={getIntegration:()=>n.get("/api/devops/integration"),saveIntegration:e=>n.put("/api/devops/integration",e),deleteIntegration:()=>n.delete("/api/devops/integration"),sync:()=>n.post("/api/devops/sync"),workItems:e=>n.get("/api/devops/work-items",{params:e?{state:e}:void 0}),cloneWorkItem:e=>n.post(`/api/devops/work-items/${e}/clone`)},m=I("devops",()=>{const e=o(null),l=o([]),r=o(!1),s=o(!1),c=o(null);async function u(){s.value=!0;try{const t=await i.getIntegration();e.value=t.data}catch{e.value=null}finally{s.value=!1}}async function d(t){const a=await i.saveIntegration(t);e.value=a.data}async function g(){await i.deleteIntegration(),e.value=null}async function f(){var t,a;r.value=!0,c.value=null;try{await i.sync(),await u()}catch(v){const p=v;throw c.value=((a=(t=p.response)==null?void 0:t.data)==null?void 0:a.detail)??p.message??"Sync failed",v}finally{r.value=!1}}async function y(t){s.value=!0;try{const a=await i.workItems(t);l.value=a.data}catch{l.value=[]}finally{s.value=!1}}return{integration:e,workItems:l,syncing:r,loading:s,error:c,fetchIntegration:u,saveIntegration:d,deleteIntegration:g,sync:f,fetchWorkItems:y}});export{i as d,m as u};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{c as e}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as e}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
6
src/static/assets/folder-open-Dm6Er06z.js
Normal file
6
src/static/assets/folder-open-Dm6Er06z.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import{c as a}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const o=a("FolderOpenIcon",[["path",{d:"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2",key:"usdka0"}]]);export{o as F};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{c as X}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as X}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
import{c as e}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as e}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
1
src/static/assets/projects-COzvhtq2.js
Normal file
1
src/static/assets/projects-COzvhtq2.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{D as p}from"./index-rN5A-UTe.js";const i={list:()=>p.get("/api/projects")};export{i as p};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{D as p}from"./index-u75TGs9I.js";const i={list:()=>p.get("/api/projects")};export{i as p};
|
||||
|
|
@ -1 +1 @@
|
|||
import{D as l,B as h,s as i}from"./index-u75TGs9I.js";const o={list:a=>l.get("/api/tasks",{params:a}),get:a=>l.get(`/api/tasks/${a}`),create:a=>l.post("/api/tasks",a),update:(a,s)=>l.patch(`/api/tasks/${a}`,s),remove:a=>l.delete(`/api/tasks/${a}`),complete:a=>l.post(`/api/tasks/${a}/complete`),blocks:a=>l.get(`/api/tasks/${a}/blocks`),createBlock:(a,s)=>l.post(`/api/tasks/${a}/blocks`,s),updateBlock:(a,s)=>l.patch(`/api/tasks/blocks/${a}`,s),deleteBlock:a=>l.delete(`/api/tasks/blocks/${a}`)},b=Object.freeze(Object.defineProperty({__proto__:null,tasksApi:o},Symbol.toStringTag,{value:"Module"})),$=h("tasks",()=>{const a=i([]),s=i(!1),n=i(null);async function u(t){s.value=!0,n.value=null;try{const e=await o.list({date:t});a.value=e.data}catch(e){const c=e;n.value=c.message??"Failed to fetch tasks"}finally{s.value=!1}}async function d(t){s.value=!0,n.value=null;try{const e=await o.list(t?{project_id:t}:void 0);a.value=e.data}catch(e){const c=e;n.value=c.message??"Failed to fetch tasks"}finally{s.value=!1}}async function p(t){const e=await o.create(t);return a.value.push(e.data),e.data}async function k(t,e){const c=await o.update(t,e),r=a.value.findIndex(g=>g.id===t);return r!==-1&&(a.value[r]=c.data),c.data}async function f(t){await o.remove(t),a.value=a.value.filter(e=>e.id!==t)}async function v(t){const e=await o.complete(t),c=a.value.findIndex(r=>r.id===t);return c!==-1&&(a.value[c]=e.data),e.data}async function y(t,e){return(await o.createBlock(t,e)).data}async function m(t,e){return(await o.updateBlock(t,e)).data}async function B(t){await o.deleteBlock(t)}return{tasks:a,loading:s,error:n,fetchForDate:u,fetchAll:d,create:p,update:k,remove:f,complete:v,createBlock:y,updateBlock:m,deleteBlock:B}});export{b as t,$ as u};
|
||||
import{D as l,B as h,s as i}from"./index-rN5A-UTe.js";const o={list:a=>l.get("/api/tasks",{params:a}),get:a=>l.get(`/api/tasks/${a}`),create:a=>l.post("/api/tasks",a),update:(a,s)=>l.patch(`/api/tasks/${a}`,s),remove:a=>l.delete(`/api/tasks/${a}`),complete:a=>l.post(`/api/tasks/${a}/complete`),blocks:a=>l.get(`/api/tasks/${a}/blocks`),createBlock:(a,s)=>l.post(`/api/tasks/${a}/blocks`,s),updateBlock:(a,s)=>l.patch(`/api/tasks/blocks/${a}`,s),deleteBlock:a=>l.delete(`/api/tasks/blocks/${a}`)},b=Object.freeze(Object.defineProperty({__proto__:null,tasksApi:o},Symbol.toStringTag,{value:"Module"})),$=h("tasks",()=>{const a=i([]),s=i(!1),n=i(null);async function u(t){s.value=!0,n.value=null;try{const e=await o.list({date:t});a.value=e.data}catch(e){const c=e;n.value=c.message??"Failed to fetch tasks"}finally{s.value=!1}}async function d(t){s.value=!0,n.value=null;try{const e=await o.list(t?{project_id:t}:void 0);a.value=e.data}catch(e){const c=e;n.value=c.message??"Failed to fetch tasks"}finally{s.value=!1}}async function p(t){const e=await o.create(t);return a.value.push(e.data),e.data}async function k(t,e){const c=await o.update(t,e),r=a.value.findIndex(g=>g.id===t);return r!==-1&&(a.value[r]=c.data),c.data}async function f(t){await o.remove(t),a.value=a.value.filter(e=>e.id!==t)}async function v(t){const e=await o.complete(t),c=a.value.findIndex(r=>r.id===t);return c!==-1&&(a.value[c]=e.data),e.data}async function y(t,e){return(await o.createBlock(t,e)).data}async function m(t,e){return(await o.updateBlock(t,e)).data}async function B(t){await o.deleteBlock(t)}return{tasks:a,loading:s,error:n,fetchForDate:u,fetchAll:d,create:p,update:k,remove:f,complete:v,createBlock:y,updateBlock:m,deleteBlock:B}});export{b as t,$ as u};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import{c as a}from"./createLucideIcon-RaI43e07.js";/**
|
||||
import{c as a}from"./createLucideIcon-BQqEiwZb.js";/**
|
||||
* @license lucide-vue-next v0.427.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
else { document.documentElement.classList.remove('dark'); }
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/cc-dashboard/static/assets/index-u75TGs9I.js"></script>
|
||||
<script type="module" crossorigin src="/cc-dashboard/static/assets/index-rN5A-UTe.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/cc-dashboard/static/assets/index-BxXZv7ay.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export interface ProjectSummary {
|
|||
last_active: string
|
||||
budget_hours: number | null
|
||||
progress_pct: number | null
|
||||
total_cost_usd: number
|
||||
}
|
||||
|
||||
export interface MonthlyDataPoint {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useRouter, RouterLink } from 'vue-router'
|
|||
import { dashboardApi } from '@/api/endpoints/dashboard'
|
||||
import Card from '@/components/ui/Card.vue'
|
||||
import CardContent from '@/components/ui/CardContent.vue'
|
||||
import Progress from '@/components/ui/Progress.vue'
|
||||
import Spinner from '@/components/ui/Spinner.vue'
|
||||
import SegmentedControl from '@/components/ui/SegmentedControl.vue'
|
||||
import EmptyState from '@/components/ui/EmptyState.vue'
|
||||
|
|
@ -37,7 +36,7 @@ const listColumns: TableColumn[] = [
|
|||
{ key: 'total_hours', title: 'Hours', width: 90, minWidth: 60, sortable: true, resizable: true, align: 'right', type: 'number' },
|
||||
{ key: 'session_count', title: 'Sessions', width: 90, minWidth: 60, sortable: true, resizable: true, align: 'right', type: 'number' },
|
||||
{ key: 'last_active', title: 'Last Active', width: 120, minWidth: 90, sortable: true, resizable: true, align: 'right', type: 'date' },
|
||||
{ key: 'progress_pct', title: 'Budget', width: 90, minWidth: 70, sortable: true, resizable: true, align: 'right', type: 'number' },
|
||||
{ key: 'total_cost_usd', title: 'Cost $', width: 90, minWidth: 60, sortable: true, resizable: true, align: 'right', type: 'number' },
|
||||
]
|
||||
|
||||
const listRows = computed((): Record<string, unknown>[] => {
|
||||
|
|
@ -56,7 +55,7 @@ const listRows = computed((): Record<string, unknown>[] => {
|
|||
total_hours: p.total_hours,
|
||||
session_count: p.session_count,
|
||||
last_active: p.last_active ?? null,
|
||||
progress_pct: p.progress_pct ?? null,
|
||||
total_cost_usd: p.total_cost_usd ?? 0,
|
||||
}))
|
||||
})
|
||||
|
||||
|
|
@ -70,12 +69,6 @@ onMounted(async () => {
|
|||
}
|
||||
})
|
||||
|
||||
const progressColor = (pct: number | null) => {
|
||||
if (!pct) return 'default'
|
||||
if (pct > 90) return 'danger'
|
||||
if (pct > 70) return 'warning'
|
||||
return 'success'
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -145,18 +138,10 @@ const progressColor = (pct: number | null) => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Budget progress -->
|
||||
<div v-if="proj.progress_pct !== null" class="mt-3">
|
||||
<div class="flex items-center justify-between text-xs mb-1">
|
||||
<span class="text-muted-foreground">Budget</span>
|
||||
<span :class="proj.progress_pct > 90 ? 'text-red-400' : 'text-muted-foreground'">
|
||||
{{ (proj.progress_pct ?? 0).toFixed(0) }}%
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
:value="proj.progress_pct"
|
||||
:color="progressColor(proj.progress_pct)"
|
||||
/>
|
||||
<!-- Cost -->
|
||||
<div v-if="proj.total_cost_usd > 0" class="flex items-center justify-between text-xs mt-1">
|
||||
<span class="text-muted-foreground">CC Cost</span>
|
||||
<span class="font-medium text-foreground">${{ proj.total_cost_usd.toFixed(2) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
|
@ -217,13 +202,11 @@ const progressColor = (pct: number | null) => {
|
|||
<span class="text-xs text-muted-foreground">{{ value ? formatDate(value as string) : '—' }}</span>
|
||||
</template>
|
||||
|
||||
<!-- Budget cell -->
|
||||
<template #cell-progress_pct="{ value }">
|
||||
<span
|
||||
v-if="value !== null"
|
||||
class="text-xs tabular-nums"
|
||||
:class="(value as number) > 90 ? 'text-red-400' : 'text-muted-foreground'"
|
||||
>{{ (value as number).toFixed(0) }}%</span>
|
||||
<!-- Cost cell -->
|
||||
<template #cell-total_cost_usd="{ value }">
|
||||
<span v-if="(value as number) > 0" class="text-xs tabular-nums text-foreground">
|
||||
${{ (value as number).toFixed(2) }}
|
||||
</span>
|
||||
<span v-else class="text-xs text-muted-foreground/40">—</span>
|
||||
</template>
|
||||
</DataTable>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue