fix: Planka admin user + branding CSS; project job_number edit form; report filename; devops title
- docker-compose: add DEFAULT_ADMIN_* passthrough + CUSTOM_UI_OVERRIDE_STYLESHEETS + mount planka-custom/ - planka-custom/planka.css: hide Planka logo/title/footer, override to orange theme matching CC Dashboard - ProjectDetailView: inline edit form for display_name/client/job_number/repo_url via PATCH /api/projects/:id - ReportsView: download filename uses report.period_date + report.type (report-2026-05-07-daily.md) - AppLayout: add devops → 'Azure DevOps' title entry Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
09cfc0a89a
commit
7c15f884c1
38 changed files with 359 additions and 84 deletions
|
|
@ -36,10 +36,16 @@ services:
|
|||
DATABASE_URL: postgresql://planka:planka@planka-db:5432/planka
|
||||
SECRET_KEY: ${PLANKA_SECRET_KEY}
|
||||
TRUST_PROXY: "1"
|
||||
DEFAULT_ADMIN_EMAIL: ${DEFAULT_ADMIN_EMAIL:-}
|
||||
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD:-}
|
||||
DEFAULT_ADMIN_NAME: ${DEFAULT_ADMIN_NAME:-Admin}
|
||||
DEFAULT_ADMIN_USERNAME: ${DEFAULT_ADMIN_USERNAME:-admin}
|
||||
CUSTOM_UI_OVERRIDE_STYLESHEETS: /app/public/custom/planka.css
|
||||
volumes:
|
||||
- planka-avatars:/app/public/user-avatars
|
||||
- planka-backgrounds:/app/public/project-background-images
|
||||
- planka-attachments:/app/private/attachments
|
||||
- ./planka-custom:/app/public/custom:ro
|
||||
depends_on:
|
||||
planka-db:
|
||||
condition: service_healthy
|
||||
|
|
|
|||
158
planka-custom/planka.css
Normal file
158
planka-custom/planka.css
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/* ============================================================
|
||||
CC Dashboard × Planka — Custom Branding Override
|
||||
============================================================ */
|
||||
|
||||
/* Body background — match CC Dashboard gradient */
|
||||
body {
|
||||
background: linear-gradient(135deg, #f0f4fa 0%, #fef9f5 100%) !important;
|
||||
font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
|
||||
}
|
||||
|
||||
/* ---- Login page ---- */
|
||||
|
||||
/* Hide Planka logo */
|
||||
img.logo,
|
||||
.logo,
|
||||
[class*="logo"] img,
|
||||
[class*="logo"],
|
||||
a[class*="logo"] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Add CC Dashboard logo via pseudo-element on the outer wrapper */
|
||||
[class*="wrapper"] > [class*="content"]::before,
|
||||
.ui.middle.aligned.grid::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
margin: 0 auto 16px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(135deg, #f97316, #ea580c);
|
||||
box-shadow: 0 8px 24px rgba(249,115,22,0.35);
|
||||
}
|
||||
|
||||
/* Override heading "PLANKA" → show CC Dashboard */
|
||||
[class*="title"],
|
||||
.ui.huge.header,
|
||||
h1.header {
|
||||
font-size: 0 !important;
|
||||
}
|
||||
[class*="title"]::after,
|
||||
.ui.huge.header::after,
|
||||
h1.header::after {
|
||||
content: 'Tasks Board';
|
||||
font-size: 1.5rem !important;
|
||||
font-weight: 700 !important;
|
||||
color: #1e293b !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* Sub-title "Log In" */
|
||||
[class*="subtitle"],
|
||||
.ui.large.header,
|
||||
h3.header {
|
||||
color: #64748b !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 0.95rem !important;
|
||||
}
|
||||
|
||||
/* Hide "Powered by PLANKA" footer */
|
||||
[class*="copyright"],
|
||||
[class*="poweredBy"],
|
||||
[class*="powered-by"],
|
||||
footer {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
/* Card/panel styling */
|
||||
.ui.stacked.segment,
|
||||
[class*="wrapper"] > [class*="content"] {
|
||||
background: rgba(255, 255, 255, 0.85) !important;
|
||||
backdrop-filter: blur(16px) !important;
|
||||
-webkit-backdrop-filter: blur(16px) !important;
|
||||
border-radius: 20px !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.9) !important;
|
||||
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.08), 0 1px 3px rgba(0,0,0,0.04) !important;
|
||||
padding: 40px 36px !important;
|
||||
}
|
||||
|
||||
/* Input fields */
|
||||
.ui.input input,
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"] {
|
||||
border-radius: 10px !important;
|
||||
border-color: #e2e8f0 !important;
|
||||
background: #f8fafc !important;
|
||||
font-family: inherit !important;
|
||||
transition: border-color 0.15s, box-shadow 0.15s !important;
|
||||
}
|
||||
.ui.input input:focus,
|
||||
input:focus {
|
||||
border-color: #f97316 !important;
|
||||
box-shadow: 0 0 0 3px rgba(249,115,22,0.12) !important;
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
/* Primary / teal button → orange */
|
||||
.ui.fluid.large.teal.button,
|
||||
.ui.teal.button,
|
||||
button[class*="teal"],
|
||||
.ui.fluid.button.positive,
|
||||
.ui.positive.button {
|
||||
background: linear-gradient(135deg, #f97316 0%, #ea580c 100%) !important;
|
||||
color: white !important;
|
||||
border-radius: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
letter-spacing: 0.01em !important;
|
||||
box-shadow: 0 4px 14px rgba(249,115,22,0.35) !important;
|
||||
border: none !important;
|
||||
transition: all 0.2s !important;
|
||||
}
|
||||
.ui.fluid.large.teal.button:hover,
|
||||
.ui.teal.button:hover {
|
||||
background: linear-gradient(135deg, #ea580c 0%, #c2410c 100%) !important;
|
||||
box-shadow: 0 6px 20px rgba(249,115,22,0.45) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
}
|
||||
|
||||
/* Label text */
|
||||
.ui.form .field > label {
|
||||
color: #374151 !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 0.8rem !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.05em !important;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
.ui.error.message {
|
||||
border-radius: 10px !important;
|
||||
border-left: 3px solid #f97316 !important;
|
||||
background: #fff7ed !important;
|
||||
color: #c2410c !important;
|
||||
}
|
||||
|
||||
/* ---- In-app top bar & sidebar ---- */
|
||||
|
||||
/* Top navigation - subtle */
|
||||
[class*="header"],
|
||||
.ui.menu:not(.secondary):not(.vertical) {
|
||||
background: rgba(255,255,255,0.8) !important;
|
||||
backdrop-filter: blur(12px) !important;
|
||||
-webkit-backdrop-filter: blur(12px) !important;
|
||||
border-bottom: 1px solid #e2e8f0 !important;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06) !important;
|
||||
}
|
||||
|
||||
/* Primary action buttons in-app */
|
||||
.ui.primary.button,
|
||||
.ui.button.primary {
|
||||
background: #f97316 !important;
|
||||
color: white !important;
|
||||
}
|
||||
.ui.primary.button:hover {
|
||||
background: #ea580c !important;
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as p,u as y,y as h,c as r,a as t,e as n,k as v,w as d,f as b,s as m,o as s,F as g,r as k,t as a,q as u,h as A}from"./index-DVV3ZbZ2.js";import{a as w}from"./admin-xS9EtPqv.js";import{_ as B,a as S}from"./CardContent.vue_vue_type_script_setup_true_lang-DdGXaWEa.js";import{_ as f}from"./Badge.vue_vue_type_script_setup_true_lang-WigJUOyH.js";import{_ as V}from"./Spinner.vue_vue_type_script_setup_true_lang-BkmDerVR.js";import{a as $}from"./utils-7WVCegLb.js";const N={class:"p-6"},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=p({__name:"AdminView",setup(I){const x=y(),_=b(),i=m([]),l=m(!1);return h(async()=>{if(!x.isAdmin){_.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 mb-6"},"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(g,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 p,u as y,y as h,c as r,a as t,e as n,k as v,w as d,f as b,s as m,o as s,F as g,r as k,t as a,q as u,h as A}from"./index-DxmLkgMg.js";import{a as w}from"./admin-Hqgc_gdo.js";import{_ as B,a as S}from"./CardContent.vue_vue_type_script_setup_true_lang-gLtslFdL.js";import{_ as f}from"./Badge.vue_vue_type_script_setup_true_lang-CVzGyv6L.js";import{_ as V}from"./Spinner.vue_vue_type_script_setup_true_lang-DMwaztwt.js";import{a as $}from"./utils-7WVCegLb.js";const N={class:"p-6"},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=p({__name:"AdminView",setup(I){const x=y(),_=b(),i=m([]),l=m(!1);return h(async()=>{if(!x.isAdmin){_.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 mb-6"},"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(g,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-DVV3ZbZ2.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-DxmLkgMg.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-BkmDerVR.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-DVV3ZbZ2.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-9 w-9 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-DMwaztwt.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-DxmLkgMg.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-9 w-9 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-DVV3ZbZ2.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-DxmLkgMg.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-DVV3ZbZ2.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-DxmLkgMg.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};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{u as D}from"./devops-HjUgCfao.js";import{_}from"./Input.vue_vue_type_script_setup_true_lang-BM1xhrlf.js";import{_ as V}from"./Button.vue_vue_type_script_setup_true_lang-ClV4YfXb.js";import{d as j,s as c,o as i,c as m,h as a,a as o,q as g,t as d,i as p,e as v,w as k,k as z,K as u}from"./index-DVV3ZbZ2.js";const B={class:"space-y-4"},I={key:0,class:"text-xs text-muted-foreground space-y-1"},N={class:"text-foreground"},P={class:"text-foreground"},S={key:0},U={key:1,class:"text-red-400"},b={class:"grid grid-cols-2 gap-3"},A={class:"space-y-1.5"},F={class:"space-y-1.5"},O={class:"space-y-1.5"},q={class:"flex items-center gap-2"},G=j({__name:"DevopsConnectForm",setup(E){var y,x;const t=D(),n=c(((y=t.integration)==null?void 0:y.organization)??""),r=c(((x=t.integration)==null?void 0:x.project)??""),s=c(""),f=c(!1);async function w(){if(!n.value||!r.value||!s.value){u.error("All fields are required");return}f.value=!0;try{await t.saveIntegration({organization:n.value,project:r.value,pat:s.value}),s.value="",u.success("Integration saved")}catch{u.error("Failed to save integration")}finally{f.value=!1}}async function C(){if(confirm("Delete ADO integration?"))try{await t.deleteIntegration(),n.value="",r.value="",s.value="",u.success("Integration deleted")}catch{u.error("Failed to delete integration")}}return(K,e)=>(i(),m("div",B,[a(t).integration?(i(),m("div",I,[o("p",null,[e[3]||(e[3]=g(" Connected to ",-1)),o("strong",N,d(a(t).integration.organization),1),e[4]||(e[4]=g(" / ",-1)),o("strong",P,d(a(t).integration.project),1)]),a(t).integration.last_synced_at?(i(),m("p",S," Last synced: "+d(new Date(a(t).integration.last_synced_at).toLocaleString()),1)):p("",!0),a(t).integration.last_sync_error?(i(),m("p",U," Error: "+d(a(t).integration.last_sync_error),1)):p("",!0)])):p("",!0),o("div",b,[o("div",A,[e[5]||(e[5]=o("label",{class:"text-sm font-medium text-foreground"},"Organization",-1)),v(_,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),placeholder:"myorg"},null,8,["modelValue"])]),o("div",F,[e[6]||(e[6]=o("label",{class:"text-sm font-medium text-foreground"},"Project",-1)),v(_,{modelValue:r.value,"onUpdate:modelValue":e[1]||(e[1]=l=>r.value=l),placeholder:"myproject"},null,8,["modelValue"])])]),o("div",O,[e[7]||(e[7]=o("label",{class:"text-sm font-medium text-foreground"}," Personal Access Token ",-1)),v(_,{modelValue:s.value,"onUpdate:modelValue":e[2]||(e[2]=l=>s.value=l),type:"password",placeholder:"••••••••",autocomplete:"new-password"},null,8,["modelValue"])]),o("div",q,[v(V,{loading:f.value,onClick:w},{default:k(()=>[g(d(a(t).integration?"Update":"Connect"),1)]),_:1},8,["loading"]),a(t).integration?(i(),z(V,{key:0,variant:"destructive",size:"sm",onClick:C},{default:k(()=>[...e[8]||(e[8]=[g(" Disconnect ",-1)])]),_:1})):p("",!0)])]))}});export{G as _};
|
||||
import{u as D}from"./devops-CwVu4BME.js";import{_}from"./Input.vue_vue_type_script_setup_true_lang-Gu5Qa2C5.js";import{_ as V}from"./Button.vue_vue_type_script_setup_true_lang-C49zRtnB.js";import{d as j,s as c,o as i,c as m,h as a,a as o,q as g,t as d,i as p,e as v,w as k,k as z,K as u}from"./index-DxmLkgMg.js";const B={class:"space-y-4"},I={key:0,class:"text-xs text-muted-foreground space-y-1"},N={class:"text-foreground"},P={class:"text-foreground"},S={key:0},U={key:1,class:"text-red-400"},b={class:"grid grid-cols-2 gap-3"},A={class:"space-y-1.5"},F={class:"space-y-1.5"},O={class:"space-y-1.5"},q={class:"flex items-center gap-2"},G=j({__name:"DevopsConnectForm",setup(E){var y,x;const t=D(),n=c(((y=t.integration)==null?void 0:y.organization)??""),r=c(((x=t.integration)==null?void 0:x.project)??""),s=c(""),f=c(!1);async function w(){if(!n.value||!r.value||!s.value){u.error("All fields are required");return}f.value=!0;try{await t.saveIntegration({organization:n.value,project:r.value,pat:s.value}),s.value="",u.success("Integration saved")}catch{u.error("Failed to save integration")}finally{f.value=!1}}async function C(){if(confirm("Delete ADO integration?"))try{await t.deleteIntegration(),n.value="",r.value="",s.value="",u.success("Integration deleted")}catch{u.error("Failed to delete integration")}}return(K,e)=>(i(),m("div",B,[a(t).integration?(i(),m("div",I,[o("p",null,[e[3]||(e[3]=g(" Connected to ",-1)),o("strong",N,d(a(t).integration.organization),1),e[4]||(e[4]=g(" / ",-1)),o("strong",P,d(a(t).integration.project),1)]),a(t).integration.last_synced_at?(i(),m("p",S," Last synced: "+d(new Date(a(t).integration.last_synced_at).toLocaleString()),1)):p("",!0),a(t).integration.last_sync_error?(i(),m("p",U," Error: "+d(a(t).integration.last_sync_error),1)):p("",!0)])):p("",!0),o("div",b,[o("div",A,[e[5]||(e[5]=o("label",{class:"text-sm font-medium text-foreground"},"Organization",-1)),v(_,{modelValue:n.value,"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l),placeholder:"myorg"},null,8,["modelValue"])]),o("div",F,[e[6]||(e[6]=o("label",{class:"text-sm font-medium text-foreground"},"Project",-1)),v(_,{modelValue:r.value,"onUpdate:modelValue":e[1]||(e[1]=l=>r.value=l),placeholder:"myproject"},null,8,["modelValue"])])]),o("div",O,[e[7]||(e[7]=o("label",{class:"text-sm font-medium text-foreground"}," Personal Access Token ",-1)),v(_,{modelValue:s.value,"onUpdate:modelValue":e[2]||(e[2]=l=>s.value=l),type:"password",placeholder:"••••••••",autocomplete:"new-password"},null,8,["modelValue"])]),o("div",q,[v(V,{loading:f.value,onClick:w},{default:k(()=>[g(d(a(t).integration?"Update":"Connect"),1)]),_:1},8,["loading"]),a(t).integration?(i(),z(V,{key:0,variant:"destructive",size:"sm",onClick:C},{default:k(()=>[...e[8]||(e[8]=[g(" Disconnect ",-1)])]),_:1})):p("",!0)])]))}});export{G as _};
|
||||
File diff suppressed because one or more lines are too long
1
src/static/assets/DevopsView-bNFkak5C.js
Normal file
1
src/static/assets/DevopsView-bNFkak5C.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
import{d as y,y as k,G as b,k as h,H as g,e as c,T as x,w as u,o as a,c as n,a as o,p as r,t as m,i,n as w}from"./index-DVV3ZbZ2.js";import{_ as $}from"./Button.vue_vue_type_script_setup_true_lang-ClV4YfXb.js";const C={key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4"},B=["aria-label"],j={key:0,class:"flex items-center justify-between p-6 pb-4"},z={class:"text-lg font-semibold text-foreground"},E={key:0,class:"text-sm text-muted-foreground mt-1"},L={class:"px-6 pb-4"},M={key:1,class:"flex justify-end gap-2 px-6 pb-6"},V=y({__name:"Dialog",props:{open:{type:Boolean},title:{},description:{},maxWidth:{default:"max-w-lg"}},emits:["close"],setup(e,{emit:f}){const p=e,l=f;function d(t){t.key==="Escape"&&p.open&&l("close")}return k(()=>document.addEventListener("keydown",d)),b(()=>document.removeEventListener("keydown",d)),(t,s)=>(a(),h(g,{to:"body"},[c(x,{"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:u(()=>[e.open?(a(),n("div",C,[o("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:s[0]||(s[0]=v=>l("close"))}),o("div",{class:w(["relative w-full bg-card border border-border rounded-lg shadow-xl z-10",e.maxWidth]),role:"dialog","aria-modal":!0,"aria-label":e.title},[e.title||t.$slots.header?(a(),n("div",j,[o("div",null,[r(t.$slots,"header",{},()=>[o("h2",z,m(e.title),1),e.description?(a(),n("p",E,m(e.description),1)):i("",!0)])]),c($,{variant:"ghost",size:"icon",class:"shrink-0",onClick:s[1]||(s[1]=v=>l("close"))},{default:u(()=>[...s[2]||(s[2]=[o("svg",{class:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])]),_:1})])):i("",!0),o("div",L,[r(t.$slots,"default")]),t.$slots.footer?(a(),n("div",M,[r(t.$slots,"footer")])):i("",!0)],10,B)])):i("",!0)]),_:3})]))}});export{V as _};
|
||||
import{d as y,y as k,H as b,k as h,I as g,e as c,T as x,w as u,o as a,c as n,a as o,p as r,t as m,i,n as w}from"./index-DxmLkgMg.js";import{_ as $}from"./Button.vue_vue_type_script_setup_true_lang-C49zRtnB.js";const C={key:0,class:"fixed inset-0 z-50 flex items-center justify-center p-4"},B=["aria-label"],j={key:0,class:"flex items-center justify-between p-6 pb-4"},z={class:"text-lg font-semibold text-foreground"},E={key:0,class:"text-sm text-muted-foreground mt-1"},L={class:"px-6 pb-4"},M={key:1,class:"flex justify-end gap-2 px-6 pb-6"},V=y({__name:"Dialog",props:{open:{type:Boolean},title:{},description:{},maxWidth:{default:"max-w-lg"}},emits:["close"],setup(e,{emit:f}){const p=e,l=f;function d(t){t.key==="Escape"&&p.open&&l("close")}return k(()=>document.addEventListener("keydown",d)),b(()=>document.removeEventListener("keydown",d)),(t,s)=>(a(),h(g,{to:"body"},[c(x,{"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:u(()=>[e.open?(a(),n("div",C,[o("div",{class:"absolute inset-0 bg-black/60 backdrop-blur-sm",onClick:s[0]||(s[0]=v=>l("close"))}),o("div",{class:w(["relative w-full bg-card border border-border rounded-lg shadow-xl z-10",e.maxWidth]),role:"dialog","aria-modal":!0,"aria-label":e.title},[e.title||t.$slots.header?(a(),n("div",j,[o("div",null,[r(t.$slots,"header",{},()=>[o("h2",z,m(e.title),1),e.description?(a(),n("p",E,m(e.description),1)):i("",!0)])]),c($,{variant:"ghost",size:"icon",class:"shrink-0",onClick:s[1]||(s[1]=v=>l("close"))},{default:u(()=>[...s[2]||(s[2]=[o("svg",{class:"h-4 w-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[o("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M6 18L18 6M6 6l12 12"})],-1)])]),_:1})])):i("",!0),o("div",L,[r(t.$slots,"default")]),t.$slots.footer?(a(),n("div",M,[r(t.$slots,"footer")])):i("",!0)],10,B)])):i("",!0)]),_:3})]))}});export{V as _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as i}from"./utils-7WVCegLb.js";import{d,o as s,c as u,n as m,h as r}from"./index-DVV3ZbZ2.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)=>(s(),u("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:m(r(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,o as s,c as u,n as m,h as r}from"./index-DxmLkgMg.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)=>(s(),u("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:m(r(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 +1 @@
|
|||
import{a as b}from"./admin-xS9EtPqv.js";import{_ as K,a as $}from"./CardContent.vue_vue_type_script_setup_true_lang-DdGXaWEa.js";import{_ as v}from"./Button.vue_vue_type_script_setup_true_lang-ClV4YfXb.js";import{_ as V}from"./Dialog.vue_vue_type_script_setup_true_lang-CSPK-Rcg.js";import{_ as N}from"./Input.vue_vue_type_script_setup_true_lang-BM1xhrlf.js";import{_ as A}from"./Spinner.vue_vue_type_script_setup_true_lang-BkmDerVR.js";import{d as B,y as L,c as l,a as t,e as r,w as n,s as i,o as a,q as p,F as P,r as F,t as u,h as k,k as I,i as j,K as y}from"./index-DVV3ZbZ2.js";import{a as h}from"./utils-7WVCegLb.js";const D={class:"p-6"},R={class:"flex items-center justify-between mb-6"},z={key:0,class:"flex items-center justify-center h-20"},M={key:1,class:"text-center text-muted-foreground py-8 text-sm"},T={key:2,class:"w-full"},U={class:"px-4 py-3 text-sm text-foreground"},q={class:"px-4 py-3 text-sm font-mono text-muted-foreground"},E={class:"px-4 py-3 text-xs text-muted-foreground"},H={class:"px-4 py-3 text-xs text-muted-foreground"},S={class:"px-4 py-3 text-right"},G={class:"space-y-4"},J={key:0,class:"rounded-md bg-emerald-500/10 border border-emerald-500/30 p-3"},O={class:"text-xs font-mono text-foreground break-all"},Q={key:1,class:"space-y-1.5"},re=B({__name:"KeysView",setup(W){const f=i([]),_=i(!1),c=i(!1),m=i(""),x=i(!1),d=i(null);L(()=>g());async function g(){_.value=!0;try{const o=await b.keys();f.value=o.data}finally{_.value=!1}}async function w(){if(m.value.trim()){x.value=!0;try{const o=await b.createKey({label:m.value});d.value=o.data.key,y.success("API key created"),await g(),m.value=""}catch{y.error("Failed to create key")}finally{x.value=!1}}}async function C(o){if(confirm(`Revoke key "${o.label}"? This cannot be undone.`))try{await b.revokeKey(o.id),y.success("Key revoked"),f.value=f.value.filter(e=>e.id!==o.id)}catch{y.error("Failed to revoke key")}}return(o,e)=>(a(),l("div",D,[t("div",R,[e[5]||(e[5]=t("h2",{class:"text-lg font-semibold text-foreground"},"API Keys",-1)),r(v,{size:"sm",onClick:e[0]||(e[0]=s=>{c.value=!0,d.value=null})},{default:n(()=>[...e[4]||(e[4]=[t("svg",{class:"h-4 w-4 mr-1.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),p(" New Key ",-1)])]),_:1})]),r(K,null,{default:n(()=>[r($,{class:"p-0"},{default:n(()=>[_.value?(a(),l("div",z,[r(A,{class:"text-primary"})])):f.value.length===0?(a(),l("div",M," No API keys ")):(a(),l("table",T,[e[7]||(e[7]=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"},"Label"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Prefix"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Created"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Last Used"),t("th",{class:"px-4 py-3"})])],-1)),t("tbody",null,[(a(!0),l(P,null,F(f.value,s=>(a(),l("tr",{key:s.id,class:"border-b border-border last:border-0 hover:bg-muted/30"},[t("td",U,u(s.label),1),t("td",q,u(s.prefix)+"...",1),t("td",E,u(k(h)(s.created_at)),1),t("td",H,u(s.last_used?k(h)(s.last_used):"Never"),1),t("td",S,[r(v,{variant:"ghost",size:"sm",class:"text-destructive",onClick:X=>C(s)},{default:n(()=>[...e[6]||(e[6]=[p(" Revoke ",-1)])]),_:1},8,["onClick"])])]))),128))])]))]),_:1})]),_:1}),r(V,{open:c.value,title:"Create API Key",onClose:e[3]||(e[3]=s=>c.value=!1)},{footer:n(()=>[r(v,{variant:"outline",onClick:e[2]||(e[2]=s=>c.value=!1)},{default:n(()=>[p(u(d.value?"Done":"Cancel"),1)]),_:1}),d.value?j("",!0):(a(),I(v,{key:0,loading:x.value,onClick:w},{default:n(()=>[...e[10]||(e[10]=[p(" Create ",-1)])]),_:1},8,["loading"]))]),default:n(()=>[t("div",G,[d.value?(a(),l("div",J,[e[8]||(e[8]=t("p",{class:"text-xs text-emerald-400 font-medium mb-1"},"Key created — save it now!",-1)),t("p",O,u(d.value),1)])):(a(),l("div",Q,[e[9]||(e[9]=t("label",{class:"text-sm font-medium text-foreground"},"Label",-1)),r(N,{modelValue:m.value,"onUpdate:modelValue":e[1]||(e[1]=s=>m.value=s),placeholder:"e.g. claude-collector",disabled:x.value},null,8,["modelValue","disabled"])]))])]),_:1},8,["open"])]))}});export{re as default};
|
||||
import{a as b}from"./admin-Hqgc_gdo.js";import{_ as K,a as $}from"./CardContent.vue_vue_type_script_setup_true_lang-gLtslFdL.js";import{_ as v}from"./Button.vue_vue_type_script_setup_true_lang-C49zRtnB.js";import{_ as V}from"./Dialog.vue_vue_type_script_setup_true_lang-BtMWNArX.js";import{_ as N}from"./Input.vue_vue_type_script_setup_true_lang-Gu5Qa2C5.js";import{_ as A}from"./Spinner.vue_vue_type_script_setup_true_lang-DMwaztwt.js";import{d as B,y as L,c as l,a as t,e as r,w as n,s as i,o as a,q as p,F as P,r as F,t as u,h as k,k as I,i as j,K as y}from"./index-DxmLkgMg.js";import{a as h}from"./utils-7WVCegLb.js";const D={class:"p-6"},R={class:"flex items-center justify-between mb-6"},z={key:0,class:"flex items-center justify-center h-20"},M={key:1,class:"text-center text-muted-foreground py-8 text-sm"},T={key:2,class:"w-full"},U={class:"px-4 py-3 text-sm text-foreground"},q={class:"px-4 py-3 text-sm font-mono text-muted-foreground"},E={class:"px-4 py-3 text-xs text-muted-foreground"},H={class:"px-4 py-3 text-xs text-muted-foreground"},S={class:"px-4 py-3 text-right"},G={class:"space-y-4"},J={key:0,class:"rounded-md bg-emerald-500/10 border border-emerald-500/30 p-3"},O={class:"text-xs font-mono text-foreground break-all"},Q={key:1,class:"space-y-1.5"},re=B({__name:"KeysView",setup(W){const f=i([]),_=i(!1),c=i(!1),m=i(""),x=i(!1),d=i(null);L(()=>g());async function g(){_.value=!0;try{const o=await b.keys();f.value=o.data}finally{_.value=!1}}async function w(){if(m.value.trim()){x.value=!0;try{const o=await b.createKey({label:m.value});d.value=o.data.key,y.success("API key created"),await g(),m.value=""}catch{y.error("Failed to create key")}finally{x.value=!1}}}async function C(o){if(confirm(`Revoke key "${o.label}"? This cannot be undone.`))try{await b.revokeKey(o.id),y.success("Key revoked"),f.value=f.value.filter(e=>e.id!==o.id)}catch{y.error("Failed to revoke key")}}return(o,e)=>(a(),l("div",D,[t("div",R,[e[5]||(e[5]=t("h2",{class:"text-lg font-semibold text-foreground"},"API Keys",-1)),r(v,{size:"sm",onClick:e[0]||(e[0]=s=>{c.value=!0,d.value=null})},{default:n(()=>[...e[4]||(e[4]=[t("svg",{class:"h-4 w-4 mr-1.5",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round","stroke-width":"2",d:"M12 4v16m8-8H4"})],-1),p(" New Key ",-1)])]),_:1})]),r(K,null,{default:n(()=>[r($,{class:"p-0"},{default:n(()=>[_.value?(a(),l("div",z,[r(A,{class:"text-primary"})])):f.value.length===0?(a(),l("div",M," No API keys ")):(a(),l("table",T,[e[7]||(e[7]=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"},"Label"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Prefix"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Created"),t("th",{class:"text-left text-xs font-medium text-muted-foreground px-4 py-3"},"Last Used"),t("th",{class:"px-4 py-3"})])],-1)),t("tbody",null,[(a(!0),l(P,null,F(f.value,s=>(a(),l("tr",{key:s.id,class:"border-b border-border last:border-0 hover:bg-muted/30"},[t("td",U,u(s.label),1),t("td",q,u(s.prefix)+"...",1),t("td",E,u(k(h)(s.created_at)),1),t("td",H,u(s.last_used?k(h)(s.last_used):"Never"),1),t("td",S,[r(v,{variant:"ghost",size:"sm",class:"text-destructive",onClick:X=>C(s)},{default:n(()=>[...e[6]||(e[6]=[p(" Revoke ",-1)])]),_:1},8,["onClick"])])]))),128))])]))]),_:1})]),_:1}),r(V,{open:c.value,title:"Create API Key",onClose:e[3]||(e[3]=s=>c.value=!1)},{footer:n(()=>[r(v,{variant:"outline",onClick:e[2]||(e[2]=s=>c.value=!1)},{default:n(()=>[p(u(d.value?"Done":"Cancel"),1)]),_:1}),d.value?j("",!0):(a(),I(v,{key:0,loading:x.value,onClick:w},{default:n(()=>[...e[10]||(e[10]=[p(" Create ",-1)])]),_:1},8,["loading"]))]),default:n(()=>[t("div",G,[d.value?(a(),l("div",J,[e[8]||(e[8]=t("p",{class:"text-xs text-emerald-400 font-medium mb-1"},"Key created — save it now!",-1)),t("p",O,u(d.value),1)])):(a(),l("div",Q,[e[9]||(e[9]=t("label",{class:"text-sm font-medium text-foreground"},"Label",-1)),r(N,{modelValue:m.value,"onUpdate:modelValue":e[1]||(e[1]=s=>m.value=s),placeholder:"e.g. claude-collector",disabled:x.value},null,8,["modelValue","disabled"])]))])]),_:1},8,["open"])]))}});export{re as default};
|
||||
|
|
@ -1 +1 @@
|
|||
import{G as T,s as g,d as J,u as O,y as V,c as f,a as o,n as b,h as l,t as v,k as $,w as x,i as k,e as C,o as c,q as w,F as B,r as F,j as z}from"./index-DVV3ZbZ2.js";import{_ as A,a as D}from"./CardContent.vue_vue_type_script_setup_true_lang-DdGXaWEa.js";import{_ as N}from"./Button.vue_vue_type_script_setup_true_lang-ClV4YfXb.js";import"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-BkmDerVR.js";function U(E){const e=g([]),i=g(!1),m=g(null);let s=null,r=null,u=!1;function p(){if(!u)try{s=new EventSource(E),s.onopen=()=>{i.value=!0,m.value=null},s.onmessage=n=>{try{const y=JSON.parse(n.data);e.value.push({type:"message",data:y}),e.value.length>200&&e.value.shift()}catch{e.value.push({type:"message",data:n.data})}},s.addEventListener("session_start",n=>{try{e.value.push({type:"session_start",data:JSON.parse(n.data)})}catch{e.value.push({type:"session_start",data:n.data})}e.value.length>200&&e.value.shift()}),s.addEventListener("session_end",n=>{try{e.value.push({type:"session_end",data:JSON.parse(n.data)})}catch{e.value.push({type:"session_end",data:n.data})}e.value.length>200&&e.value.shift()}),s.addEventListener("activity",n=>{try{e.value.push({type:"activity",data:JSON.parse(n.data)})}catch{e.value.push({type:"activity",data:n.data})}e.value.length>200&&e.value.shift()}),s.onerror=()=>{i.value=!1,m.value="Connection lost, reconnecting...",s==null||s.close(),s=null,u||(r=setTimeout(()=>p(),5e3))}}catch{m.value="Failed to connect to event stream",u||(r=setTimeout(()=>p(),5e3))}}function _(){u=!0,r&&clearTimeout(r),s==null||s.close(),s=null,i.value=!1}function h(){e.value=[]}return T(()=>{_()}),{events:e,connected:i,error:m,connect:p,disconnect:_,clearEvents:h}}const I={class:"p-6 h-full flex flex-col"},R={class:"flex items-center gap-3 mb-4"},q={class:"flex items-center gap-2"},G={class:"text-xs text-muted-foreground"},M={key:0,class:"mb-4 text-xs text-amber-400 bg-amber-500/10 border border-amber-500/30 rounded px-3 py-2"},P={key:0,class:"flex items-center justify-center h-full text-sm text-muted-foreground"},W={key:1,class:"overflow-y-auto h-full font-mono text-xs"},H={class:"flex-1 min-w-0"},K={class:"flex items-center gap-2 flex-wrap"},Q={key:0,class:"text-muted-foreground"},X={class:"text-muted-foreground truncate mt-0.5"},ne=J({__name:"LiveView",setup(E){const e=O(),i=e.getToken(),m=`/cc-dashboard/api/events${i?`?token=${encodeURIComponent(i)}`:""}`,{events:s,connected:r,error:u,connect:p,clearEvents:_}=U(m);V(()=>{e.isAuthenticated&&i&&p()});const h=z(()=>[...s.value].reverse().slice(0,100));function n(t){return t==="session_start"?"text-emerald-400":t==="session_end"?"text-amber-400":t==="activity"?"text-blue-400":"text-muted-foreground"}function y(t){return t==="session_start"?"▶":t==="session_end"?"■":t==="activity"?"●":"○"}function j(t){if(typeof t=="string")return t;if(t&&typeof t=="object"){const a=t;return a.message||a.summary||JSON.stringify(t)}return String(t)}function S(t){if(t&&typeof t=="object"){const a=t;return a.display_name||a.project_id||""}return""}return(t,a)=>(c(),f("div",I,[o("div",R,[a[2]||(a[2]=o("h2",{class:"text-lg font-semibold text-foreground flex-1"},"Live Feed",-1)),o("div",q,[o("div",{class:b(["h-2 w-2 rounded-full",l(r)?"bg-emerald-500 animate-pulse":"bg-red-500"])},null,2),o("span",G,v(l(r)?"Connected":"Disconnected"),1)]),l(r)?k("",!0):(c(),$(N,{key:0,variant:"outline",size:"sm",onClick:l(p)},{default:x(()=>[...a[0]||(a[0]=[w(" Reconnect ",-1)])]),_:1},8,["onClick"])),C(N,{variant:"ghost",size:"sm",onClick:l(_)},{default:x(()=>[...a[1]||(a[1]=[w(" Clear ",-1)])]),_:1},8,["onClick"])]),l(u)&&!l(r)?(c(),f("div",M,v(l(u)),1)):k("",!0),C(A,{class:"flex-1 overflow-hidden"},{default:x(()=>[C(D,{class:"p-0 h-full"},{default:x(()=>[h.value.length===0?(c(),f("div",P,[...a[3]||(a[3]=[o("div",{class:"text-center"},[o("div",{class:"text-2xl mb-2"},"📡"),o("p",null,"Waiting for events..."),o("p",{class:"text-xs mt-1"},"Activity will appear here in real-time")],-1)])])):(c(),f("div",W,[(c(!0),f(B,null,F(h.value,(d,L)=>(c(),f("div",{key:L,class:"flex items-start gap-2 px-4 py-1.5 hover:bg-muted/50 border-b border-border/30"},[o("span",{class:b([n(d.type),"shrink-0 mt-0.5"])},v(y(d.type)),3),o("div",H,[o("div",K,[o("span",{class:b([n(d.type),"font-medium"])},v(d.type),3),S(d.data)?(c(),f("span",Q,v(S(d.data)),1)):k("",!0)]),o("p",X,v(j(d.data)),1)])]))),128))]))]),_:1})]),_:1})]))}});export{ne as default};
|
||||
import{H as T,s as g,d as J,u as O,y as V,c as f,a as o,n as b,h as l,t as v,k as $,w as x,i as k,e as C,o as c,q as w,F as B,r as F,j as z}from"./index-DxmLkgMg.js";import{_ as A,a as D}from"./CardContent.vue_vue_type_script_setup_true_lang-gLtslFdL.js";import{_ as N}from"./Button.vue_vue_type_script_setup_true_lang-C49zRtnB.js";import"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-DMwaztwt.js";function U(E){const e=g([]),i=g(!1),m=g(null);let s=null,r=null,u=!1;function p(){if(!u)try{s=new EventSource(E),s.onopen=()=>{i.value=!0,m.value=null},s.onmessage=n=>{try{const y=JSON.parse(n.data);e.value.push({type:"message",data:y}),e.value.length>200&&e.value.shift()}catch{e.value.push({type:"message",data:n.data})}},s.addEventListener("session_start",n=>{try{e.value.push({type:"session_start",data:JSON.parse(n.data)})}catch{e.value.push({type:"session_start",data:n.data})}e.value.length>200&&e.value.shift()}),s.addEventListener("session_end",n=>{try{e.value.push({type:"session_end",data:JSON.parse(n.data)})}catch{e.value.push({type:"session_end",data:n.data})}e.value.length>200&&e.value.shift()}),s.addEventListener("activity",n=>{try{e.value.push({type:"activity",data:JSON.parse(n.data)})}catch{e.value.push({type:"activity",data:n.data})}e.value.length>200&&e.value.shift()}),s.onerror=()=>{i.value=!1,m.value="Connection lost, reconnecting...",s==null||s.close(),s=null,u||(r=setTimeout(()=>p(),5e3))}}catch{m.value="Failed to connect to event stream",u||(r=setTimeout(()=>p(),5e3))}}function _(){u=!0,r&&clearTimeout(r),s==null||s.close(),s=null,i.value=!1}function h(){e.value=[]}return T(()=>{_()}),{events:e,connected:i,error:m,connect:p,disconnect:_,clearEvents:h}}const I={class:"p-6 h-full flex flex-col"},R={class:"flex items-center gap-3 mb-4"},q={class:"flex items-center gap-2"},H={class:"text-xs text-muted-foreground"},M={key:0,class:"mb-4 text-xs text-amber-400 bg-amber-500/10 border border-amber-500/30 rounded px-3 py-2"},P={key:0,class:"flex items-center justify-center h-full text-sm text-muted-foreground"},W={key:1,class:"overflow-y-auto h-full font-mono text-xs"},G={class:"flex-1 min-w-0"},K={class:"flex items-center gap-2 flex-wrap"},Q={key:0,class:"text-muted-foreground"},X={class:"text-muted-foreground truncate mt-0.5"},ne=J({__name:"LiveView",setup(E){const e=O(),i=e.getToken(),m=`/cc-dashboard/api/events${i?`?token=${encodeURIComponent(i)}`:""}`,{events:s,connected:r,error:u,connect:p,clearEvents:_}=U(m);V(()=>{e.isAuthenticated&&i&&p()});const h=z(()=>[...s.value].reverse().slice(0,100));function n(t){return t==="session_start"?"text-emerald-400":t==="session_end"?"text-amber-400":t==="activity"?"text-blue-400":"text-muted-foreground"}function y(t){return t==="session_start"?"▶":t==="session_end"?"■":t==="activity"?"●":"○"}function j(t){if(typeof t=="string")return t;if(t&&typeof t=="object"){const a=t;return a.message||a.summary||JSON.stringify(t)}return String(t)}function S(t){if(t&&typeof t=="object"){const a=t;return a.display_name||a.project_id||""}return""}return(t,a)=>(c(),f("div",I,[o("div",R,[a[2]||(a[2]=o("h2",{class:"text-lg font-semibold text-foreground flex-1"},"Live Feed",-1)),o("div",q,[o("div",{class:b(["h-2 w-2 rounded-full",l(r)?"bg-emerald-500 animate-pulse":"bg-red-500"])},null,2),o("span",H,v(l(r)?"Connected":"Disconnected"),1)]),l(r)?k("",!0):(c(),$(N,{key:0,variant:"outline",size:"sm",onClick:l(p)},{default:x(()=>[...a[0]||(a[0]=[w(" Reconnect ",-1)])]),_:1},8,["onClick"])),C(N,{variant:"ghost",size:"sm",onClick:l(_)},{default:x(()=>[...a[1]||(a[1]=[w(" Clear ",-1)])]),_:1},8,["onClick"])]),l(u)&&!l(r)?(c(),f("div",M,v(l(u)),1)):k("",!0),C(A,{class:"flex-1 overflow-hidden"},{default:x(()=>[C(D,{class:"p-0 h-full"},{default:x(()=>[h.value.length===0?(c(),f("div",P,[...a[3]||(a[3]=[o("div",{class:"text-center"},[o("div",{class:"text-2xl mb-2"},"📡"),o("p",null,"Waiting for events..."),o("p",{class:"text-xs mt-1"},"Activity will appear here in real-time")],-1)])])):(c(),f("div",W,[(c(!0),f(B,null,F(h.value,(d,L)=>(c(),f("div",{key:L,class:"flex items-start gap-2 px-4 py-1.5 hover:bg-muted/50 border-b border-border/30"},[o("span",{class:b([n(d.type),"shrink-0 mt-0.5"])},v(y(d.type)),3),o("div",G,[o("div",K,[o("span",{class:b([n(d.type),"font-medium"])},v(d.type),3),S(d.data)?(c(),f("span",Q,v(S(d.data)),1)):k("",!0)]),o("p",X,v(j(d.data)),1)])]))),128))]))]),_:1})]),_:1})]))}});export{ne as default};
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as h,u as f,c as o,a as t,b as m,e as a,w as d,o as r,f as g,g as p,h as i,t as x,i as w}from"./index-DVV3ZbZ2.js";import{_ as y,a as b}from"./CardContent.vue_vue_type_script_setup_true_lang-DdGXaWEa.js";import"./utils-7WVCegLb.js";const v={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 c=g(),l=p(),s=f();async function u(){try{await s.loginWithMicrosoft();const n=l.query.redirect;c.push(n??"/")}catch{}}return(n,e)=>(r(),o("div",v,[t("div",_,[e[2]||(e[2]=m('<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(y,null,{default:d(()=>[a(b,{class:"pt-6"},{default:d(()=>[t("div",k,[i(s).error?(r(),o("div",C,x(i(s).error),1)):w("",!0),t("button",{type:"button",disabled:i(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:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",onClick:u},[e[0]||(e[0]=t("svg",{class:"h-5 w-5 shrink-0",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg"},[t("rect",{x:"1",y:"1",width:"9",height:"9",fill:"#F25022"}),t("rect",{x:"11",y:"1",width:"9",height:"9",fill:"#7FBA00"}),t("rect",{x:"1",y:"11",width:"9",height:"9",fill:"#00A4EF"}),t("rect",{x:"11",y:"11",width:"9",height:"9",fill:"#FFB900"})],-1)),i(s).loading?(r(),o("span",V,"Signing in…")):(r(),o("span",S,"Sign in with Microsoft"))],8,B),e[1]||(e[1]=t("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 t,b as m,e as a,w as d,o as r,f as g,g as p,h as i,t as x,i as w}from"./index-DxmLkgMg.js";import{_ as y,a as b}from"./CardContent.vue_vue_type_script_setup_true_lang-gLtslFdL.js";import"./utils-7WVCegLb.js";const v={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 c=g(),l=p(),s=f();async function u(){try{await s.loginWithMicrosoft();const n=l.query.redirect;c.push(n??"/")}catch{}}return(n,e)=>(r(),o("div",v,[t("div",_,[e[2]||(e[2]=m('<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(y,null,{default:d(()=>[a(b,{class:"pt-6"},{default:d(()=>[t("div",k,[i(s).error?(r(),o("div",C,x(i(s).error),1)):w("",!0),t("button",{type:"button",disabled:i(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:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors",onClick:u},[e[0]||(e[0]=t("svg",{class:"h-5 w-5 shrink-0",viewBox:"0 0 21 21",fill:"none",xmlns:"http://www.w3.org/2000/svg"},[t("rect",{x:"1",y:"1",width:"9",height:"9",fill:"#F25022"}),t("rect",{x:"11",y:"1",width:"9",height:"9",fill:"#7FBA00"}),t("rect",{x:"1",y:"11",width:"9",height:"9",fill:"#00A4EF"}),t("rect",{x:"11",y:"11",width:"9",height:"9",fill:"#FFB900"})],-1)),i(s).loading?(r(),o("span",V,"Signing in…")):(r(),o("span",S,"Sign in with Microsoft"))],8,B),e[1]||(e[1]=t("p",{class:"text-center text-xs text-muted-foreground"}," Use your @oliver.agency account ",-1))])]),_:1})]),_:1})])]))}});export{M as default};
|
||||
|
|
@ -1 +1 @@
|
|||
import{c as r}from"./utils-7WVCegLb.js";import{d as s,o as n,c as t,n as l,h as c,a as d,B as u}from"./index-DVV3ZbZ2.js";const h=s({__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,m)=>(n(),t("div",{class:l(c(r)("relative h-2 w-full overflow-hidden rounded-full bg-secondary",e.class))},[d("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:u({width:`${o()}%`})},null,6)],2))}});export{h as _};
|
||||
import{c as r}from"./utils-7WVCegLb.js";import{d as s,o as n,c as t,n as l,h as c,a as d,B as u}from"./index-DxmLkgMg.js";const h=s({__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,m)=>(n(),t("div",{class:l(c(r)("relative h-2 w-full overflow-hidden rounded-full bg-secondary",e.class))},[d("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:u({width:`${o()}%`})},null,6)],2))}});export{h as _};
|
||||
1
src/static/assets/ProjectDetailView-C4aTXqRu.js
Normal file
1
src/static/assets/ProjectDetailView-C4aTXqRu.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
File diff suppressed because one or more lines are too long
1
src/static/assets/ReportsView-CMpy2coK.css
Normal file
1
src/static/assets/ReportsView-CMpy2coK.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
[data-v-beecdbb2] .prose{color:hsl(var(--foreground))}[data-v-beecdbb2] .prose h1{color:hsl(var(--foreground));font-weight:600;margin-top:1rem;margin-bottom:.5rem}[data-v-beecdbb2] .prose p{margin-bottom:.75rem;color:hsl(var(--muted-foreground))}[data-v-beecdbb2] .prose ul{margin-left:1.25rem;color:hsl(var(--muted-foreground))}[data-v-beecdbb2] .prose li{margin-bottom:.25rem}[data-v-beecdbb2] .prose code{background:hsl(var(--muted));padding:.125rem .25rem;border-radius:.25rem;font-size:.85em;word-break:break-word;overflow-wrap:anywhere}[data-v-beecdbb2] .prose pre{white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;max-width:100%;overflow-x:auto}[data-v-beecdbb2] .prose pre code{word-break:break-word;overflow-wrap:anywhere}
|
||||
|
|
@ -1 +0,0 @@
|
|||
[data-v-32c92954] .prose{color:hsl(var(--foreground))}[data-v-32c92954] .prose h1{color:hsl(var(--foreground));font-weight:600;margin-top:1rem;margin-bottom:.5rem}[data-v-32c92954] .prose p{margin-bottom:.75rem;color:hsl(var(--muted-foreground))}[data-v-32c92954] .prose ul{margin-left:1.25rem;color:hsl(var(--muted-foreground))}[data-v-32c92954] .prose li{margin-bottom:.25rem}[data-v-32c92954] .prose code{background:hsl(var(--muted));padding:.125rem .25rem;border-radius:.25rem;font-size:.85em;word-break:break-word;overflow-wrap:anywhere}[data-v-32c92954] .prose pre{white-space:pre-wrap;word-break:break-word;overflow-wrap:anywhere;max-width:100%;overflow-x:auto}[data-v-32c92954] .prose pre code{word-break:break-word;overflow-wrap:anywhere}
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as N,u as E,y as P,c as U,a,e as t,w as s,s as f,o as k,q as u,h as c,k as z,i as B,E as I,K as x}from"./index-DVV3ZbZ2.js";import{u as F}from"./devops-HjUgCfao.js";import{_ as w,a as V}from"./CardContent.vue_vue_type_script_setup_true_lang-DdGXaWEa.js";import{_ as $,a as S}from"./CardTitle.vue_vue_type_script_setup_true_lang-CT9DRTds.js";import{_ as y}from"./Input.vue_vue_type_script_setup_true_lang-BM1xhrlf.js";import{_}from"./Button.vue_vue_type_script_setup_true_lang-ClV4YfXb.js";import{_ as O}from"./DevopsConnectForm.vue_vue_type_script_setup_true_lang-5nCaOxp1.js";import{i as C}from"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-BkmDerVR.js";function T(i,l){const n=`/cc-dashboard/api/export/timesheet.csv?from=${i}&to=${l}`,o=document.createElement("a");o.href=n,o.download=`timesheet-${i}-${l}.csv`,o.click()}function A(i,l){const n=`/cc-dashboard/api/export/timesheet.ics?from=${i}&to=${l}`,o=document.createElement("a");o.href=n,o.download=`timesheet-${i}-${l}.ics`,o.click()}const H={class:"p-6 space-y-6 max-w-2xl"},K={class:"space-y-1.5"},M={class:"space-y-1.5"},j={class:"flex items-center justify-between"},q={class:"flex items-center gap-3 flex-wrap"},h={class:"space-y-1.5"},G={class:"space-y-1.5"},J={class:"flex items-center gap-2"},se=N({__name:"SettingsView",setup(i){const l=E(),n=F(),o=f(""),p=f(0),g=f(!1),d=f(""),m=f("");P(()=>{l.user&&(o.value=l.user.username,p.value=l.user.daily_overhead_hours??0),n.fetchIntegration();const v=new Date;m.value=C(v);const e=new Date(v);e.setDate(v.getDate()-30),d.value=C(e)});async function D(){g.value=!0;try{await I.patch("/api/auth/me",{username:o.value,daily_overhead_hours:p.value}),await l.fetchMe(),x.success("Profile saved")}catch{x.error("Failed to save profile")}finally{g.value=!1}}async function b(){try{await n.sync(),x.success("Sync complete")}catch{x.error(n.error??"Sync failed")}}return(v,e)=>(k(),U("div",H,[e[18]||(e[18]=a("h2",{class:"text-lg font-semibold text-foreground"},"Settings",-1)),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[t(S,{class:"text-sm"},{default:s(()=>[...e[6]||(e[6]=[u("Profile",-1)])]),_:1})]),_:1}),t(V,{class:"space-y-4"},{default:s(()=>[a("div",K,[e[7]||(e[7]=a("label",{class:"text-sm font-medium text-foreground"},"Username",-1)),t(y,{modelValue:o.value,"onUpdate:modelValue":e[0]||(e[0]=r=>o.value=r),placeholder:"username"},null,8,["modelValue"])]),a("div",M,[e[8]||(e[8]=a("label",{class:"text-sm font-medium text-foreground"},"Daily Overhead Hours",-1)),t(y,{modelValue:p.value,"onUpdate:modelValue":e[1]||(e[1]=r=>p.value=r),type:"number",min:"0",max:"8",step:"0.25",class:"w-32"},null,8,["modelValue"]),e[9]||(e[9]=a("p",{class:"text-xs text-muted-foreground"}," Hours per day to add for overhead / meetings ",-1))]),t(_,{loading:g.value,onClick:D},{default:s(()=>[...e[10]||(e[10]=[u("Save Profile",-1)])]),_:1},8,["loading"])]),_:1})]),_:1}),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[a("div",j,[t(S,{class:"text-sm"},{default:s(()=>[...e[11]||(e[11]=[u("Azure DevOps Integration",-1)])]),_:1}),c(n).integration?(k(),z(_,{key:0,variant:"outline",size:"sm",loading:c(n).syncing,onClick:b},{default:s(()=>[...e[12]||(e[12]=[u(" Sync Now ",-1)])]),_:1},8,["loading"])):B("",!0)])]),_:1}),t(V,null,{default:s(()=>[t(O)]),_:1})]),_:1}),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[t(S,{class:"text-sm"},{default:s(()=>[...e[13]||(e[13]=[u("Export",-1)])]),_:1})]),_:1}),t(V,{class:"space-y-4"},{default:s(()=>[a("div",q,[a("div",h,[e[14]||(e[14]=a("label",{class:"text-xs text-muted-foreground"},"From",-1)),t(y,{modelValue:d.value,"onUpdate:modelValue":e[2]||(e[2]=r=>d.value=r),type:"date",class:"h-8 text-xs"},null,8,["modelValue"])]),a("div",G,[e[15]||(e[15]=a("label",{class:"text-xs text-muted-foreground"},"To",-1)),t(y,{modelValue:m.value,"onUpdate:modelValue":e[3]||(e[3]=r=>m.value=r),type:"date",class:"h-8 text-xs"},null,8,["modelValue"])])]),a("div",J,[t(_,{variant:"outline",size:"sm",onClick:e[4]||(e[4]=r=>c(T)(d.value,m.value))},{default:s(()=>[...e[16]||(e[16]=[u(" Download CSV ",-1)])]),_:1}),t(_,{variant:"outline",size:"sm",onClick:e[5]||(e[5]=r=>c(A)(d.value,m.value))},{default:s(()=>[...e[17]||(e[17]=[u(" Download ICS ",-1)])]),_:1})])]),_:1})]),_:1})]))}});export{se as default};
|
||||
import{d as N,u as E,y as P,c as U,a,e as t,w as s,s as f,o as k,q as u,h as c,k as z,i as B,E as I,K as x}from"./index-DxmLkgMg.js";import{u as F}from"./devops-CwVu4BME.js";import{_ as w,a as V}from"./CardContent.vue_vue_type_script_setup_true_lang-gLtslFdL.js";import{_ as $,a as S}from"./CardTitle.vue_vue_type_script_setup_true_lang-DiCmBSdj.js";import{_ as y}from"./Input.vue_vue_type_script_setup_true_lang-Gu5Qa2C5.js";import{_}from"./Button.vue_vue_type_script_setup_true_lang-C49zRtnB.js";import{_ as O}from"./DevopsConnectForm.vue_vue_type_script_setup_true_lang-BRLYQ8KS.js";import{i as C}from"./utils-7WVCegLb.js";import"./Spinner.vue_vue_type_script_setup_true_lang-DMwaztwt.js";function T(i,l){const n=`/cc-dashboard/api/export/timesheet.csv?from=${i}&to=${l}`,o=document.createElement("a");o.href=n,o.download=`timesheet-${i}-${l}.csv`,o.click()}function A(i,l){const n=`/cc-dashboard/api/export/timesheet.ics?from=${i}&to=${l}`,o=document.createElement("a");o.href=n,o.download=`timesheet-${i}-${l}.ics`,o.click()}const H={class:"p-6 space-y-6 max-w-2xl"},K={class:"space-y-1.5"},M={class:"space-y-1.5"},j={class:"flex items-center justify-between"},q={class:"flex items-center gap-3 flex-wrap"},h={class:"space-y-1.5"},G={class:"space-y-1.5"},J={class:"flex items-center gap-2"},se=N({__name:"SettingsView",setup(i){const l=E(),n=F(),o=f(""),p=f(0),g=f(!1),d=f(""),m=f("");P(()=>{l.user&&(o.value=l.user.username,p.value=l.user.daily_overhead_hours??0),n.fetchIntegration();const v=new Date;m.value=C(v);const e=new Date(v);e.setDate(v.getDate()-30),d.value=C(e)});async function D(){g.value=!0;try{await I.patch("/api/auth/me",{username:o.value,daily_overhead_hours:p.value}),await l.fetchMe(),x.success("Profile saved")}catch{x.error("Failed to save profile")}finally{g.value=!1}}async function b(){try{await n.sync(),x.success("Sync complete")}catch{x.error(n.error??"Sync failed")}}return(v,e)=>(k(),U("div",H,[e[18]||(e[18]=a("h2",{class:"text-lg font-semibold text-foreground"},"Settings",-1)),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[t(S,{class:"text-sm"},{default:s(()=>[...e[6]||(e[6]=[u("Profile",-1)])]),_:1})]),_:1}),t(V,{class:"space-y-4"},{default:s(()=>[a("div",K,[e[7]||(e[7]=a("label",{class:"text-sm font-medium text-foreground"},"Username",-1)),t(y,{modelValue:o.value,"onUpdate:modelValue":e[0]||(e[0]=r=>o.value=r),placeholder:"username"},null,8,["modelValue"])]),a("div",M,[e[8]||(e[8]=a("label",{class:"text-sm font-medium text-foreground"},"Daily Overhead Hours",-1)),t(y,{modelValue:p.value,"onUpdate:modelValue":e[1]||(e[1]=r=>p.value=r),type:"number",min:"0",max:"8",step:"0.25",class:"w-32"},null,8,["modelValue"]),e[9]||(e[9]=a("p",{class:"text-xs text-muted-foreground"}," Hours per day to add for overhead / meetings ",-1))]),t(_,{loading:g.value,onClick:D},{default:s(()=>[...e[10]||(e[10]=[u("Save Profile",-1)])]),_:1},8,["loading"])]),_:1})]),_:1}),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[a("div",j,[t(S,{class:"text-sm"},{default:s(()=>[...e[11]||(e[11]=[u("Azure DevOps Integration",-1)])]),_:1}),c(n).integration?(k(),z(_,{key:0,variant:"outline",size:"sm",loading:c(n).syncing,onClick:b},{default:s(()=>[...e[12]||(e[12]=[u(" Sync Now ",-1)])]),_:1},8,["loading"])):B("",!0)])]),_:1}),t(V,null,{default:s(()=>[t(O)]),_:1})]),_:1}),t(w,null,{default:s(()=>[t($,null,{default:s(()=>[t(S,{class:"text-sm"},{default:s(()=>[...e[13]||(e[13]=[u("Export",-1)])]),_:1})]),_:1}),t(V,{class:"space-y-4"},{default:s(()=>[a("div",q,[a("div",h,[e[14]||(e[14]=a("label",{class:"text-xs text-muted-foreground"},"From",-1)),t(y,{modelValue:d.value,"onUpdate:modelValue":e[2]||(e[2]=r=>d.value=r),type:"date",class:"h-8 text-xs"},null,8,["modelValue"])]),a("div",G,[e[15]||(e[15]=a("label",{class:"text-xs text-muted-foreground"},"To",-1)),t(y,{modelValue:m.value,"onUpdate:modelValue":e[3]||(e[3]=r=>m.value=r),type:"date",class:"h-8 text-xs"},null,8,["modelValue"])])]),a("div",J,[t(_,{variant:"outline",size:"sm",onClick:e[4]||(e[4]=r=>c(T)(d.value,m.value))},{default:s(()=>[...e[16]||(e[16]=[u(" Download CSV ",-1)])]),_:1}),t(_,{variant:"outline",size:"sm",onClick:e[5]||(e[5]=r=>c(A)(d.value,m.value))},{default:s(()=>[...e[17]||(e[17]=[u(" Download ICS ",-1)])]),_:1})])]),_:1})]),_:1})]))}});export{se as default};
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as l,o as n,c as o,n as t,a as r}from"./index-DVV3ZbZ2.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-DxmLkgMg.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 _};
|
||||
|
|
@ -1 +1 @@
|
|||
import{E as e}from"./index-DVV3ZbZ2.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{E as e}from"./index-DxmLkgMg.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 +1 @@
|
|||
import{E as t}from"./index-DVV3ZbZ2.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{E as t}from"./index-DxmLkgMg.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{E as s,C as I,s as o}from"./index-DVV3ZbZ2.js";const i={getIntegration:()=>s.get("/api/devops/integration"),saveIntegration:e=>s.put("/api/devops/integration",e),deleteIntegration:()=>s.delete("/api/devops/integration"),sync:()=>s.post("/api/devops/sync"),workItems:e=>s.get("/api/devops/work-items",{params:e?{state:e}:void 0})},m=I("devops",()=>{const e=o(null),l=o([]),r=o(!1),n=o(!1),c=o(null);async function u(){n.value=!0;try{const t=await i.getIntegration();e.value=t.data}catch{e.value=null}finally{n.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){n.value=!0;try{const a=await i.workItems(t);l.value=a.data}catch{l.value=[]}finally{n.value=!1}}return{integration:e,workItems:l,syncing:r,loading:n,error:c,fetchIntegration:u,saveIntegration:d,deleteIntegration:g,sync:f,fetchWorkItems:y}});export{m as u};
|
||||
import{E as s,C as I,s as o}from"./index-DxmLkgMg.js";const i={getIntegration:()=>s.get("/api/devops/integration"),saveIntegration:e=>s.put("/api/devops/integration",e),deleteIntegration:()=>s.delete("/api/devops/integration"),sync:()=>s.post("/api/devops/sync"),workItems:e=>s.get("/api/devops/work-items",{params:e?{state:e}:void 0})},m=I("devops",()=>{const e=o(null),l=o([]),r=o(!1),n=o(!1),c=o(null);async function u(){n.value=!0;try{const t=await i.getIntegration();e.value=t.data}catch{e.value=null}finally{n.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){n.value=!0;try{const a=await i.workItems(t);l.value=a.data}catch{l.value=[]}finally{n.value=!1}}return{integration:e,workItems:l,syncing:r,loading:n,error:c,fetchIntegration:u,saveIntegration:d,deleteIntegration:g,sync:f,fetchWorkItems:y}});export{m as u};
|
||||
File diff suppressed because one or more lines are too long
1
src/static/assets/index-DXTScSCX.css
Normal file
1
src/static/assets/index-DXTScSCX.css
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 +1 @@
|
|||
import{E as l,C as w,s as i}from"./index-DVV3ZbZ2.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"})),$=w("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(h=>h.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 g(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:g}});export{b as t,$ as u};
|
||||
import{E as l,C as w,s as i}from"./index-DxmLkgMg.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"})),$=w("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(h=>h.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 g(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:g}});export{b as t,$ as u};
|
||||
|
|
@ -14,8 +14,8 @@
|
|||
else { document.documentElement.classList.remove('dark'); }
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/cc-dashboard/static/assets/index-DVV3ZbZ2.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/cc-dashboard/static/assets/index-CS_oOq1J.css">
|
||||
<script type="module" crossorigin src="/cc-dashboard/static/assets/index-DxmLkgMg.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/cc-dashboard/static/assets/index-DXTScSCX.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ const pageTitle = computed(() => {
|
|||
const routeToTitle: Record<string, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
calendar: 'Calendar',
|
||||
planner: 'Planner',
|
||||
projects: 'Projects',
|
||||
'project-detail': 'Project Details',
|
||||
live: 'Live Feed',
|
||||
reports: 'AI Reports',
|
||||
keys: 'API Keys',
|
||||
devops: 'Azure DevOps',
|
||||
settings: 'Settings',
|
||||
admin: 'Admin',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, reactive } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { dashboardApi } from '@/api/endpoints/dashboard'
|
||||
import Card from '@/components/ui/Card.vue'
|
||||
|
|
@ -9,6 +9,7 @@ import CardContent from '@/components/ui/CardContent.vue'
|
|||
import Spinner from '@/components/ui/Spinner.vue'
|
||||
import { formatDuration, formatDateTime } from '@/lib/utils'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { toast } from 'vue-sonner'
|
||||
import type { ProjectDetail } from '@/types'
|
||||
|
||||
const route = useRoute()
|
||||
|
|
@ -20,6 +21,46 @@ const authStore = useAuthStore()
|
|||
const data = ref<ProjectDetail | null>(null)
|
||||
const loading = ref(false)
|
||||
const summarizingSession = ref<string | null>(null)
|
||||
const editMode = ref(false)
|
||||
const saving = ref(false)
|
||||
const editForm = reactive({ display_name: '', client: '', job_number: '', repo_url: '' })
|
||||
|
||||
function openEdit() {
|
||||
if (!data.value) return
|
||||
editForm.display_name = data.value.project.display_name ?? ''
|
||||
editForm.client = data.value.project.client ?? ''
|
||||
editForm.job_number = data.value.project.job_number ?? ''
|
||||
editForm.repo_url = data.value.project.repo_url ?? ''
|
||||
editMode.value = true
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
saving.value = true
|
||||
try {
|
||||
const res = await fetch(`/cc-dashboard/api/projects/${projectId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${authStore.token}`,
|
||||
},
|
||||
body: JSON.stringify(editForm),
|
||||
})
|
||||
if (!res.ok) throw new Error(await res.text())
|
||||
const updated = await res.json()
|
||||
if (data.value) {
|
||||
data.value.project.display_name = updated.display_name
|
||||
data.value.project.client = updated.client
|
||||
data.value.project.job_number = updated.job_number
|
||||
data.value.project.repo_url = updated.repo_url
|
||||
}
|
||||
editMode.value = false
|
||||
toast.success('Saved')
|
||||
} catch {
|
||||
toast.error('Failed to save')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
|
|
@ -77,39 +118,109 @@ async function summarizeSession(sessionId: string) {
|
|||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-start justify-between gap-4 flex-wrap">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-1 flex-wrap">
|
||||
<h2 class="text-xl font-bold text-foreground">{{ data.project.display_name }}</h2>
|
||||
<span v-if="dateParam" class="text-sm text-primary font-medium">{{ dateParam }}</span>
|
||||
<button
|
||||
v-if="dateParam"
|
||||
class="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
@click="router.push({ name: 'project-detail', params: { id: projectId } })"
|
||||
>
|
||||
← All time
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 mt-1 flex-wrap">
|
||||
<span v-if="data.project.client" class="text-sm text-muted-foreground">
|
||||
{{ data.project.client }}
|
||||
</span>
|
||||
<span
|
||||
v-if="data.project.job_number"
|
||||
class="text-xs bg-muted text-muted-foreground px-2 py-1 rounded"
|
||||
>
|
||||
{{ data.project.job_number }}
|
||||
</span>
|
||||
<a
|
||||
v-if="data.project.repo_url"
|
||||
:href="data.project.repo_url"
|
||||
target="_blank"
|
||||
class="text-xs text-primary hover:underline"
|
||||
>
|
||||
Repository →
|
||||
</a>
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- View mode -->
|
||||
<template v-if="!editMode">
|
||||
<div class="flex items-center gap-3 mb-1 flex-wrap">
|
||||
<h2 class="text-xl font-bold text-foreground">{{ data.project.display_name }}</h2>
|
||||
<span v-if="dateParam" class="text-sm text-primary font-medium">{{ dateParam }}</span>
|
||||
<button
|
||||
v-if="dateParam"
|
||||
class="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
@click="router.push({ name: 'project-detail', params: { id: projectId } })"
|
||||
>
|
||||
← All time
|
||||
</button>
|
||||
<button
|
||||
class="p-1 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors"
|
||||
title="Edit project"
|
||||
@click="openEdit"
|
||||
>
|
||||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 mt-1 flex-wrap">
|
||||
<span v-if="data.project.client" class="text-sm text-muted-foreground">
|
||||
{{ data.project.client }}
|
||||
</span>
|
||||
<span
|
||||
v-if="data.project.job_number"
|
||||
class="text-xs bg-primary/8 text-primary border border-primary/20 px-2 py-0.5 rounded-full font-medium"
|
||||
>
|
||||
{{ data.project.job_number }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-xs text-muted-foreground/60 border border-dashed border-muted-foreground/30 px-2 py-0.5 rounded-full cursor-pointer hover:border-primary/40 hover:text-primary/60 transition-colors"
|
||||
@click="openEdit"
|
||||
>
|
||||
+ job number
|
||||
</span>
|
||||
<a
|
||||
v-if="data.project.repo_url"
|
||||
:href="data.project.repo_url"
|
||||
target="_blank"
|
||||
class="text-xs text-primary hover:underline"
|
||||
>
|
||||
Repository →
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div v-else class="space-y-3">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="text-xs font-medium text-muted-foreground mb-1 block">Project name</label>
|
||||
<input
|
||||
v-model="editForm.display_name"
|
||||
class="w-full px-3 py-1.5 text-sm rounded-lg border border-border bg-background focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-medium text-muted-foreground mb-1 block">Client</label>
|
||||
<input
|
||||
v-model="editForm.client"
|
||||
class="w-full px-3 py-1.5 text-sm rounded-lg border border-border bg-background focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-medium text-muted-foreground mb-1 block">Job number</label>
|
||||
<input
|
||||
v-model="editForm.job_number"
|
||||
placeholder="e.g. JOB-12345"
|
||||
class="w-full px-3 py-1.5 text-sm rounded-lg border border-border bg-background focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-medium text-muted-foreground mb-1 block">Repo URL</label>
|
||||
<input
|
||||
v-model="editForm.repo_url"
|
||||
placeholder="https://github.com/..."
|
||||
class="w-full px-3 py-1.5 text-sm rounded-lg border border-border bg-background focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
:disabled="saving"
|
||||
class="px-3 py-1.5 text-xs font-semibold rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 transition-colors"
|
||||
@click="saveEdit"
|
||||
>
|
||||
{{ saving ? 'Saving…' : 'Save' }}
|
||||
</button>
|
||||
<button
|
||||
class="px-3 py-1.5 text-xs font-medium rounded-lg text-muted-foreground hover:bg-muted/40 transition-colors"
|
||||
@click="editMode = false"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-right shrink-0">
|
||||
<p class="text-2xl font-bold text-foreground">
|
||||
{{ formatDuration(data.daily.reduce((sum, p) => sum + p.hours, 0)) }}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -56,9 +56,9 @@ function renderMarkdown(md: string): string {
|
|||
return marked(md) as string
|
||||
}
|
||||
|
||||
async function downloadReport(reportId: string, fmt: 'md' | 'html') {
|
||||
async function downloadReport(report: AiReport, fmt: 'md' | 'html') {
|
||||
try {
|
||||
const res = await fetch(`/cc-dashboard/api/reports/${reportId}/export?format=${fmt}`, {
|
||||
const res = await fetch(`/cc-dashboard/api/reports/${report.id}/export?format=${fmt}`, {
|
||||
headers: { Authorization: `Bearer ${authStore.token}` },
|
||||
})
|
||||
if (!res.ok) { toast.error('Export failed'); return }
|
||||
|
|
@ -66,7 +66,7 @@ async function downloadReport(reportId: string, fmt: 'md' | 'html') {
|
|||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `report.${fmt}`
|
||||
a.download = `report-${report.period_date}-${report.type}.${fmt}`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch {
|
||||
|
|
@ -155,11 +155,11 @@ async function downloadReport(reportId: string, fmt: 'md' | 'html') {
|
|||
<div class="flex gap-2 mb-3">
|
||||
<button
|
||||
class="text-xs px-2.5 py-1 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/30 transition-colors"
|
||||
@click="downloadReport(report.id, 'md')"
|
||||
@click="downloadReport(report, 'md')"
|
||||
>↓ Markdown</button>
|
||||
<button
|
||||
class="text-xs px-2.5 py-1 rounded-lg border border-border text-muted-foreground hover:text-foreground hover:border-foreground/30 transition-colors"
|
||||
@click="downloadReport(report.id, 'html')"
|
||||
@click="downloadReport(report, 'html')"
|
||||
>↓ HTML</button>
|
||||
</div>
|
||||
<div
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue