From 7aa3521775cdc0dfb3516f5bce2608b8b6133f87 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Mon, 24 Oct 2022 15:42:47 -0500 Subject: [PATCH 1/7] Don't crash app home if count comes back null; don't crash table if no field named 'id' --- src/qqq/pages/app-home/index.tsx | 22 ++++++++++++++++++-- src/qqq/pages/entity-list/index.tsx | 32 +++++++++++++++-------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx index fd17fc6..e14d8b4 100644 --- a/src/qqq/pages/app-home/index.tsx +++ b/src/qqq/pages/app-home/index.tsx @@ -55,6 +55,8 @@ function AppHome({app}: Props): JSX.Element const [reports, setReports] = useState([] as QReportMetaData[]); const [childApps, setChildApps] = useState([] as QAppMetaData[]); const [tableCounts, setTableCounts] = useState(new Map()); + const [tableCountNumbers, setTableCountNumbers] = useState(new Map()); + const [tableCountTexts, setTableCountTexts] = useState(new Map()); const [updatedTableCounts, setUpdatedTableCounts] = useState(new Date()); const [widgets, setWidgets] = useState([] as any[]); @@ -113,6 +115,8 @@ function AppHome({app}: Props): JSX.Element setChildApps(newChildApps); const tableCounts = new Map(); + const tableCountNumbers = new Map(); + const tableCountTexts = new Map(); newTables.forEach((table) => { tableCounts.set(table.name, {isLoading: true, value: null}); @@ -122,6 +126,20 @@ function AppHome({app}: Props): JSX.Element const count = await qController.count(table.name); tableCounts.set(table.name, {isLoading: false, value: count}); setTableCounts(tableCounts); + + if(count !== null && count !== undefined) + { + tableCountNumbers.set(table.name, count.toLocaleString()); + tableCountTexts.set(table.name, count === 1 ? "total record" : "total records"); + } + else + { + tableCountNumbers.set(table.name, "--"); + tableCountTexts.set(table.name, " "); + } + setTableCountNumbers(tableCountNumbers); + setTableCountTexts(tableCountTexts); + setUpdatedTableCounts(new Date()); }, 1); }); @@ -261,8 +279,8 @@ function AppHome({app}: Props): JSX.Element {table.iconName || app.iconName}}} direction="right" /> diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 54206bd..aacc5ca 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -592,9 +592,26 @@ function EntityList({table, launchProcess}: Props): JSX.Element row[field.name] = value; }); + if(!row["id"]) + { + row["id"] = row[tableMetaData.primaryKeyField]; + } + rows.push(row); }); + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // do this secondary check for columnsToRender - in case we didn't have any rows above, and our check for string isn't enough. // + // ... shouldn't this be just based on the field definition anyway... ? plus adornments? // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + fields.forEach((field) => + { + if(field.possibleValueSourceName) + { + columnsToRender[field.name] = true; + } + }); + if(columnsModel.length == 0) { setupGridColumns(columnsToRender); @@ -877,21 +894,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element return ""; } - function getRecordIdsForProcess(): string | QQueryFilter - { - if (selectFullFilterState === "filter") - { - return (buildQFilter(filterModel)); - } - - if (selectedIds.length > 0) - { - return (selectedIds.join(",")); - } - - return ""; - } - const openModalProcess = (process: QProcessMetaData = null) => { if (selectFullFilterState === "filter") From b87dbf231db9ddc687cbbfb3e30b0c2efe7be618 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 27 Oct 2022 13:38:42 -0500 Subject: [PATCH 2/7] add section icons; fix 'All All' on entity-list --- package.json | 2 +- public/integration-logos/deposco.png | Bin 0 -> 11168 bytes public/integration-logos/easypost.png | Bin 0 -> 13371 bytes public/integration-logos/infoplus.png | Bin 0 -> 14597 bytes public/integration-logos/shipstation.png | Bin 0 -> 13613 bytes src/qqq/pages/app-home/index.tsx | 18 +++++++++++++----- src/qqq/pages/entity-list/index.tsx | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 public/integration-logos/deposco.png create mode 100644 public/integration-logos/easypost.png create mode 100644 public/integration-logos/infoplus.png create mode 100644 public/integration-logos/shipstation.png diff --git a/package.json b/package.json index 7ff3342..d083061 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.25", + "@kingsrook/qqq-frontend-core": "1.0.27", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", diff --git a/public/integration-logos/deposco.png b/public/integration-logos/deposco.png new file mode 100644 index 0000000000000000000000000000000000000000..dfacdc66be80518446222260c0410161f476de4a GIT binary patch literal 11168 zcmeHsXH-+$);18NBOq0}G^Hj4LMQazi_(Qa0)#H1_g)q0h=6oKKv8<{NSCS?=5vODhlz@3_<}tJ zqwESOGsfNI3q6l zKfinO^r$HKF*)vA<>5*D93iPxzXg|a%=1d#3=`h$TF~*q>G>r&&Nc1T^4eC#WVh4} zBjKn?w0Fpfc%8-BWaK#y^Ln{(?iTU&(LGcyVfb2fHX}=)7Y-})Liffp$z*zd;YVlS zd1A_Qo1Kfvq2cM6Yj>4X&fc@-{jZbH)TElppsV}YHgQKsgMt3d)CR98uPFSlByB#U zP6qjW2E)W!g^uR?{Xe>gatgXbczC~dEOe2sTnh$pJ(T1OM-(w!s!wL#=k8IcHTCTI zaUn7Xj~;biCe&)mhR#ykKWI6-o7`H=HD5a^cUwpgK}oywR!2CYGjk)e@01l`r#*6>ARY9j|||I60&vG5gE`zGUlU%dMwD*boy4>-iYkCU+oX1Hx?8fcoq zMVZs98!_LF;JTku^*;7m%zi3bp05|U?kfhLl>f9=y-XCo$+rIVz&-Fqz4^hh*_TvX z+xe%GX2m%((kD+ePEu}yy3Lt}%$xU%p5LqiAVEMI5XU6r{a~^+j_Zq}=c~ie_}NSf zYzt%I=-L7?{qJ3-I6~9V@)xz{tY-~POa;k#6(!iD%?0mOZr0hKf4g5O*=JA@N`Je= zQ;;zuy*m3nC)uK8cT(Tar^GAWb{!JX zRN3Y_YyrVX0zSpp;PLIs>=#HEH#K=a~D-(kI zVT5J}Peo!0i&At})l=Jr0Ro4`0q^q^jt$s8+7xsx?w_Vu9!d7}`87YyRFaC0A_;n? zXz%ef46E>Dqod8n!Z=akG0NE2GpL9hEcLmFuqEv0^Njkv58vn*#Bij^`-HNGvRpCn zm0u5ZC(9@r2VE`O@d%Bcb?vj&QWHNeG6AV&K)QBhv$#jAGHgD49e3RpIP~H6WyU-# zQ}en$=vm#gwz_}dyV><%o80hL_{R=urdrJm?`aT$?Gs(o#r{6*xHEis*8Q^N{`46~ zt}!K1z3IEO+m)2ZE%x)#ZEWHyUpWqak1F3Bis2@WB|pvL?W?|r(1_hM8rFX8r5)9e zk!YMgq({RH)ODv%)x!FcBWBm@X2KO3W=+7qCis@bKSjtcn-w?7=Vk1@0jCKo?6qy% z9Jefm+*vD~QnwWmNVyT288kPZv$XT}I#kW5?yg37kZ!WWV@CRU`=15zcVbG1^wY~v z>J7QwHOE-0+Ht6!n2IC!7#w!SwFz0fYPh>;e+Kcig>U!|k>%#%04hpf_Kb;Jo#9yO zcn6H0oQ*i_sb+r-!bUyUW!oqz6;Kk&o1l95Gbrg1g?ZiEl0s{bjP0zU@0*$T=}K|Q z;GD*DkAyDnzMt){TDoft7z#S2KO`=wvmvRhbemQA;X57>)Zd`cyGq=br{YVKPoi99 z;d(n~OkS@}#ID6xH(a@YaHq48YFm$R1h_Kh!(YeD6(UhMdft;54BN$rBf1~Ttwcs)*$K?6^u!NE9GxB2 zY_b@}Dhg|4F(}x%h~g&&Fv36PRomKkFhvv@`(jUCAVHPs{_@f9bifBwvMa{P7Qm>8 zk6;(G)IJ?X@&5E9&Npg(q^INzb9JAH<`W=V6Jl+>kv_92?!ieDyegQ7S2+eB3#|Lz zJ--+k#4zCSe50#L;Yho}V>Q__)kGVraUJJ zDM>oNhoc@XlXqc@#HND$6I(CoPe)nmJiR93-s4dzvOH9oz!*~SQ<>)Hx zWE>*DggP`diJQJXG3qLBesy_ntEL65zx74WBLk;^1+eGYmid{MAO?yb{Fc-ba3}SL zZAtZIR5)1nUdQfu%KIp<5Iy{FgNYv<3*{UgGPLMLaJ2Tl90zadDY5&%JZ$raMsp+6 ziE~EAoha&X#l8;Md*sP;mqh%CG3g}I-|Sx`>xSH}5;muJPdVR!Np;L*dcdhjFfcq( z((FG)EuaIcwfImHQiqC%fZWZLcf{l>Y{qfo+_Za=hY|G}u0Q8(NiDH(Sdf=f3eT`w zvqOCw02&F`AH!$f`$BzHLO@^NL09r@93GYF1Rl(T)K3uKwE#EWTehoa58}${1cz^% z)447DxITKE&bl`cG#W^%qikg9WM!#a6AuGS3Orq`a3C)Wj2rg+3}{)u&4iaVn43HH0F4qg9 zbk%oSbq>$f%Lp;~jKY(#_ylH$HAzz$CM;HLPq4PXBorQ#o6e3%)Qx^9*!U^IacU;6 z%$gmuA!?Wy#R!O0!%QQ8;7^!4ixCv%5|HeVoA*#~jBYS|#14$*n_H6qPDKJ^MxDO6(rk1S?Zm6D)H7{CpuN1I#@hHK|<4LHV3*Pg4UlzR@8RQvqrU0iaJlxo;*)DoUy{w1{ zC`%+e*+boW@w!Twvtels+ZdZ^TA7))%n?h=C@p5)m5XZ7mj^fw$l1^C5ea+Zru%*N zEky|bo9%g|P!?XFc@mr9-2@T{Fc0^nK%ME)g5@A*`>KgE`!2{susUqH`@S9!rDP%y6HETK__cNtqhA--UrUOUV-zMXBg{N4F#sR4oqtj#uN>V(tS+@-(zaj zERhz^eYY^?ZUiNMAoyP|ed19syYoLYoUQLFp4 z$k(q^i(8$K419aPTDUL-J!g=2E%v3Kn&!@$+4rEhOpUU2=#+g_*&fIz&7u!~OPoG9 z-;rBEXU8hF_hMm>hJJ+r?=xRaTYjYyRSZeZJF22eAqMO*_u?}rUs_@byD1mNTRB%R ze`+fo>%A#Vu&t|UejD>(GMYn0KISBojAbAotw3(mG0qXpTe}jpN*y*JSGD~E`@IUL z50f(qzuQbSPsdX2+Y&iSn5uGy(F@qD@cWtdZk#~bSW_PIwC5WisARof*A017roD}}^q(MpjQv#>>QmwR2L_=luqytmZC?`T} zy5j0RU!y7E_2X{AtZo*gQK}dCneUzo^@qs6schqMZMpE~TBf6hmO3d|+*_3A=mM+u z@gH7fP12d3`@{}abD2o%Z&5vw*`+b2!SuCN4Hf;2?~|Vk$GDQry4}C`Y*B(jS*e+) z@@ts7*FZ#RTuR?jKDoL>bZU_Vhr==@R+HkvA@NZx$_Co=I&JSfhb8@rjH0;KU{nFF z9-fS=>&`nO2eC1ywpBXD9LKdPv6bK}(hp(d8$E!BnbH%+ z0n6I;wy-8eNlgYuHi`-bFoey)Gj_fx_T{Am&+~heoT^yQc}fD(42_g2P*j7^g9k$1 zhzk73{K{zY&2Oed&$b67MQ8{LGDw9(faz-zyNfJfCK#4H?m81s7lrWclo=k)jdp&+j#I|ce{N_Y7NBzpOfTDJ=Xs1IW zezW|gE^2<3Vx5M&N`*`rmqoJUVQ_~PLUcB5WYJ2`t9 z1d0yz>Ey*3(U*?IJjy+=NdaA)&tDLe5lCxFcoo%=Wmi*PJ$8>0e}dm-JA0hlXii+v zn|YdfHY&fFySGfis3?{)n=9*XLYmT0_0YMPX6$~<<$crr6s|`AdG}=-qbB89$zi*P zgC3r}gZXt-iqQ$hVhol`CrhZhTvQ}a6Lw;)=Evfgf`cD15)oti{_#2R@lBAovobWu zI5x9VFYD~oD;{6kwWpP=2`WvHI!r*}5AbaWKgVJcVkafcYg+dh)AZI2P>IZfX;5-$ z)-gYf)|2C-u6j>zJP~}&Psqnobtu)uE}32QsdyQ`Av-8J{5UwPAZ*26J*RGd1mh_7 zD?hX;(fhy+O82U@9947l#ILu&W+an8O|qOj?{sd==yt#s7ALR##j^6s$fl%Bz|=OX zIm5HvM49Nu%emtcqxE^uH=|Usr<&wg?q5P}R4%R}Mj7@{eQQ_mKFy3Yy3c8JKCoaP zAROL37gL?NRa3r&`jBy;_qKL*ArD(wg1Q4%r^Wm(=7wk)QRA*%y#VUCFB=8v*>6YM z#TV-tJbLEYZV3l_4zsD7&u`WRt$ZG0+`T78UD%EX#;V}#{Kiz#owRKi?lP@cNmp7f zzCSmuiEz%^>#VhHso^rsrK9WR_R4XJcp=fo@Ii2ns&Jc%w!=y#opIz-RUc-N`HN>Z z;U|mtKvcH(8HkisNE3gIQi{bsrkRz=r>8C2V56><++;}5ZJ9Dg>z z(!~ee#Dm$)9px&3qLj|5q9f+G`hDSkJm7Grl3C6m#ry&*x$65?-pdbQsx$4f`A;JT zxQC5S75msuB0Q%ri}r@RH?JdyZ^CxVlRU}*RT*rT2lQDL%43)VV!G6| zpuuFLV3FOS(1wLP=hnD6^cGNE+m-_+9_Ns%P`1ab%DL#kyC>s_jLS@5Vx!h%Q@QrX zsMtGO*VYdXOfR$|RVlCHl$88nw#nm`5IJQWcHJqF&pu9`joHVaKFI?-!SSRU`rPkI zHhq~HTYm(bnJ)%VOHdM=p2@_FLNA1_1#BBV<$4~&6IN0;Zx#5PHvlHK7(rCW0u*v) z^3UhD8z9`H3aIXg1!L2R8`0tyOvND{-BpHJkA|DQmZ0{^*lG&6x%K`&luMEH#duf; zBDCCW7F(?74X8>j2CKg00MVZFmE>()#}_x5!X>-XG+xyOvO|m-G1{daj#x}>BW<{y zt#WwSMw{Z&@c{B0FV4372Jr|s&Eqk$9YAVaUK{@D4Grf!X+GreN8aa(rHeC)9ZO8^ zk$P=>F`7wXkr*cn%{Qt~p*ihJ?z`_695x=4`)^W@rM@ncLGKyBP^7F4qAQhy|;fc)zD{kf^dyYKYo*?1sPcM2z! zV_UjF#EyogElbf-Qw?uOUmbYRhsc{9n#3(@qpn3waG6KNm1gkfTHNRzZK84B@cEX1 zC2|`@xq(01MKNr_af;nW_K>(fR6wut50A0xYTh*xe!45@H@g32gj0$@3M!{lP+H#; z*o8~Y^>e!|emL{|nk(&7QS$}=VOQ6Y*=g0HUmWDksRZx&B){;tvS*OEReNTj^0g2PuFN6w_E6jDZQ?Tx zik%;axJe87Ej@nEdaHLIw1zme$e*b_jSl}TlCNgfk8QC5r_IiMAr@f>6@Eb+N(YitCziIS5DBg} z7whnQ1ov=eH2Tgy`JLd^*B4E?WQB0sdlX_`&@1VKa(g5&{;H1QjResW|oy8Mn{A7ZQ zB3JeerLjAx{*dscAR8xNT!?*p`}6LS(Adg%x8H>@pJFbpe=k^PVZR1)Rz>YE)-mnw zNbbyI%I0_ISK8BSLk5)ZFIfuJave9Z1c7kKXYh6}2X7Q&9TNMM&nX$JCB?RE`v4gIAmLI0t zU2!WDbMI)8DN#Jk_q(@M(cv9iY5eBH7s)fiP;W;>`j%j1LK{nJdlg*SM{X5W*yKCtc8Ox7MlS9=wrCLcy(?D}RTt zL*#8YM5ai`z-r7vzr@GR_6e*xfpm3sPWC?;UFrcuk{gNlL=&@LYd^r)wK>!fwOFLW zE30p=K7MkJ0WU87PVC}>_mqm$cW!yEIji}~_x0xm61;2I<#cNvZuEHqJraFxV5p%k zX65Y22ZK9XBKW)#UM?^zdxRUaCBg>jB+0Vd*vi6;giEp*Kr}!aE^-K4 zq_U4ILeEE2-^$0{N)*l_1tpO15<>$xBHUoiUXBh1h(9po5hyEHq>CHU*@^iVCd|^=-A$5( z1ubX(SA32x8XEtGcS8Ni0-6t?7t967&j$iJIs*S{fpSyuK!f}l(En4 zXLnaCgn|dc$&K}|5OAx1+q<~CI{Z!tZUsa*ARN)ED707pzqwRW(a`?4#V-nMkd7|D ztOdd%n$zHlJoECp0hpvC<`mY%Huay7ku7BwIuNe5Rl>h0j|8H~={O6Si z;e`GhFfjO7e_fbfg;E~qL0mT#4F%j)0$M6z0nOaya14xF z3o7z5`i7l9jLd9Jd+tiwtj?Fty$vYq)wSz3j7wUSmS;)Fxg#Do{#tumg6#oG;k^+- z3RN{GRke%7ND&IDWTDr&-31dPFQox|Ds%(>{I3Zh;Y&D?HJk~dg{jKilNG`hEPZ1; z^Wv!U_x*~-Z&T({mLl?Cj63tqsI_fjn@^rCMyee}<#+wKzT%YK01RaupNqChV^d2J z2JFj{%6GKoTpkU@q*4~IBoQQYtKzr0W9yUfc-I^9wSK4CSWcI^bhuvGZ7j_v{X%V| z@jh`&?*J%?x?0m3Y=W$twsTXl8IOC!dhZ@yZsZq=7e>a`e>i*NptEmG@qxpsk3Hbn zksLUsx1jOT{PvDdThJ?q`2+o2D-&fvx@%dC^KC`v>wZaJme~ddJgIuyzS=6>=U0ZK z93*x-T$sv=mM?DmG7ZgEKIeDXowoMqvH#B&w-F&AUBFAV2plUWqVM@0EA8()ccTn$X~%`75H9K$;Z<^i*ED&E zvLSYvCi3=a!DV-bN7S=U0&mDMNA9oz+P;)x*VJ>Le=N_>YNRWY$aUN8?2|Od#bV*W z+?sqKi#_#ont9x*7>5(sHqh78x9;tU#C9~R@igZeEW7o{Xe`S3VJ(qbB7j($6B8h9 z`m`{0r*Yrm_-InAgi(iDTA3-18TM>rd%MA^Coao#Axotwsz0k4Pc1047UMfPCv#Vu zeuj_NX@|f6RlrdCYl?bcz_*0XNRr*n-TOvaCWp}US3FP6r1`e8@-Mks1AYvVzY-G= z6a;}lU@_fxc7PYIbl+vY!GRLf_&%W9TtY1H^6S}6Q%0|^xeMj#gEl?e9D}BakhbW7 zr1F+yU)yPMbmsxn zvAm@};J@E{{PTEvFBfQkR#7yb9*Rps`&o_8yZjp=bwJ?egsRPxC-4G8o(NbS1r>4p zW}V~fgZA})I{cTt1#A{PpYYivECv8ICx;S&>!hFY)(D^Mhf@c*u17|4d#^S>;$gvO z?k{-d2i=*n|0W^JT0bE)ei0R1qQXZ_jLecG=$!LCo@K5!I55Eq{q&=~Qt9C4-2O#l zhg)oO=|YY3Td*Z>p0u&m*85;=<$QJDk`px^mEufW{D)n3JLgBy(a|ZunfY;Y1)cEA zvghJvx?H40b;W%HGpAFh&Hh)5Nc5YjPNnjiIJwF^($J~j_Gw6&p_h>7KT&-FJgKiq>GJ}}Y4-mgQWYk@(yI4>Ra zhi@tc@Dln`KEPM!dV!>>G)C;$Z!BGR zQZfQ@tovhaZCP5GX@V6sW4-4iCi&h?!b^O~+AUC?*QwVP%*hi2y_8NrOn|zTtHhT5 zEngtj1tfmMxD4#$m^5w7wklGG4<~BQq&(iT4+Vfs(ws(_SL5cqR#UzMTf5iePrMJe ziI4q;oSG|BtH9#oX__{V@FSY|%v=1nr#_H)URQkk+}wm=&ZT*eWSVUk+2qJ&6dlyA zJMQOfexnK){eVp3w?5L#o;|F#6Jo0MaAIus;!jb1ILb>HgAJDn8=|VX^WbwT{_EF8 z2C(Y)#d-H|X_TE2@=zG|&3q9iaO6wm12Oe2R^)Ll?7&hJ(eKJ>e`}9!He7ZI8 zX5wO3OH;F^x*BtRXUAz#*&x}uw?D1%K0JE($L7U~nv%KSei84`5HBz9w8L?40_%OP zS7ko&hy0G#*4A!r6xJHXR8u+C)zy`y0+f&hHcrm80SzrJ8*n-r&grtg0@EJw1*eRz b`UWd@W}3WRSd!`22APV2ru;iui;({Ty)B-% literal 0 HcmV?d00001 diff --git a/public/integration-logos/easypost.png b/public/integration-logos/easypost.png new file mode 100644 index 0000000000000000000000000000000000000000..577a0c3bc0cd75c2e39806e1d11392ff60f59d75 GIT binary patch literal 13371 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 literal 0 HcmV?d00001 diff --git a/public/integration-logos/infoplus.png b/public/integration-logos/infoplus.png new file mode 100644 index 0000000000000000000000000000000000000000..72380d0ac01a2b50e378299c16f13b02363a7698 GIT binary patch literal 14597 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 literal 0 HcmV?d00001 diff --git a/public/integration-logos/shipstation.png b/public/integration-logos/shipstation.png new file mode 100644 index 0000000000000000000000000000000000000000..7eebd590db921597b9bfd7d744752ccf3d39ffc3 GIT binary patch literal 13613 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 ( - - {section.label} - + + { + section.icon && + ( + section.icon.path && {section.label} + ) + } + + {section.label} + + { section.processes ? ( diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index aacc5ca..665ddd6 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -1113,7 +1113,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element selectFullFilterState === "filter" && (
All - {` ${totalRecords ? totalRecords.toLocaleString() : "All"} `} + {` ${totalRecords ? totalRecords.toLocaleString() : ""} `} records matching this query are selected.
+ } + secondary={ + <> + {QValueUtils.formatDateTime(revision.values.createDate)} +
+ {revision.values.author} + + } + /> + settings + + + + )) + } + ; + } + + function getScriptLogs(revisionId: number) + { + const logs = scriptLogs[revisionId] as any[]; + if (logs === null || logs === undefined) + { + return Loading...; + } + + if (logs.length === 0) + { + return No logs available for this version.; + } + + return ( + + + + + Timestamp + Run Time (ms) + Had Error? + Input + Output + Logs + + + + { + logs.map((logRecord) => + { + let logs = ""; + if (logRecord.values.scriptLogLine) + { + for (let i = 0; i < logRecord.values.scriptLogLine.length; i++) + { + console.log(" += " + i); + logs += (logRecord.values.scriptLogLine[i].values.text + "\n"); + } + } + + return ( + + {QValueUtils.formatDateTime(logRecord.values.startTimestamp)} + {logRecord.values.runTimeMillis?.toLocaleString()} + +
{QValueUtils.formatBoolean(logRecord.values.hadError)}
+
+ {logRecord.values.input} + + {logRecord.values.output} + {logRecord.values.error} + + {logs} +
+ ); + }) + } +
+
+
+ ); + } + + return ( + + + + + + { + notFoundMessage + ? + {notFoundMessage} + : + + { + alertText ? ( + setAlertText(null)} anchorOrigin={{vertical: "top", horizontal: "center"}}> + setAlertText(null)}> + {alertText} + + + ) : ("") + } + + + + + Record Raw Values as JSON + + + + { + associatedScripts.map((object) => + { + let fieldName = object.associatedScript?.fieldName; + let field = tableMetaData.fields.get(fieldName); + + let currentScriptRevisionId = object.script?.values?.currentScriptRevisionId; + + if (!selectedTabs[fieldName]) + { + selectedTabs[fieldName] = 0; + } + + if (!viewingRevisions[fieldName] || viewingRevisions[fieldName] === -1) + { + console.log(`Defaulting revision for ${fieldName} to ${currentScriptRevisionId}`); + viewingRevisions[fieldName] = currentScriptRevisionId; + + if(!scriptLogs[currentScriptRevisionId]) + { + loadRevisionLogs(fieldName, currentScriptRevisionId); + } + } + + const viewingRevisionArray = object.scriptRevisions?.filter((rev: any) => rev?.values?.id === viewingRevisions[fieldName]); + const code = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.contents : ""; + const viewingSequenceNo = viewingRevisionArray?.length > 0 ? viewingRevisionArray[0].values.sequenceNo : ""; + + let editButtonTooltip = ""; + let editButtonText = "Create New Script"; + if (currentScriptRevisionId) + { + if (currentScriptRevisionId === viewingRevisions[fieldName]) + { + editButtonTooltip = "If you make any changes to this script, a new version will be created when you hit Save."; + editButtonText = "Edit"; + } + else + { + editButtonTooltip = "If you want to make this previous Version active, bring up the Edit window, make any changes " + + "to the old Version if they are needed, then click Save. A new Version will be created, and set as Current."; + editButtonText = "Edit and Activate"; + } + } + + + return ( + + + + {field?.label} + changeTab(newValue, fieldName)} + variant="standard" + > + + + + + + + + + + + + Versions + + {getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)} + + + + + { + currentScriptRevisionId && + + { + currentScriptRevisionId === viewingRevisions[fieldName] + ? (<>Current Version ({viewingSequenceNo})) + : (<>Version {viewingSequenceNo}) + } + + } + + + + + { + code ? ( + <> + + + ) : null + } + + + + + + + + Versions + + {getRevisionsList(object.scriptRevisions, fieldName, currentScriptRevisionId)} + + + + Script Logs (Version {viewingSequenceNo}) + + + {getScriptLogs(viewingRevisions[fieldName])} + + + + + + + + + + + Test Input + + + + +
+ +
+
+
+
+
+ + + + Test Output + + + +
+
+ + + + + + Documentation + + + +

A Deposco Order Optimization Batch Name Script is called when an order is being + optimized for shipping within Deposco. It is responsible for determining the order's  + Batch Name - in other words, an indication of what day the order should be shipped, + and whether or not the order is a line haul.

+ +

Input

+

The input to this type of script is an object named input, with the following fields:

+
    +
  • warehouseId The id of the warehouse that the order is shipping from. See the Warehouse table for mappings.
  • +
  • shipToZipCode The zip code that the order is shipping to.
  • +
  • estimatedNoOfCartons The estimated number of cartons that the order will ship in.
  • +
+ +

Output

+

The script is responsible only for outputting a single value - a string which will be set as the order's  + Batch Name in Deposco.

+ +

Example

+ + if(today.weekday == 1) + ( + return "TUE-Line-Haul" + ) + + +
+
+
+
+
+
+
+ ); + }) + } + +
+
+ + { + editingScript && + closeEditingScript(event, reason)}> + + + } + +
+ } +
+
+
+
+
+ ); +} + +export default EntityDeveloperView; diff --git a/src/qqq/pages/entity-view/components/ViewContents/index.tsx b/src/qqq/pages/entity-view/EntityView.tsx similarity index 65% rename from src/qqq/pages/entity-view/components/ViewContents/index.tsx rename to src/qqq/pages/entity-view/EntityView.tsx index abd9e3e..c182d8d 100644 --- a/src/qqq/pages/entity-view/components/ViewContents/index.tsx +++ b/src/qqq/pages/entity-view/EntityView.tsx @@ -41,8 +41,9 @@ import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import Modal from "@mui/material/Modal"; import React, {useContext, useEffect, useReducer, useState} from "react"; -import {useLocation, useNavigate, useSearchParams} from "react-router-dom"; +import {useLocation, useNavigate, useParams, useSearchParams} from "react-router-dom"; import QContext from "QContext"; +import BaseLayout from "qqq/components/BaseLayout"; import DashboardWidgets from "qqq/components/DashboardWidgets"; import {QActionsMenuButton, QDeleteButton, QEditButton} from "qqq/components/QButtons"; import QRecordSidebar from "qqq/components/QRecordSidebar"; @@ -61,18 +62,20 @@ const qController = QClient.getInstance(); // Declaring props types for ViewForm interface Props { - id: string; table?: QTableMetaData; launchProcess?: QProcessMetaData; } -ViewContents.defaultProps = { - table: null, - launchProcess: null -}; +EntityView.defaultProps = + { + table: null, + launchProcess: null + }; -function ViewContents({id, table, launchProcess}: Props): JSX.Element +function EntityView({table, launchProcess}: Props): JSX.Element { + const {id} = useParams(); + const location = useLocation(); const navigate = useNavigate(); @@ -330,6 +333,11 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element {process.label} ))} + + navigate("dev")}> + data_object + Developer Mode + ); @@ -355,111 +363,122 @@ function ViewContents({id, table, launchProcess}: Props): JSX.Element }; return ( - notFoundMessage - ? - {notFoundMessage} - : - - { - (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( - - {tableMetaData?.label} - {" "} - successfully - {" "} - {searchParams.get("createSuccess") ? "created" : "updated"} + + + + + + { + notFoundMessage + ? + {notFoundMessage} + : + + { + (searchParams.get("createSuccess") || searchParams.get("updateSuccess")) ? ( + + {tableMetaData?.label} + {" "} + successfully + {" "} + {searchParams.get("createSuccess") ? "created" : "updated"} - - ) : ("") - } + + ) : ("") + } - - - - - + + + + + + + + + + + + + + {tableMetaData?.iconName} + + + + + + {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} + + + {renderActionsMenu} + + + {t1SectionElement ? ({t1SectionElement}) : null} + + + + {tableMetaData && tableMetaData.widgets && record && ( + + )} + {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ + iconName, label, name, fieldNames, tier, + }: any) => ( + + + + {label} + + {sectionFieldElements.get(name)} + + + )) : null} + + + + + + + + + + + {/* Delete confirmation Dialog */} + + Confirm Deletion + + + Are you sure you want to delete this record? + + + + + + + + + { + activeModalProcess && + closeModalProcess(event, reason)}> +
+ +
+
+ } - - - - - - - - {tableMetaData?.iconName} - - - - - - {tableMetaData && record ? `Viewing ${tableMetaData?.label}: ${record?.recordLabel}` : ""} - - - {renderActionsMenu} - - {t1SectionElement ? ({t1SectionElement}) : null} - - - - {tableMetaData && tableMetaData.widgets && record && ( - - )} - {nonT1TableSections.length > 0 ? nonT1TableSections.map(({ - iconName, label, name, fieldNames, tier, - }: any) => ( - - - - {label} - - {sectionFieldElements.get(name)} - - - )) : null} - - - - - + } -
- - {/* Delete confirmation Dialog */} - - Confirm Deletion - - - Are you sure you want to delete this record? - - - - - - - - - { - activeModalProcess && - closeModalProcess(event, reason)}> -
- -
-
- } -
- + ); } -export default ViewContents; +export default EntityView; diff --git a/src/qqq/pages/entity-view/index.tsx b/src/qqq/pages/entity-view/index.tsx deleted file mode 100644 index 14e88ef..0000000 --- a/src/qqq/pages/entity-view/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * QQQ - Low-code Application Framework for Engineers. - * Copyright (C) 2021-2022. Kingsrook, LLC - * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States - * contact@kingsrook.com - * https://github.com/Kingsrook/ - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; -import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; -import Grid from "@mui/material/Grid"; -import {useParams} from "react-router-dom"; -import BaseLayout from "qqq/components/BaseLayout"; -import MDBox from "qqq/components/Temporary/MDBox"; -import ViewContents from "./components/ViewContents"; - -interface Props -{ - table?: QTableMetaData; - launchProcess?: QProcessMetaData; -} - -function EntityView({table, launchProcess}: Props): JSX.Element -{ - const {id} = useParams(); - - return ( - - - - - - - - - - - - ); -} - -EntityView.defaultProps = { - table: null, - launchProcess: null -}; - -export default EntityView; diff --git a/src/qqq/pages/process-run/components/QValidationReview.tsx b/src/qqq/pages/process-run/components/QValidationReview.tsx index 94ecce0..8932061 100644 --- a/src/qqq/pages/process-run/components/QValidationReview.tsx +++ b/src/qqq/pages/process-run/components/QValidationReview.tsx @@ -35,6 +35,7 @@ import Tooltip from "@mui/material/Tooltip"; import React, {useState} from "react"; import MDBox from "components/MDBox"; import MDTypography from "components/MDTypography"; +import CustomWidthTooltip from "qqq/components/CustomWidthTooltip/CustomWidthTooltip"; import {ProcessSummaryLine} from "qqq/pages/process-run/model/ProcessSummaryLine"; import QClient from "qqq/utils/QClient"; import QValueUtils from "qqq/utils/QValueUtils"; @@ -87,15 +88,6 @@ function QValidationReview({ setPreviewRecordIndex(newIndex); }; - const CustomWidthTooltip = styled(({className, ...props}: TooltipProps) => ( - - ))({ - [`& .${tooltipClasses.tooltip}`]: { - maxWidth: 500, - textAlign: "left", - }, - }); - const buildDoFullValidationRadioListItem = (value: "true" | "false", labelText: string, tooltipHTML: JSX.Element): JSX.Element => { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/qqq/styles/qqq-override-styles.css b/src/qqq/styles/qqq-override-styles.css index f8a3c2d..5b85615 100644 --- a/src/qqq/styles/qqq-override-styles.css +++ b/src/qqq/styles/qqq-override-styles.css @@ -238,3 +238,24 @@ input[type="search"]::-webkit-search-results-decoration { display: none; } color: gray; right: 0.125rem; } + +.devDocumentation ul>li +{ + margin-left: 30px; +} + +.devDocumentation * +{ + line-height: 1.5; +} + +.devDocumentation p +{ + margin-top: .5rem; + margin-bottom: .5rem; +} + +.devDocumentation code +{ + white-space: pre-wrap; +} diff --git a/src/qqq/utils/QValueUtils.tsx b/src/qqq/utils/QValueUtils.tsx index 338faef..065d134 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -26,7 +26,6 @@ import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstan import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; import {Chip, Icon} from "@mui/material"; -import {queryByTestId} from "@testing-library/react"; import React, {Fragment} from "react"; import {Link} from "react-router-dom"; import QClient from "qqq/utils/QClient"; @@ -42,9 +41,9 @@ class QValueUtils private static getQInstance(): QInstance { - if(QValueUtils.qInstance == null) + if (QValueUtils.qInstance == null) { - if(QValueUtils.loadingQInstance) + if (QValueUtils.loadingQInstance) { return (null); } @@ -87,20 +86,20 @@ class QValueUtils let href = rawValue; const toRecordFromTable = adornment.getValue("toRecordFromTable"); - if(toRecordFromTable) + if (toRecordFromTable) { - if(QValueUtils.getQInstance()) + if (QValueUtils.getQInstance()) { let tablePath = QValueUtils.getQInstance().getTablePathByName(toRecordFromTable); - if(!tablePath) + if (!tablePath) { console.log("Couldn't find path for table: " + tablePath); return (""); } - if(!tablePath.endsWith("/")) + if (!tablePath.endsWith("/")) { - tablePath += "/" + tablePath += "/"; } href = tablePath + rawValue; } @@ -113,33 +112,33 @@ class QValueUtils } } - if(!href) + if (!href) { return (""); } - if(href.startsWith("http")) + if (href.startsWith("http")) { - return ( e.stopPropagation()}>{displayValue ?? rawValue}) + return ( e.stopPropagation()}>{displayValue ?? rawValue}); } else { - return ( e.stopPropagation()}>{displayValue ?? rawValue}) + return ( e.stopPropagation()}>{displayValue ?? rawValue}); } } if (field.hasAdornment(AdornmentType.CHIP)) { - if(!displayValue) + if (!displayValue) { - return (); + return (); } const adornment = field.getAdornment(AdornmentType.CHIP); - const color = adornment.getValue("color." + rawValue) ?? "default" + const color = adornment.getValue("color." + rawValue) ?? "default"; const iconName = adornment.getValue("icon." + rawValue) ?? null; const iconElement = iconName ? {iconName} : null; - return (); + return (); } return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); @@ -158,8 +157,7 @@ class QValueUtils return (""); } const date = new Date(rawValue); - // @ts-ignore - return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`); + return this.formatDateTime(date); } else if (field.type === QFieldType.DATE) { @@ -185,6 +183,29 @@ class QValueUtils return (returnValue); } + public static formatDateTime(date: Date) + { + if(!(date instanceof Date)) + { + date = new Date(date) + } + // @ts-ignore + return (`${date.toString("yyyy-MM-dd hh:mm:ss")} ${date.getHours() < 12 ? "AM" : "PM"} ${date.getTimezone()}`); + } + + public static formatBoolean(value: any) + { + if(value === true) + { + return ("Yes"); + } + else if(value === false) + { + return ("No"); + } + return (null); + } + public static getFormattedNumber(n: number): string { try @@ -221,28 +242,28 @@ class QValueUtils /******************************************************************************* ** Take a date-time value, and format it the way the ui's date-times want it - ** to be. + ** to be. *******************************************************************************/ public static formatDateTimeValueForForm(value: string): string { - if(value === null || value === undefined) + if (value === null || value === undefined) { return (value); } - if(value.match(/^\d{4}-\d{2}-\d{2}$/)) + if (value.match(/^\d{4}-\d{2}-\d{2}$/)) { ////////////////////////////////////////////////////////////////// // if we just passed in a date (w/o time), attach T00:00 to it. // ////////////////////////////////////////////////////////////////// - return(value + "T00:00"); + return (value + "T00:00"); } - else if(value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/)) + else if (value.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}.*/)) { /////////////////////////////////////////////////////////////////////////////////// // if we passed in something too long (e.g., w/ seconds and fractions), trim it. // /////////////////////////////////////////////////////////////////////////////////// - return(value.substring(0, 16)); + return (value.substring(0, 16)); } else { From 860c79c405e5aad717ec6b01d5ace5e0f18bd7c9 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 1 Nov 2022 08:32:28 -0500 Subject: [PATCH 4/7] Update qqq-frontend-core to 1.0.30 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f884563..f75f7ef 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.28", + "@kingsrook/qqq-frontend-core": "1.0.30", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", From 61394f4a52002986953fc566104ee40aa66aafe5 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 1 Nov 2022 16:29:27 -0500 Subject: [PATCH 5/7] Add tableSection.isHidden; table.capabilities; adornmentType.CODE_EDITOR --- package.json | 4 +- src/qqq/components/EntityForm/index.tsx | 31 ++++++++++++ src/qqq/components/QButtons/index.tsx | 4 +- src/qqq/components/QRecordSidebar/index.tsx | 5 ++ src/qqq/pages/entity-list/index.tsx | 50 +++++++++++++------ .../pages/entity-view/EntityDeveloperView.tsx | 2 +- src/qqq/pages/entity-view/EntityView.tsx | 50 ++++++++++++------- .../components/QValidationReview.tsx | 2 +- src/qqq/pages/process-run/index.tsx | 9 ++-- src/qqq/utils/QValueUtils.tsx | 36 +++++++++++-- 10 files changed, 149 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index f75f7ef..d20632d 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "@fullcalendar/interaction": "5.10.0", "@fullcalendar/react": "5.10.0", "@fullcalendar/timegrid": "5.10.0", - "@kingsrook/qqq-frontend-core": "1.0.30", + "@kingsrook/qqq-frontend-core": "1.0.31", "@mui/icons-material": "5.4.1", "@mui/material": "5.4.1", "@mui/styled-engine": "5.4.1", @@ -75,7 +75,7 @@ "geff-ham": "rm -rf node_modules/ && rm -rf package-lock.json && npm install --legacy-peer-deps && npm start", "install-legacy-peer-deps": "npm install --legacy-peer-deps", "prepublishOnly": "tsc -p ./ --outDir lib/", - "start": "react-scripts start", + "start": "BROWSER=none react-scripts start", "test": "react-scripts test", "cypress:open": "cypress open" }, diff --git a/src/qqq/components/EntityForm/index.tsx b/src/qqq/components/EntityForm/index.tsx index 8f6493c..06616e8 100644 --- a/src/qqq/components/EntityForm/index.tsx +++ b/src/qqq/components/EntityForm/index.tsx @@ -19,6 +19,7 @@ * along with this program. If not, see . */ +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; @@ -73,6 +74,8 @@ function EntityForm({table, id}: Props): JSX.Element const [tableSections, setTableSections] = useState(null as QTableSection[]); const [, forceUpdate] = useReducer((x) => x + 1, 0); + const [noCapabilityError, setNoCapabilityError] = useState(null as string); + const {pageHeader, setPageHeader} = useContext(QContext); const navigate = useNavigate(); @@ -140,11 +143,21 @@ function EntityForm({table, id}: Props): JSX.Element }); setFormValues(formValues); + + if(!tableMetaData.capabilities.has(Capability.TABLE_UPDATE)) + { + setNoCapabilityError("You may not edit records in this table"); + } } else { setFormTitle(`Creating New ${tableMetaData?.label}`); setPageHeader(`Creating New ${tableMetaData?.label}`); + + if(!tableMetaData.capabilities.has(Capability.TABLE_INSERT)) + { + setNoCapabilityError("You may not create records in this table"); + } } setInitialValues(initialValues); @@ -168,6 +181,11 @@ function EntityForm({table, id}: Props): JSX.Element const section = tableSections[i]; const sectionDynamicFormFields: any[] = []; + if(section.isHidden) + { + continue; + } + for (let j = 0; j < section.fieldNames.length; j++) { const fieldName = section.fieldNames[j]; @@ -277,6 +295,19 @@ function EntityForm({table, id}: Props): JSX.Element const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`; + if(noCapabilityError) + { + return + + + + {noCapabilityError} + + + + ; + } + return ( diff --git a/src/qqq/components/QButtons/index.tsx b/src/qqq/components/QButtons/index.tsx index 489686b..b813ea9 100644 --- a/src/qqq/components/QButtons/index.tsx +++ b/src/qqq/components/QButtons/index.tsx @@ -71,7 +71,7 @@ interface QDeleteButtonProps export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element { return ( - + delete}> Delete @@ -82,7 +82,7 @@ export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element export function QEditButton(): JSX.Element { return ( - + edit}> Edit diff --git a/src/qqq/components/QRecordSidebar/index.tsx b/src/qqq/components/QRecordSidebar/index.tsx index a15a61f..a404978 100644 --- a/src/qqq/components/QRecordSidebar/index.tsx +++ b/src/qqq/components/QRecordSidebar/index.tsx @@ -59,6 +59,11 @@ function QRecordSidebar({tableSections, widgetMetaDataList, light, stickyTop}: P const sidebarEntries = [] as SidebarEntry[]; tableSections && tableSections.forEach((section, index) => { + if(section.isHidden) + { + return; + } + if (index === 1 && widgetMetaDataList) { widgetMetaDataList.forEach((widget) => diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/index.tsx index 665ddd6..bdb9e0d 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/index.tsx @@ -20,6 +20,7 @@ */ import {AdornmentType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/AdornmentType"; +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldType"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; @@ -584,9 +585,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element const row: any = {}; fields.forEach((field) => { - const value = QValueUtils.getDisplayValue(field, record); + const value = QValueUtils.getDisplayValue(field, record, "query"); if (typeof value !== "string") { + console.log(`Need to render [${field.name}]`); columnsToRender[field.name] = true; } row[field.name] = value; @@ -1146,18 +1148,27 @@ function EntityList({table, launchProcess}: Props): JSX.Element onClose={closeActionsMenu} keepMounted > - - library_add - Bulk Load - - - edit - Bulk Edit - - - delete - Bulk Delete - + { + table.capabilities.has(Capability.TABLE_INSERT) && + + library_add + Bulk Load + + } + { + table.capabilities.has(Capability.TABLE_UPDATE) && + + edit + Bulk Edit + + } + { + table.capabilities.has(Capability.TABLE_DELETE) && + + delete + Bulk Delete + + } {tableProcesses.length > 0 && } {tableProcesses.map((process) => ( processClicked(process)}> @@ -1165,6 +1176,13 @@ function EntityList({table, launchProcess}: Props): JSX.Element {process.label} ))} + { + tableProcesses.length == 0 && !table.capabilities.has(Capability.TABLE_INSERT) && !table.capabilities.has(Capability.TABLE_UPDATE) && !table.capabilities.has(Capability.TABLE_DELETE) && + + block + No actions available + + } ); @@ -1226,7 +1244,10 @@ function EntityList({table, launchProcess}: Props): JSX.Element {renderActionsMenu} - + { + table.capabilities.has(Capability.TABLE_INSERT) && + + } @@ -1244,6 +1265,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element disableSelectionOnClick autoHeight rows={rows} + // getRowHeight={() => "auto"} // maybe nice? wraps values in cells... columns={columnsModel} rowBuffer={10} rowCount={totalRecords === null ? 0 : totalRecords} diff --git a/src/qqq/pages/entity-view/EntityDeveloperView.tsx b/src/qqq/pages/entity-view/EntityDeveloperView.tsx index 5e9126e..2da31f1 100644 --- a/src/qqq/pages/entity-view/EntityDeveloperView.tsx +++ b/src/qqq/pages/entity-view/EntityDeveloperView.tsx @@ -423,7 +423,7 @@ function EntityDeveloperView({table}: Props): JSX.Element { - associatedScripts.map((object) => + associatedScripts && associatedScripts.map((object) => { let fieldName = object.associatedScript?.fieldName; let field = tableMetaData.fields.get(fieldName); diff --git a/src/qqq/pages/entity-view/EntityView.tsx b/src/qqq/pages/entity-view/EntityView.tsx index c182d8d..c552cc2 100644 --- a/src/qqq/pages/entity-view/EntityView.tsx +++ b/src/qqq/pages/entity-view/EntityView.tsx @@ -20,6 +20,7 @@ */ import {QException} from "@kingsrook/qqq-frontend-core/lib/exceptions/QException"; +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QProcessMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QProcessMetaData"; import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData"; import {QTableSection} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableSection"; @@ -234,6 +235,11 @@ function EntityView({table, launchProcess}: Props): JSX.Element for (let i = 0; i < tableSections.length; i++) { const section = tableSections[i]; + if(section.isHidden) + { + continue; + } + sectionFieldElements.set( section.name, @@ -244,7 +250,7 @@ function EntityView({table, launchProcess}: Props): JSX.Element {tableMetaData.fields.get(fieldName).label}: - {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record)} + {QValueUtils.getDisplayValue(tableMetaData.fields.get(fieldName), record, "view")} )) @@ -313,27 +319,33 @@ function EntityView({table, launchProcess}: Props): JSX.Element onClose={closeActionsMenu} keepMounted > - navigate("edit")}> - edit - Edit - - { - setActionsMenu(null); - handleClickDeleteButton(); - }} - > - delete - Delete - - {tableProcesses.length > 0 && } + table.capabilities.has(Capability.TABLE_UPDATE) && + navigate("edit")}> + edit + Edit + + } + { + table.capabilities.has(Capability.TABLE_DELETE) && + + { + setActionsMenu(null); + handleClickDeleteButton(); + }} + > + delete + Delete + + } + {tableProcesses.length > 0 && (table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && } {tableProcesses.map((process) => ( processClicked(process)}> {process.iconName ?? "arrow_forward"} {process.label} ))} - + {tableProcesses.length > 0 && } navigate("dev")}> data_object Developer Mode @@ -433,8 +445,12 @@ function EntityView({table, launchProcess}: Props): JSX.Element )) : null} - - + { + table.capabilities.has(Capability.TABLE_DELETE) && + } + { + table.capabilities.has(Capability.TABLE_UPDATE) && + } diff --git a/src/qqq/pages/process-run/components/QValidationReview.tsx b/src/qqq/pages/process-run/components/QValidationReview.tsx index 8932061..d252f2a 100644 --- a/src/qqq/pages/process-run/components/QValidationReview.tsx +++ b/src/qqq/pages/process-run/components/QValidationReview.tsx @@ -253,7 +253,7 @@ function QValidationReview({ {" "}   {" "} - {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex])} + {QValueUtils.getDisplayValue(field, previewRecords[previewRecordIndex], "view")} )) } diff --git a/src/qqq/pages/process-run/index.tsx b/src/qqq/pages/process-run/index.tsx index 601c9ff..4761d55 100644 --- a/src/qqq/pages/process-run/index.tsx +++ b/src/qqq/pages/process-run/index.tsx @@ -362,8 +362,11 @@ function ProcessRun({process, defaultProcessValues, isModal, recordIds, closeMod {localTableSections.map((section: QTableSection, index: number) => { const name = section.name - console.log(formData); - console.log(section.fieldNames); + + if(section.isHidden) + { + return ; + } const sectionFormFields = {}; for(let i = 0; i - {QValueUtils.getValueForDisplay(field, processValues[field.name])} + {QValueUtils.getValueForDisplay(field, processValues[field.name], "view")} ))} diff --git a/src/qqq/utils/QValueUtils.tsx b/src/qqq/utils/QValueUtils.tsx index 065d134..038d5f7 100644 --- a/src/qqq/utils/QValueUtils.tsx +++ b/src/qqq/utils/QValueUtils.tsx @@ -25,8 +25,9 @@ import {QFieldType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QField import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord"; import "datejs"; -import {Chip, Icon} from "@mui/material"; +import {Chip, Icon, Typography} from "@mui/material"; import React, {Fragment} from "react"; +import AceEditor from "react-ace"; import {Link} from "react-router-dom"; import QClient from "qqq/utils/QClient"; @@ -66,19 +67,19 @@ class QValueUtils ** When you have a field, and a record - call this method to get a string or ** element back to display the field's value. *******************************************************************************/ - public static getDisplayValue(field: QFieldMetaData, record: QRecord): string | JSX.Element + public static getDisplayValue(field: QFieldMetaData, record: QRecord, usage: "view" | "query" = "view"): string | JSX.Element { const displayValue = record.displayValues ? record.displayValues.get(field.name) : undefined; const rawValue = record.values ? record.values.get(field.name) : undefined; - return QValueUtils.getValueForDisplay(field, rawValue, displayValue); + return QValueUtils.getValueForDisplay(field, rawValue, displayValue, usage); } /******************************************************************************* ** When you have a field and a value (either just a raw value, or a raw and ** display value), call this method to get a string Element to display. *******************************************************************************/ - public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue): string | JSX.Element + public static getValueForDisplay(field: QFieldMetaData, rawValue: any, displayValue: any = rawValue, usage: "view" | "query" = "view"): string | JSX.Element { if (field.hasAdornment(AdornmentType.LINK)) { @@ -141,6 +142,28 @@ class QValueUtils return (); } + if (field.hasAdornment(AdornmentType.CODE_EDITOR)) + { + if(usage === "view") + { + return (); + } + else + { + return rawValue; + } + } + return (QValueUtils.getUnadornedValueForDisplay(field, rawValue, displayValue)); } @@ -227,6 +250,11 @@ class QValueUtils public static breakTextIntoLines(value: string): JSX.Element { + if(!value) + { + return ; + } + return ( {value.split(/\n/).map((value: string, index: number) => ( From 05af6f23345c8f2905d0d096569ccf1249d5b0a6 Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Thu, 3 Nov 2022 10:34:08 -0500 Subject: [PATCH 6/7] Better support for non-countable tables --- src/qqq/pages/app-home/index.tsx | 29 +++++++++----- .../entity-list/{index.tsx => EntityList.tsx} | 39 ++++++++++++++----- 2 files changed, 50 insertions(+), 18 deletions(-) rename src/qqq/pages/entity-list/{index.tsx => EntityList.tsx} (96%) diff --git a/src/qqq/pages/app-home/index.tsx b/src/qqq/pages/app-home/index.tsx index b6b760b..a6f1d75 100644 --- a/src/qqq/pages/app-home/index.tsx +++ b/src/qqq/pages/app-home/index.tsx @@ -18,6 +18,7 @@ * along with this program. If not, see . */ +import {Capability} from "@kingsrook/qqq-frontend-core/lib/model/metaData/Capability"; import {QAppMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppMetaData"; import {QAppNodeType} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QAppNodeType"; import {QInstance} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QInstance"; @@ -123,23 +124,33 @@ function AppHome({app}: Props): JSX.Element setTimeout(async () => { - const count = await qController.count(table.name); - tableCounts.set(table.name, {isLoading: false, value: count}); - setTableCounts(tableCounts); - - if (count !== null && count !== undefined) + const tableMetaData = await qController.loadTableMetaData(table.name); + let countResult = null; + if(tableMetaData.capabilities.has(Capability.TABLE_COUNT)) { - tableCountNumbers.set(table.name, count.toLocaleString()); - tableCountTexts.set(table.name, count === 1 ? "total record" : "total records"); + countResult = await qController.count(table.name); + + if (countResult !== null && countResult !== undefined) + { + tableCountNumbers.set(table.name, countResult.toLocaleString()); + tableCountTexts.set(table.name, countResult === 1 ? "total record" : "total records"); + } + else + { + tableCountNumbers.set(table.name, "--"); + tableCountTexts.set(table.name, " "); + } } else { - tableCountNumbers.set(table.name, "--"); + tableCountNumbers.set(table.name, "–"); tableCountTexts.set(table.name, " "); } + + tableCounts.set(table.name, {isLoading: false, value: countResult}); + setTableCounts(tableCounts); setTableCountNumbers(tableCountNumbers); setTableCountTexts(tableCountTexts); - setUpdatedTableCounts(new Date()); }, 1); }); diff --git a/src/qqq/pages/entity-list/index.tsx b/src/qqq/pages/entity-list/EntityList.tsx similarity index 96% rename from src/qqq/pages/entity-list/index.tsx rename to src/qqq/pages/entity-list/EntityList.tsx index bdb9e0d..49c8d9a 100644 --- a/src/qqq/pages/entity-list/index.tsx +++ b/src/qqq/pages/entity-list/EntityList.tsx @@ -220,7 +220,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element const [tableProcesses, setTableProcesses] = useState([] as QProcessMetaData[]); const [allTableProcesses, setAllTableProcesses] = useState([] as QProcessMetaData[]); const [pageNumber, setPageNumber] = useState(0); - const [totalRecords, setTotalRecords] = useState(0); + const [totalRecords, setTotalRecords] = useState(null); const [selectedIds, setSelectedIds] = useState([] as string[]); const [selectFullFilterState, setSelectFullFilterState] = useState("n/a" as "n/a" | "checked" | "filter"); const [columnsModel, setColumnsModel] = useState([] as GridColDef[]); @@ -385,12 +385,15 @@ function EntityList({table, launchProcess}: Props): JSX.Element setLatestQueryId(thisQueryId); console.log(`Issuing query: ${thisQueryId}`); - qController.count(tableName, qFilter).then((count) => + if (tableMetaData.capabilities.has(Capability.TABLE_COUNT)) { - countResults[thisQueryId] = count; - setCountResults(countResults); - setReceivedCountTimestamp(new Date()); - }); + qController.count(tableName, qFilter).then((count) => + { + countResults[thisQueryId] = count; + setCountResults(countResults); + setReceivedCountTimestamp(new Date()); + }); + } qController.query(tableName, qFilter, rowsPerPage, pageNumber * rowsPerPage).then((results) => { @@ -588,7 +591,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element const value = QValueUtils.getDisplayValue(field, record, "query"); if (typeof value !== "string") { - console.log(`Need to render [${field.name}]`); columnsToRender[field.name] = true; } row[field.name] = value; @@ -983,7 +985,24 @@ function EntityList({table, launchProcess}: Props): JSX.Element // @ts-ignore const defaultLabelDisplayedRows = ({from, to, count}) => { - if (count !== null && count !== undefined) + console.log(`In defaultLabelDisplayedRows with ${from} ${to} ${count}`); + if(tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT)) + { + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // to avoid a non-countable table showing (this is what data-grid did) 91-100 even if there were only 95 records, // + // we'll do this... not quite good enough, but better than the original // + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if(rows.length > 0 && rows.length < to - from) + { + to = from + rows.length; + } + return (`Showing ${from.toLocaleString()} to ${to.toLocaleString()}`); + } + + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // treat -1 as the sentinel that it's set as below -- remember, we did that so that 'to' would have a value in here when there's no count. // + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + if (count !== null && count !== undefined && count !== -1) { if (count === 0) { @@ -1002,7 +1021,9 @@ function EntityList({table, launchProcess}: Props): JSX.Element return ( Date: Thu, 3 Nov 2022 10:43:34 -0500 Subject: [PATCH 7/7] Feedback from code reviews --- src/qqq/pages/entity-list/EntityList.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/qqq/pages/entity-list/EntityList.tsx b/src/qqq/pages/entity-list/EntityList.tsx index 49c8d9a..0fe2b7c 100644 --- a/src/qqq/pages/entity-list/EntityList.tsx +++ b/src/qqq/pages/entity-list/EntityList.tsx @@ -985,7 +985,6 @@ function EntityList({table, launchProcess}: Props): JSX.Element // @ts-ignore const defaultLabelDisplayedRows = ({from, to, count}) => { - console.log(`In defaultLabelDisplayedRows with ${from} ${to} ${count}`); if(tableMetaData && !tableMetaData.capabilities.has(Capability.TABLE_COUNT)) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -1190,7 +1189,7 @@ function EntityList({table, launchProcess}: Props): JSX.Element Bulk Delete } - {tableProcesses.length > 0 && } + {(table.capabilities.has(Capability.TABLE_INSERT) || table.capabilities.has(Capability.TABLE_UPDATE) || table.capabilities.has(Capability.TABLE_DELETE)) && tableProcesses.length > 0 && } {tableProcesses.map((process) => ( processClicked(process)}> {process.iconName ?? "arrow_forward"}