From 98cf813f0005693ef302a71fe0de8a5d19cf1e4a Mon Sep 17 00:00:00 2001 From: NPS Agent Date: Thu, 21 May 2026 11:33:32 +0930 Subject: [PATCH] Refactor task audits, integrate OpenClaw, and fix timezone handling --- app.jsx | 10 +++------- backend/main.py | 18 ++++++++++++++++++ components.jsx | 23 +++++++++++++++++++---- dashy.db | Bin 110592 -> 122880 bytes screens.jsx | 22 ++++++++++++++++++++-- 5 files changed, 60 insertions(+), 13 deletions(-) diff --git a/app.jsx b/app.jsx index 531c407..5ea70ab 100644 --- a/app.jsx +++ b/app.jsx @@ -103,7 +103,8 @@ function App() { React.useEffect(() => { window.dbUsers = dbUsers; - }, [dbUsers]); + window.workspace = workspace; + }, [dbUsers, workspace]); React.useEffect(() => { const handleKeyDown = (e) => { @@ -172,15 +173,10 @@ function App() { const addTask = async ({ title, description, assignee, priority }) => { try { - const t = await api.createTask({ + await api.createTask({ title, description, assignee_id: assignee, priority, added_by: meId, source: 'manual', status: 'open', tags: [] }); - await api.addAudit({ - actor: meId, action: 'task_created', - summary: 'Created task "' + title + '" for ' + (merge(assignee)||{}).name, - target: t.id - }); setAdding(null); } catch(e) { console.error(e); diff --git a/backend/main.py b/backend/main.py index 57838e6..4602a79 100644 --- a/backend/main.py +++ b/backend/main.py @@ -201,6 +201,24 @@ async def create_task(task: schemas.TaskCreate, db: Session = Depends(get_db), c db.commit() db.refresh(db_task) + + # Create audit log entry + assignee_name = "Unassigned" + if db_task.assignee_id: + assignee = db.query(models.User).filter(models.User.id == db_task.assignee_id).first() + if assignee: + assignee_name = assignee.name + + db_audit = models.AuditLog( + id=f"al_{uuid.uuid4().hex[:8]}", + actor=current_user.id, + action='task_created', + summary=f'Created task "{db_task.title}" for {assignee_name}', + target=db_task.id + ) + db.add(db_audit) + db.commit() + await manager.broadcast(json.dumps({"type": "refresh"})) return db_task diff --git a/components.jsx b/components.jsx index 83eb74a..c7a2b0a 100644 --- a/components.jsx +++ b/components.jsx @@ -74,21 +74,36 @@ function SourceTag({ source }) { ); } +function getSafeTimezone() { + const tz = window.workspace ? window.workspace.timezone : undefined; + if (!tz) return undefined; + try { + Intl.DateTimeFormat(undefined, { timeZone: tz }); + return tz; + } catch (e) { + return undefined; // Fallback to browser time if invalid + } +} + function relTime(iso) { - const now = new Date('2026-05-08T10:30:00'); + if (typeof iso === 'string' && !iso.endsWith('Z') && !iso.includes('+')) iso += 'Z'; + const now = new Date(); const then = new Date(iso); const diff = (now - then) / 1000; if (diff < 60) return 'just now'; if (diff < 3600) return Math.floor(diff/60) + 'm ago'; if (diff < 86400) return Math.floor(diff/3600) + 'h ago'; if (diff < 86400*7) return Math.floor(diff/86400) + 'd ago'; - return then.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); + + return then.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: getSafeTimezone() }); } function fmtDateTime(iso) { + if (typeof iso === 'string' && !iso.endsWith('Z') && !iso.includes('+')) iso += 'Z'; const d = new Date(iso); - return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }) + - ' · ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); + const tz = getSafeTimezone(); + return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', timeZone: tz }) + + ' · ' + d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', timeZone: tz }); } function findUser(id) { diff --git a/dashy.db b/dashy.db index f84c9e8fdc43efea1e44b29ea05a326665aca51f..d95039b8ba6e38737a2fd75e01491070fbd38b17 100644 GIT binary patch delta 9535 zcmbta3v?XSdETqpm-aCuA!J#8&f3_Pi!9G`XJ6Uv!7fOc9B`z0T5GL#*VyVN_>KT~f||3sZxNbPuz>Zay<;D@BWXW@;B^*xvLBwh+oXQ{o^XQ>GJJb8k= zncS58IQb9B$CB0L)yY)iy~Ilksh+PTtV?>rf!=|Bz9J|GW{ZVFv0`uxm)^vPn>dzc z*euUxMQ)f12U6~?3DYzgMH9}mo98bfKY=I5WI1^_33*x`Q`uxxndh~0sNinrhZ9(Z zmfaPjTxnI9O=m@6IF5HSnGDC6I_L53*llGQE-T2xF}$4@RasR;Y4PpuZblyN#=Fy+ zs_?p~oNG6e<)z{1d3N_UyPAR9xrLi=f4mp;JaaTSN{E3&CnB$Gy&?F1@X_E+!6b2- z*hXC3_0z6LyQaV|!P8(4drv#aVj1 zpp?tG-33FhOh_;cWJ%T}I~Pgu0|ToA$^^&gX_k?pzmZkGI6f;do`dqm-MPXI9s@d8 z5N&G;lJ0bQ!=JH#z<*1 z-9gGMMtaaTkYfqgv7#av0>ih7Wu+{S9J>R$kgk^K*<#Vm6?W4zO2wGXDfu$;s&15u z735RsczCHaNz*e-TC(-JeyS=;Ds*V8daRHYP$;+iM5|NEy2WXxz&4XInJf>_f!pF< z@>WvORw1c>3>n2tOD%RW=`08BVmppOw3RxYCZ(CQqRPIqpW6hFo)KZ<$nxn9!tM5? zt80o)DH9pP;5o@?QB}x7Z3PEp!7Arn0K$xDa;nLgTHc`|54Xcap{r?GZ{MC}nG?$EYt{)n>{}3TXsMzIjLy2rsD3$IeI>+u z2LKW0o;yKQPTMy_nttzAs2No@SwU#0$uj&lrI2eq$AyvC&cw!&Fbk{?JQkp)s1fpe z16Tz6Abq44gxv*i}Bc7rfB zPq`$Ux4_hpHVs4Lgu3;062UnX1?=$nJc-ytPpKv=GVn5BtLuAtzQLZUSE8}`WL=CT z8!9YMVh1TFIJZUtvtevLQQxeyB219!Hk~+F?__T?v^kEHf;Nj8B`vF&HqKrlYbdoz z7@v>T?a(<@kaT$wk|sOE30%&`=ez5qLWb20S*~9Tb9yQJRn>KZF*a;AP6kx)p= zWsob!=RH zacgozjyQp9`1pKRom4cj0=aljkw0Uj{B8(ga8^xJY-hdgpSyze6 z7_g>`Rr@y2{6nrhmX&mPvs;cH;=)XpD^({+20IS?)hqRan_xTLE}0siqVYJo>v+}c{1(0O?MWYsL#hZ;cv zF_@~eFf^L5HIro_PD#7mPcUaATOsEHbWN^68#78S@4Lh=SNPU#w-0(z>K0GNG-0G= z+L`iM-l^T=7f%j{cP(KbK*zViH5aO5WaM;9nt%$iS+*S-x|pzBP_*eXJya?}0I22< zK#*9}ig}1?Xr-XjdFXM75;rzG8Oxov_qz(aMH{9L!O#u1<>o`7@Ec`Bw@6Ru1`G)2 zND!>J!mHec>Wm8A!g(dUGGS-}r$NwHw&xm$Co{UrYGS)5T*fVjcW!L!AD$-3X_?Wq zvb`_rYF#5>?iUo9S2JuUcSd#&1;KUeciC0X#?M0=Wz&?K!54}nXd~ks41(dbYhz_X z(KJ;M81Z(yg5-2VQr$EZm2JR}1}W#txpXIv!Slh&2AZr9b{R46^YqeG0~Wj12xMe8 zb-K)NXL-nvz0Q<3u%#74cN*Uw3aWH z4Z3L3YPFmz809jwSXT}p+0BhkCc~N{42yks2{|*;2O~m)F_B3(N;~5NJc;bK=^MSFf+gd3(kq+3kiBm*!x$X*kFyarOtnh_whdZUAD*&bw?70M&F2_Rftg zfh8|qAuhJ3(IH9hAu-Tz83^y|5y!X%0v78o&O4>wxf+InLaWMd_Z&)ws^$%tS7D>c zay&f-QM=nZT``;6XAINVDKKDW6#BXwc6Oc-1WJObo5C~ihUwsI0qV7d-pRkGF5L)% z%QtS)4O6M+D_g3S;-;Oj>Xu82W^AdJE3-;Ir)<9AfL<^Te47Scqr{H`KX5{mVD(V9 z8vS1AD8Y1pH#i#mfS^M=qIbkZY9@A+{031XFOKviUW}iP_d8cif;C;yl4DMSRXy=_ z!AF8j_fT{+{3f|J(eE6c1S{4jMybCGz8QTaQR;pq_8sEgOk|%L?Zyxt{JyWR52C^Ep*6<0;CD$P1z;Ov@Pa0& z@)e1%1`^m7f2*p&DS*gpGsv2HG8Pqs+6(vRXQ5!;r!<7IdgiexuA(14K?398Er-{IlfOk`E;Bg3~%Px;}a~`c`sh=zlO*VFUB+Rq4-kb?(o;+(Qug9ANwF`M%KsvHTHV=aBxTP(b$X8 zC9%JZ{YmUAu`kDNi(VCTl6%4DR|L-xzYMMl{vwzRmWYo+7Y8?os=*rZI`IVY+|$R7 z12?w9mM*}N8m!XbIPxFv@xZ-kje9Y0kH7YNCve1H`&|vV+h6+)2RMw@xTgSj^$=^j zVDiBz9Ms$g7JP#7Gq}TN;iF$*VG_HTGJO`%G8jsz;Zl3@T#DL+8@TXTm_tw?~oRWAm zDWLfr{Yev0(Hc*XfZ~7d4~D>`F9FYcz=TiW&uYMz&>9b8z%I1L^A>OmTqj{V#KRPD z^Rq8hL3SA#f_ENJAG$YxCH3J#@6rFDz7SZkEC$yp(hN|)fn)Z!sh?4=Q?F7#qW+fp zF7+(+6!|puhvW(BIJJ@dJ~dC?NyW*3Bj2DhYo0 zUl!B4ztue%xG(x^@ITR7bWP;p$fe<@x=dm)^jz@A#MxuV#(Qg*l2nSVB?kuj2mARz zfOkeufx9=>(Sp}bdcDD>w`!M1sNU^0s&BAA)xQPq?W#!5sW3=6Ll1#{mvqoDa{RL0 zJ!GnXW8VOz@wm@9;7Y4=r%0`>F-yqQc(t!@aHY4ktAU{}1abRtx#Z7$-3Gi9+;RdXu$pzoXV<{l z9YEe{9qZ$r^sKSB3h`DJ{@?=VPkJ42f!cd78d|i6uUlZlk;$H_RhLnzU3UMV@AClk zImY@}tE~4rqSKYQPr&=I_J&S33zOlThi>4!EwHattekFq5?gwx&V}1JLSI+(odSE% zN^S-YS77sBE*V?T#`od?13{th96NIl}{4*=()ecfd5*5sgjnkWg&ZtLmHod(>xPBJF`=e40I z*}H4PbA%`1sfWPfMI`W{pE}<;4IX!a z15I!GyLPCyVl|oCI@hczMl|^PW?{fDGNu(fQnr_-w!tjxj-)ziEI8XeuYxW5dE?^M zqqS>SQK>6yUxfD)v zQ%3g(3>FrXGUDCb3y4E^IBkiaIavGbN-A|(ZF}=2z!-77H`2O`GcesdN8u~MVdgVaB ZZSpE|23pm znu;u0B5g#l2;Hz1L^@3eXk*Ih*iKW8JtI~ujwMJ9w}E#Jh;wx*r{y|Hxqi4Nt80e$ zK~vVmEzo`ZSVnM=1q2I*`7CVOh)F?dd23rR5bKE6#-d$i4PBA;!{I3WO?`tQuJ}ssNrDADOTGbk~U7A-b(el{aVO+dh^@%~XMjTW<{Hp3yt)ifsls}Z8 zlzC-ZNhu@3pfsbLQ2LaZ5|jp&2B}}!rFfMRB~SQ3kpw}$D+R@3`8Rn<=x6S3{G8D} zVUnJS;p%lRjo*W8gi&Z#y@YA)bW*;fBhu&Au+nktlG{U(NTA9S>1t)3 zF`T1TINa`PcUAS1Uj90ZkK=B;G>Zg;>Mhi1FSX^`^6eFd4O7SPb`un_TL#4%&!gb< z#I@X9`~B_YD1J>WTQxt5c*Oh@ct0^OBErs3;7{_@FA#}46>8h}a=zUV)Ea)KsK+z# z;qOIk=mKsxL6SY5#?dSk-zb80ZybzMo&WRLo^yC8OL;18m#Fpmru?=0tm)b2OSqpc zOyOgMW}$a={XO>5BtApv$EZ@*-OysD);cZUW_$P^{mMmLpRJ4=$}A%u09&|(M_Ax8 zKJAi?vJJcl{x$}~udo73Fc06rmoNos7=>YY3yy*pqDC_vfStx%kb!y6;;a7w3+p(O diff --git a/screens.jsx b/screens.jsx index 86f17a0..c827cb9 100644 --- a/screens.jsx +++ b/screens.jsx @@ -917,7 +917,13 @@ function AuditScreen({ entries, onOpen }) { const actor = r.actor === 'system' ? null : findUser(r.actor); return (
  • - {new Date(r.at).toLocaleTimeString('en-US',{hour:'numeric', minute:'2-digit'})} + {(() => { + let safeTz = undefined; + if (window.workspace && window.workspace.timezone) { + try { Intl.DateTimeFormat(undefined, { timeZone: window.workspace.timezone }); safeTz = window.workspace.timezone; } catch(e) {} + } + return new Date(typeof r.at === 'string' && !r.at.endsWith('Z') && !r.at.includes('+') ? r.at + 'Z' : r.at).toLocaleTimeString('en-US',{hour:'numeric', minute:'2-digit', timeZone: safeTz}); +})()} {actor ? : SYS} {actor ? actor.name : 'System'} @@ -1189,6 +1195,7 @@ function WorkspaceTab({ user, isAdmin, dbUsers = [], onSwitchUser, onCreateUser, const [wsName, setWsName] = React.useState(workspace ? workspace.name : ''); const [wsTz, setWsTz] = React.useState(workspace ? workspace.timezone : ''); const [wsSaved, setWsSaved] = React.useState(false); + const [wsError, setWsError] = React.useState(''); // User editing state const [editingUserId, setEditingUserId] = React.useState(null); @@ -1234,6 +1241,16 @@ function WorkspaceTab({ user, isAdmin, dbUsers = [], onSwitchUser, onCreateUser, }; const handleUpdateWorkspace = async () => { + setWsError(''); // Clear previous errors + if (wsTz) { + try { + Intl.DateTimeFormat(undefined, { timeZone: wsTz }); + } catch (e) { + setWsError('Not a proper timezone (e.g., Asia/Tokyo)'); + return; // Stop the save process + } + } + await onUpdateWorkspace({ name: wsName, timezone: wsTz }); setWsSaved(true); setTimeout(() => setWsSaved(false), 2000); @@ -1378,12 +1395,13 @@ function WorkspaceTab({ user, isAdmin, dbUsers = [], onSwitchUser, onCreateUser, {isAdmin && (
    {wsSaved && Saved} - +
    )}