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:
Vadym Samoilenko 2026-05-13 12:47:26 +01:00
parent c3022c0c66
commit fde9b61465
57 changed files with 390 additions and 131 deletions

View 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")

View 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)

View file

@ -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)

View file

@ -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()
],

View file

@ -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)

View file

@ -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):

View file

@ -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

View file

@ -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 _};

View file

@ -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 _};

View file

@ -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};

View file

@ -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};

View file

@ -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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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

View file

@ -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 _};

View file

@ -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 _};

View file

@ -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 _};

View file

@ -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.

View file

@ -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.

View file

@ -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};

View file

@ -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.

View file

@ -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 _};

View file

@ -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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},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={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},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,`

View file

@ -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 _};

View file

@ -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 _};

View file

@ -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

View file

@ -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 _};

View file

@ -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 _};

View file

@ -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};

View file

@ -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.

View file

@ -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.

View file

@ -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.

View file

@ -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};

View file

@ -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};

View file

@ -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.

View 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};

View file

@ -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

View file

@ -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.

View file

@ -0,0 +1 @@
import{D as p}from"./index-rN5A-UTe.js";const i={list:()=>p.get("/api/projects")};export{i as p};

View file

@ -1 +0,0 @@
import{D as p}from"./index-u75TGs9I.js";const i={list:()=>p.get("/api/projects")};export{i as p};

View file

@ -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};

View file

@ -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.

View file

@ -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>

View file

@ -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 {

View file

@ -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>