codicent-app-sdk 0.6.7 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/pages/ListPage.d.ts +49 -0
- package/dist/cjs/pages/ListPage.d.ts.map +1 -1
- package/dist/cjs/pages/ListPage.js +1 -1
- package/dist/esm/pages/ListPage.d.ts +49 -0
- package/dist/esm/pages/ListPage.d.ts.map +1 -1
- package/dist/esm/pages/ListPage.js +1 -1
- package/dist/index.d.ts +49 -0
- package/package.json +1 -1
|
@@ -13,6 +13,55 @@ export interface ListPageProps {
|
|
|
13
13
|
/** Optional data filter applied after fetch. Useful for app-specific filtering (e.g. empty notes). */
|
|
14
14
|
filterData?: (data: DataMessage[], tag: string) => DataMessage[];
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* ListPage — data fetch & cache behaviour
|
|
18
|
+
*
|
|
19
|
+
* ## Fetch key guard
|
|
20
|
+
* A `fetchKey` string (`tag-contentTitle-loadAll-refreshKey-debouncedSearch`) is stored in
|
|
21
|
+
* `lastFetchKey` (ref). The fetch effect bails out early if the key hasn't changed, preventing
|
|
22
|
+
* duplicate calls when unrelated state updates re-render the component.
|
|
23
|
+
* The ref is reset to `""` on effect cleanup (unmount / dep change) so that re-navigation
|
|
24
|
+
* always triggers a fresh fetch.
|
|
25
|
+
*
|
|
26
|
+
* ## Race-condition protection
|
|
27
|
+
* `activeFetchId` (ref) is incremented each time a new fetch starts. Each in-flight promise
|
|
28
|
+
* captures its own `myFetchId` and checks `isActive()` before writing state. A superseded
|
|
29
|
+
* fetch (e.g. from rapid search typing or quick navigation) silently discards its result
|
|
30
|
+
* without calling `setSearching(false)`, so the spinner stays visible until the winning fetch
|
|
31
|
+
* completes.
|
|
32
|
+
*
|
|
33
|
+
* ## Cache strategy
|
|
34
|
+
* | Visit | Cache present? | Behaviour |
|
|
35
|
+
* |-------|---------------|-----------|
|
|
36
|
+
* | First | No | `searching=true` → API call → populate cache → show list |
|
|
37
|
+
* | First | Yes | Instant render from cache (no spinner) → background delta-fetch for records newer than the latest cached timestamp → silently prepend new rows |
|
|
38
|
+
* | Back-navigation | Yes | Unmount cleanup resets fetchKey → same as "First, cache present" |
|
|
39
|
+
* | Back-navigation | Expired | Same as "First, no cache" |
|
|
40
|
+
* | Refresh button | Any | Clears cache for tag + resets fetchKey + increments `refreshKey` → full API call |
|
|
41
|
+
*
|
|
42
|
+
* ## Search / filter flow
|
|
43
|
+
* - Typing triggers a 500 ms debounce before `debouncedSearch` updates.
|
|
44
|
+
* - `debouncedSearch` change → new fetchKey → new `activeFetchId` → previous in-flight
|
|
45
|
+
* fetch is superseded and its result discarded.
|
|
46
|
+
* - `filter_<field>=<value>` URL params are applied client-side after fetch (not sent to API)
|
|
47
|
+
* except for `filter_<tag>=<value>` which seeds `debouncedSearch` for server-side filtering.
|
|
48
|
+
* - `q=` URL param is kept in sync with the search box via a debounced `setSearchParams`
|
|
49
|
+
* call that preserves all other existing params (`canAdd`, `canEdit`, `filter_*`, etc.).
|
|
50
|
+
*
|
|
51
|
+
* ## URL params (all optional)
|
|
52
|
+
* | Param | Type | Default | Purpose |
|
|
53
|
+
* |-------|------|---------|---------|
|
|
54
|
+
* | `tag` | string | `"logbook"` | Data tag to query |
|
|
55
|
+
* | `title` | string | `"Loggbok"` | Page heading |
|
|
56
|
+
* | `contentTitle` | string | — | If set, fetches raw messages and maps this field as the display column |
|
|
57
|
+
* | `loadAll` | `"true"` | — | Removes PAGE_SIZE cap; disables Load-more |
|
|
58
|
+
* | `canAdd` | `"true"` | — | Shows Add Record button (opens RecordModal) |
|
|
59
|
+
* | `canEdit` | `"true"` | — | Shows per-row Edit/Delete buttons |
|
|
60
|
+
* | `canExport` | `"false"` to hide | visible | Shows Export to Excel button |
|
|
61
|
+
* | `addUrl` | string | — | External URL opened on Add click (alternative to modal) |
|
|
62
|
+
* | `q` | string | — | Initial search term; kept in sync with search box |
|
|
63
|
+
* | `filter_<field>` | string | — | Client-side row filter; `%USERNAME%` resolves to current user |
|
|
64
|
+
*/
|
|
16
65
|
export declare const ListPage: ({ state, getColumnDefs, getListConfig, filterData }: ListPageProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
66
|
export default ListPage;
|
|
18
67
|
//# sourceMappingURL=ListPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListPage.d.ts","sourceRoot":"","sources":["../../../src/pages/ListPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAuB3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkE3D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,gBAAgB,CAAC;IACxB,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACxD,sGAAsG;IACtG,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;CAClE;AAED,eAAO,MAAM,QAAQ,wDAAyD,aAAa,
|
|
1
|
+
{"version":3,"file":"ListPage.d.ts","sourceRoot":"","sources":["../../../src/pages/ListPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAuB3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkE3D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,gBAAgB,CAAC;IACxB,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACxD,sGAAsG;IACtG,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,QAAQ,wDAAyD,aAAa,4CA+mB1F,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),t=require("react");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js");var r=require("@fluentui/react-components");require("../components/Spinner.js");var n=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var a=require("@fluentui/react-icons");require("../services/codicent.js");var s=require("../services/dataCache.js"),i=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js"),require("../config/index.js"),require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var o=require("../utils/excelExport.js");require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./CrmPage.js"),require("./CrmPagePersistent.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var c=require("react-router-dom");require("./Purchase.js"),require("../components/Content.js");var l=require("../components/Page.js");require("./QrScan.js");var u=require("../hooks/useLocalization.js");require("../components/FileThumbnail.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var d=require("../hooks/useUserRoles.js");require("../components/MessageInput.js"),require("../components/UploadFile.js"),require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/AiInput.js");var p=require("../components/SearchBox.js");require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js"),require("../components/QrCodeDialog.js"),require("../components/QrScanner.js"),require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js");var m=require("../components/ListView.js"),g=require("../components/RecordModal.js");require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js");var f=require("../node_modules/lodash/lodash.js");const h=r.makeStyles({container:{width:"100%",flex:1,maxWidth:"100%",height:"100%",minHeight:0,backgroundColor:"#ffffff",display:"flex",flexDirection:"column",margin:"0 auto",overflow:"hidden","@media (max-width: 768px)":{maxWidth:"100%",borderRadius:"0"},touchAction:"pan-y",paddingLeft:"0",paddingRight:"0"},listContainer:{overflow:"hidden",flex:1,minHeight:0,marginTop:"10px"},headerRow:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"10px",flexWrap:"wrap"},buttonGroup:{display:"flex",gap:"8px",flexWrap:"wrap"},addButton:{minWidth:"auto",whiteSpace:"nowrap"},recordsCount:{fontSize:"14px",color:"#666",marginTop:"8px"},backButton:{marginBottom:"10px"},searchBox:{marginTop:"8px"},loadMoreContainer:{display:"flex",justifyContent:"center",padding:"16px 0",borderTop:"1px solid #e0e0e0",marginTop:"4px"}}),j=500,q=({state:q,getColumnDefs:x,getListConfig:C,filterData:S})=>{const[y,w]=c.useSearchParams(),A=y.get("tag")||"logbook",v=y.get("title")||"",k=y.get("contentTitle"),b="true"===y.get("loadAll"),D="true"===y.get("canAdd"),M="true"===y.get("canEdit"),R="false"!==y.get("canExport"),B=y.get("addUrl")||"",E=y.get("q")||"",[I,P]=t.useState(E),[_,T]=t.useState(E),{service:$}=q,[N,L]=t.useState([]),[U,F]=t.useState(!1),[O,H]=t.useState(!1),[W,z]=t.useState(!1),V=t.useRef(null),G=t.useRef(E),J=t.useRef(""),Q=t.useRef(0),K=t.useRef($);K.current=$;const X=!!$,[Y,Z]=t.useState(0),[ee,te]=t.useState(null),re=h(),{t:ne}=u.default(),ae=c.useNavigate(),{getCurrentUserName:se}=d.useUserRoles($,q.context.nickname,q.context.selectedApp),ie=t.useMemo((()=>(k?[{key:k,title:k,maxWidth:250}]:C?C(A)?.columns??[]:x?x(A):[]).map((e=>({...e,title:e.title?ne(e.title):void 0})))),[k,A,ne,x,C]),[oe,ce]=t.useState(!1),[le,ue]=t.useState(null),[de,pe]=t.useState(!1),[me,ge]=t.useState(null),fe=t.useMemo((()=>`listpage_searches_${A}`),[A]),he=e=>{try{const t=localStorage.getItem(e);return t?JSON.parse(t):[]}catch{return[]}},[je,qe]=t.useState((()=>he(fe)));t.useEffect((()=>{qe(he(fe))}),[fe]);const xe=t.useCallback((e=>{const t=e.trim();t&&qe((e=>{const r=[t,...e.filter((e=>e!==t))].slice(0,20);try{localStorage.setItem(fe,JSON.stringify(r))}catch{}return r}))}),[fe]),Ce=t.useCallback((e=>{qe((t=>{const r=t.filter((t=>t!==e));try{localStorage.setItem(fe,JSON.stringify(r))}catch{}return r}))}),[fe]),Se=t.useMemo((()=>f.lodashExports.debounce((e=>T(e)),500)),[T]);t.useEffect((()=>()=>{Se.cancel()}),[Se]);const ye=t.useCallback((e=>{P(e),Se(e)}),[Se]),we=t.useCallback((e=>{xe(e)}),[xe]);t.useEffect((()=>{if(!y.get("q")){const e=y.get(`filter_${A}`);if(e){let t=e;if("%USERNAME%"===t){const e=se();if(!e)return;t=e}P(t),T(t),G.current=t}}}),[A,y,se]),t.useEffect((()=>{const e=y.get("q")||"";e!==G.current&&(P(e),T(e),G.current=e,J.current="")}),[y]),t.useEffect((()=>(V.current&&clearTimeout(V.current),V.current=setTimeout((()=>{if(G.current!==I){const e=new URLSearchParams(y);I?e.set("q",I):e.delete("q"),w(e,{replace:!0}),G.current=I}}),500),()=>{V.current&&clearTimeout(V.current)})),[I,y,w]),t.useEffect((()=>{if(!K.current)return;const e=`${A}-${k}-${b}-${Y}-${_}`;if(e===J.current)return;J.current=e;const t=++Q.current,r=()=>t===Q.current,n=k?`${A}-${_}-${k}`:`${A}-${_||"all"}`,a=s.dataCache.get(n);if(a){if(L(a),H(!b&&!k&&a.length>=j),!k&&!_&&a.length>0){const e=a.reduce(((e,t)=>t.createdAt>e?t.createdAt:e),a[0].createdAt);K.current.readDataMessages(A,"",void 0,void 0,void 0,e).then((e=>{r()&&L((t=>{const r=new Set(t.map((e=>e.id))),a=e.filter((e=>!r.has(e.id)));if(0===a.length)return t;const i=[...a,...t];return s.dataCache.set(n,i),i}))})).catch(console.warn)}}else if(H(!1),F(!0),k)K.current.getMessagesFast([A],_).then((e=>{if(!r())return;const t=e.map((e=>{const t=new Date(e.createdAt).toLocaleString();return{id:e.id,data:{[k]:new i.default(e.content).content,createdAt:t},fileId:null,fileIds:[],tags:[A],mentions:[K.current.codicent],createdAt:t}}));s.dataCache.set(n,t),L(t)})).catch(console.warn).finally((()=>{r()&&F(!1)}));else{const e=_||"",t=b||e?void 0:j;K.current.readDataMessages(A,e,void 0,0,t).then((t=>{r()&&(s.dataCache.set(n,t),L(t),H(!b&&!e&&t.length===j))})).catch(console.warn).finally((()=>{r()&&F(!1)}))}}),[A,k,b,_,Y,X]);const Ae=t.useMemo((()=>{const e={};y.forEach(((t,r)=>{r.startsWith("filter_")&&(e[r.replace("filter_","")]=t)}));let t=N.map(((e,t)=>({item:e,index:t})));for(const[r,n]of Object.entries(e)){const e=n.replace("%USERNAME%",se()),a=q.context.nickname||"";t=t.filter((({item:t})=>{const n=t.data[r];return null!=n&&(String(n).includes(e)||String(n).includes(a))}))}if(S){const e=S(t.map((({item:e})=>e)),A),r=new Set(e.map((e=>e.id)));t=t.filter((({item:e})=>r.has(e.id)))}const r=t.map((({item:e,index:t})=>({...e.data,createdAt:e.createdAt,_id:e.id,originalMessageId:e.originalMessageId||e.id,_fileIds:e.fileIds??[],_index:t}))),n=C?.(A)?.deduplicateBy;if(n){const e=Array.isArray(n)?n:[n],t=t=>{for(const r of e){const e=t[r];if(null!=e&&""!==String(e).trim())return String(e)}},a=new Set;return r.filter((e=>{const r=t(e);return void 0===r||!a.has(r)&&(a.add(r),!0)}))}return r}),[N,y,q.context.nickname,se,S,A,C]),ve=t.useMemo((()=>{const e=(I||"").trim().toLowerCase();if(!e)return Ae;const t=ie.filter((e=>!e.hidden)).map((e=>e.key));return Ae.filter((r=>{for(const n of t)if(Array.isArray(n))for(const t of n){const n=r[t];if(null!=n&&String(n).toLowerCase().includes(e))return!0}else{const t=r[n];if(null!=t&&String(t).toLowerCase().includes(e))return!0}return!1}))}),[Ae,I,ie]),ke=t.useCallback((()=>{s.dataCache.clearPattern(A),J.current="",Z((e=>e+1))}),[A]),be=t.useCallback((async()=>{z(!0);try{const e=await K.current.readDataMessages(A,"",void 0,N.length,j),t=[...N,...e];L(t),s.dataCache.set(`${A}-all`,t),H(e.length===j)}catch(e){console.warn(e)}finally{z(!1)}}),[N,A]),De=t.useCallback((e=>{te(e)}),[]),Me=()=>{pe(!1),ge(null)},Re=t.useCallback((async()=>{const e=`${A}_${(new Date).toISOString().split("T")[0]}`.replace(/[/\\:*?"<>|]/g,"_");const t=null!==ee?ee:ve;await o.exportToExcel(t,ie,e,ne)||console.warn("No data to export")}),[ee,ve,ie,A,ne]),Be=t.useMemo((()=>null!==ee?ee.length:ve.length),[ee,ve]);return e.jsx(l.Page,{hideHeader:!0,children:e.jsxs("div",{className:re.container,children:[e.jsx("div",{className:re.backButton,children:e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowLeft24Regular,{}),onClick:()=>ae(-1),children:ne("Tillbaka")})}),e.jsxs("div",{className:re.headerRow,children:[e.jsx(n.default,{title:ne(v||"Loggbok")}),e.jsxs("div",{className:re.buttonGroup,children:[e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowClockwise24Regular,{}),onClick:ke,title:ne("Refresh"),children:ne("Refresh")}),R&&e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowDownload24Regular,{}),onClick:Re,title:ne("Export to Excel"),children:ne("Export to Excel")}),(D||B)&&e.jsx(r.Button,{appearance:"primary",icon:e.jsx(a.Add24Regular,{}),onClick:D?()=>{ue(null),ce(!0)}:()=>window.open(B,"_blank"),className:re.addButton,children:ne("Add Record")})]})]}),e.jsx("div",{className:re.searchBox,children:e.jsx(p.default,{placeholder:ne("Sök")+"...",value:I,onChange:ye,recentSearches:je,onCommitSearch:we,onRemoveRecentSearch:Ce})}),!U&&e.jsxs("div",{className:re.recordsCount,children:[ne("Total"),": ",Be," ",ne("records"),Be!==N.length&&` (${ne("filtered from")} ${N.length})`]}),e.jsxs("div",{className:re.listContainer,children:[U&&e.jsx("p",{children:ne("Söker")+"..."}),!U&&e.jsx(m.default,{data:ve,columns:ie,canEdit:M,onEdit:(e,t)=>{const r=N[t];r&&(ue({data:{...r.data,_fileIds:r.fileIds??[]},index:t}),ce(!0))},onDelete:(e,t)=>{const r=N[t];r&&(ge({index:t,id:r.id}),pe(!0))},onToggleCheckbox:async(e,t,r,n)=>{const a=N[n];if(a){const e=Array.isArray(t)?t[0]:t,i={...a.data,[e]:String(r)},o=await $.updateDataMessage(a.id,i);L((e=>{const t=[...e];return t[n]={...a,id:o,data:i},t})),s.dataCache.clearPattern(A)}},onEnumChange:async(e,t,r,n)=>{const a=N[n];if(a){const e=Array.isArray(t)?t[0]:t,i={...a.data,[e]:r},o=await $.updateDataMessage(a.id,i);L((e=>{const t=[...e];return t[n]={...a,id:o,data:i},t})),s.dataCache.clearPattern(A)}},onFilteredDataChange:De})]}),O&&!I.trim()&&e.jsx("div",{className:re.loadMoreContainer,children:e.jsx(r.Button,{appearance:"primary",size:"medium",onClick:be,disabled:W,children:W?ne("Laddar..."):ne("Visa fler")+` (${N.length} ${ne("laddade")})`})}),e.jsx(g.RecordModal,{open:oe,onClose:()=>ce(!1),onSave:async e=>{const t=new Set(ie.filter((e=>"thumbnail"===e.type)).map((e=>Array.isArray(e.key)?e.key[0]:e.key)));if(le){const r=N[le.index];if(r){const n={};for(const r in e){if(t.has(r))continue;const a=e[r];n[r]=null==a?"":String(a)}const a=await $.updateDataMessage(r.id,n);L((e=>{const t=[...e];return t[le.index]={...r,id:a,data:n},t})),s.dataCache.clearPattern(A)}}else{const r={};for(const n in e){if(t.has(n))continue;const a=e[n];r[n]=null==a?"":String(a)}const n={id:await $.createDataMessage(A,r),data:r,createdAt:(new Date).toISOString(),fileId:null,fileIds:[],tags:[A],mentions:[$.codicent]};L((e=>[n,...e])),s.dataCache.clearPattern(A)}},columns:ie,initialData:le?.data}),e.jsx(r.Dialog,{open:de,onOpenChange:(e,t)=>!t.open&&Me(),children:e.jsx(r.DialogSurface,{children:e.jsxs(r.DialogBody,{children:[e.jsx(r.DialogTitle,{children:ne("Confirm Delete")}),e.jsx(r.DialogContent,{children:ne("Are you sure you want to delete this record? This action cannot be undone.")}),e.jsxs(r.DialogActions,{children:[e.jsx(r.Button,{appearance:"secondary",onClick:Me,children:ne("Cancel")}),e.jsx(r.Button,{appearance:"primary",onClick:async()=>{if(me)try{await $.deleteDataMessage(me.id),L((e=>e.filter(((e,t)=>t!==me.index)))),s.dataCache.clearPattern(A)}catch(e){console.error("Failed to delete record:",e)}finally{pe(!1),ge(null)}},children:ne("Delete")})]})]})})})]})})};exports.ListPage=q,exports.default=q;
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react/jsx-runtime"),t=require("react");require("../components/Markdown.js"),require("../components/Textarea.js"),require("../components/Button.js"),require("../components/CompoundButton.js");var r=require("@fluentui/react-components");require("../components/Spinner.js");var n=require("../components/TextHeader.js");require("../components/TypingIndicator.js"),require("../components/Dialog.js"),require("../components/ChatInput.js"),require("../components/CombinedPlaceholderDialog.js"),require("../components/ChatMessage.js"),require("../components/Header.js");var a=require("@fluentui/react-icons");require("../services/codicent.js");var s=require("../services/dataCache.js"),i=require("../utils/MessageContent.js");require("../node_modules/tinycolor2/esm/tinycolor.js"),require("../_virtual/index.js"),require("../config/index.js"),require("../utils/cacheManager.js"),require("../lib/wavtools/lib/wav_packer.js"),require("../lib/wavtools/lib/analysis/audio_analysis.js"),require("../lib/wavtools/lib/wav_stream_player.js"),require("../lib/wavtools/lib/wav_recorder.js");var o=require("../utils/excelExport.js");require("./AppFrame.js"),require("./Canvas.js"),require("./Chat.js"),require("./Compose.js"),require("./Snap.js"),require("./Search.js"),require("./Menu.js"),require("./Log.js"),require("./Login.js"),require("./Home.js"),require("./CrmPage.js"),require("./CrmPagePersistent.js"),require("./ImageView.js"),require("./FormInvite.js"),require("./FormAccept.js"),require("./Sales.js");var c=require("react-router-dom");require("./Purchase.js"),require("../components/Content.js");var l=require("../components/Page.js");require("./QrScan.js");var u=require("../hooks/useLocalization.js");require("../components/FileThumbnail.js"),require("react-dom/client"),require("../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js"),require("../hooks/useAppStyles.js");var d=require("../hooks/useUserRoles.js");require("../components/MessageInput.js"),require("../components/UploadFile.js"),require("../components/SnapFooter.js"),require("../components/Profile.js"),require("../components/MessageItem.js"),require("../components/AiInput.js");var p=require("../components/SearchBox.js");require("../components/DataMessagePicker.js"),require("../components/HtmlView.js"),require("../components/Footer.js"),require("../components/QrCodeDialog.js"),require("../components/QrScanner.js"),require("../components/OfflineMessage.js"),require("../components/LanguageSelector.js");var m=require("../components/ListView.js"),g=require("../components/RecordModal.js");require("../components/BulkUploadDialog.js"),require("../components/CookieBanner.js"),require("../components/audit/AuditCircularProgress.js"),require("../components/audit/AuditHorizontalProgress.js"),require("../components/audit/AuditRoleIndicator.js"),require("../components/audit/AuditUnitSwitcher.js"),require("../components/audit/AuditAnswerCell.js"),require("../components/audit/AuditSearchBar.js"),require("../components/audit/AuditFilterChips.js"),require("../components/audit/AuditFilterBar.js"),require("../components/audit/AuditGroupsProgress.js"),require("../components/audit/AuditSummaryDashboard.js"),require("../components/audit/AuditRequirementDialog.js"),require("../components/audit/AuditUnitExportDialog.js"),require("../components/audit/AuditUnitImportDialog.js"),require("../components/audit/AuditBulkExportDialog.js"),require("../components/audit/AuditBulkUploadDialog.js"),require("../components/audit/AuditSortPresets.js");var f=require("../node_modules/lodash/lodash.js");const h=r.makeStyles({container:{width:"100%",flex:1,maxWidth:"100%",height:"100%",minHeight:0,backgroundColor:"#ffffff",display:"flex",flexDirection:"column",margin:"0 auto",overflow:"hidden","@media (max-width: 768px)":{maxWidth:"100%",borderRadius:"0"},touchAction:"pan-y",paddingLeft:"0",paddingRight:"0"},listContainer:{overflow:"hidden",flex:1,minHeight:0,marginTop:"10px"},headerRow:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"10px",flexWrap:"wrap"},buttonGroup:{display:"flex",gap:"8px",flexWrap:"wrap"},addButton:{minWidth:"auto",whiteSpace:"nowrap"},recordsCount:{fontSize:"14px",color:"#666",marginTop:"8px"},backButton:{marginBottom:"10px"},searchBox:{marginTop:"8px"},loadMoreContainer:{display:"flex",justifyContent:"center",padding:"16px 0",borderTop:"1px solid #e0e0e0",marginTop:"4px"}}),j=500,q=({state:q,getColumnDefs:x,getListConfig:C,filterData:S})=>{const[y,w]=c.useSearchParams(),A=y.get("tag")||"logbook",v=y.get("title")||"",k=y.get("contentTitle"),b="true"===y.get("loadAll"),D="true"===y.get("canAdd"),M="true"===y.get("canEdit"),R="false"!==y.get("canExport"),B=y.get("addUrl")||"",E=y.get("q")||"",[I,P]=t.useState(E),[_,T]=t.useState(E),{service:$}=q,[N,L]=t.useState([]),[U,F]=t.useState(!1),[O,H]=t.useState(!1),[W,z]=t.useState(!1),V=t.useRef(null),G=t.useRef(E),J=t.useRef(""),Q=t.useRef(0),K=t.useRef($);K.current=$;const X=!!$,[Y,Z]=t.useState(0),[ee,te]=t.useState(null),re=h(),{t:ne}=u.default(),ae=c.useNavigate(),{getCurrentUserName:se}=d.useUserRoles($,q.context.nickname,q.context.selectedApp),ie=t.useMemo((()=>(k?[{key:k,title:k,maxWidth:250}]:C?C(A)?.columns??[]:x?x(A):[]).map((e=>({...e,title:e.title?ne(e.title):void 0})))),[k,A,ne,x,C]),[oe,ce]=t.useState(!1),[le,ue]=t.useState(null),[de,pe]=t.useState(!1),[me,ge]=t.useState(null),fe=t.useMemo((()=>`listpage_searches_${A}`),[A]),he=e=>{try{const t=localStorage.getItem(e);return t?JSON.parse(t):[]}catch{return[]}},[je,qe]=t.useState((()=>he(fe)));t.useEffect((()=>{qe(he(fe))}),[fe]);const xe=t.useCallback((e=>{const t=e.trim();t&&qe((e=>{const r=[t,...e.filter((e=>e!==t))].slice(0,20);try{localStorage.setItem(fe,JSON.stringify(r))}catch{}return r}))}),[fe]),Ce=t.useCallback((e=>{qe((t=>{const r=t.filter((t=>t!==e));try{localStorage.setItem(fe,JSON.stringify(r))}catch{}return r}))}),[fe]),Se=t.useMemo((()=>f.lodashExports.debounce((e=>T(e)),500)),[T]);t.useEffect((()=>()=>{Se.cancel()}),[Se]);const ye=t.useCallback((e=>{P(e),Se(e)}),[Se]),we=t.useCallback((e=>{xe(e)}),[xe]);t.useEffect((()=>{if(!y.get("q")){const e=y.get(`filter_${A}`);if(e){let t=e;if("%USERNAME%"===t){const e=se();if(!e)return;t=e}P(t),T(t),G.current=t}}}),[A,y,se]),t.useEffect((()=>{const e=y.get("q")||"";e!==G.current&&e!==_&&(P(e),T(e),G.current=e,J.current="")}),[y]),t.useEffect((()=>(V.current&&clearTimeout(V.current),V.current=setTimeout((()=>{if(G.current!==I){const e=new URLSearchParams(y);I?e.set("q",I):e.delete("q"),w(e,{replace:!0}),G.current=I}}),500),()=>{V.current&&clearTimeout(V.current)})),[I,y,w]),t.useEffect((()=>{if(!K.current)return;const e=`${A}-${k}-${b}-${Y}-${_}`;if(e===J.current)return;J.current=e;const t=++Q.current,r=()=>t===Q.current,n=k?`${A}-${_}-${k}`:`${A}-${_||"all"}`,a=s.dataCache.get(n);if(!a){if(H(!1),F(!0),k)K.current.getMessagesFast([A],_).then((e=>{if(!r())return;const t=e.map((e=>{const t=new Date(e.createdAt).toLocaleString();return{id:e.id,data:{[k]:new i.default(e.content).content,createdAt:t},fileId:null,fileIds:[],tags:[A],mentions:[K.current.codicent],createdAt:t}}));s.dataCache.set(n,t),L(t)})).catch(console.warn).finally((()=>{r()&&F(!1)}));else{const e=_||"",t=b||e?void 0:j;K.current.readDataMessages(A,e,void 0,0,t).then((t=>{r()&&(s.dataCache.set(n,t),L(t),H(!b&&!e&&t.length===j))})).catch(console.warn).finally((()=>{r()&&F(!1)}))}return()=>{J.current=""}}if(L(a),H(!b&&!k&&a.length>=j),!k&&!_&&a.length>0){const e=a.reduce(((e,t)=>t.createdAt>e?t.createdAt:e),a[0].createdAt);K.current.readDataMessages(A,"",void 0,void 0,void 0,e).then((e=>{r()&&L((t=>{const r=new Set(t.map((e=>e.id))),a=e.filter((e=>!r.has(e.id)));if(0===a.length)return t;const i=[...a,...t];return s.dataCache.set(n,i),i}))})).catch(console.warn)}}),[A,k,b,_,Y,X]);const Ae=t.useMemo((()=>{const e={};y.forEach(((t,r)=>{r.startsWith("filter_")&&(e[r.replace("filter_","")]=t)}));let t=N.map(((e,t)=>({item:e,index:t})));for(const[r,n]of Object.entries(e)){const e=n.replace("%USERNAME%",se()),a=q.context.nickname||"";t=t.filter((({item:t})=>{const n=t.data[r];return null!=n&&(String(n).includes(e)||String(n).includes(a))}))}if(S){const e=S(t.map((({item:e})=>e)),A),r=new Set(e.map((e=>e.id)));t=t.filter((({item:e})=>r.has(e.id)))}const r=t.map((({item:e,index:t})=>({...e.data,createdAt:e.createdAt,_id:e.id,originalMessageId:e.originalMessageId||e.id,_fileIds:e.fileIds??[],_index:t}))),n=C?.(A)?.deduplicateBy;if(n){const e=Array.isArray(n)?n:[n],t=t=>{for(const r of e){const e=t[r];if(null!=e&&""!==String(e).trim())return String(e)}},a=new Set;return r.filter((e=>{const r=t(e);return void 0===r||!a.has(r)&&(a.add(r),!0)}))}return r}),[N,y,q.context.nickname,se,S,A,C]),ve=t.useMemo((()=>{const e=(I||"").trim().toLowerCase();if(!e)return Ae;const t=ie.filter((e=>!e.hidden)).map((e=>e.key));return Ae.filter((r=>{for(const n of t)if(Array.isArray(n))for(const t of n){const n=r[t];if(null!=n&&String(n).toLowerCase().includes(e))return!0}else{const t=r[n];if(null!=t&&String(t).toLowerCase().includes(e))return!0}return!1}))}),[Ae,I,ie]),ke=t.useCallback((()=>{s.dataCache.clearPattern(A),J.current="",Z((e=>e+1))}),[A]),be=t.useCallback((async()=>{z(!0);try{const e=await K.current.readDataMessages(A,"",void 0,N.length,j),t=[...N,...e];L(t),s.dataCache.set(`${A}-all`,t),H(e.length===j)}catch(e){console.warn(e)}finally{z(!1)}}),[N,A]),De=t.useCallback((e=>{te(e)}),[]),Me=()=>{pe(!1),ge(null)},Re=t.useCallback((async()=>{const e=`${A}_${(new Date).toISOString().split("T")[0]}`.replace(/[/\\:*?"<>|]/g,"_");const t=null!==ee?ee:ve;await o.exportToExcel(t,ie,e,ne)||console.warn("No data to export")}),[ee,ve,ie,A,ne]),Be=t.useMemo((()=>null!==ee?ee.length:ve.length),[ee,ve]);return e.jsx(l.Page,{hideHeader:!0,children:e.jsxs("div",{className:re.container,children:[e.jsx("div",{className:re.backButton,children:e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowLeft24Regular,{}),onClick:()=>ae(-1),children:ne("Tillbaka")})}),e.jsxs("div",{className:re.headerRow,children:[e.jsx(n.default,{title:ne(v||"Loggbok")}),e.jsxs("div",{className:re.buttonGroup,children:[e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowClockwise24Regular,{}),onClick:ke,title:ne("Refresh"),children:ne("Refresh")}),R&&e.jsx(r.Button,{appearance:"subtle",icon:e.jsx(a.ArrowDownload24Regular,{}),onClick:Re,title:ne("Export to Excel"),children:ne("Export to Excel")}),(D||B)&&e.jsx(r.Button,{appearance:"primary",icon:e.jsx(a.Add24Regular,{}),onClick:D?()=>{ue(null),ce(!0)}:()=>window.open(B,"_blank"),className:re.addButton,children:ne("Add Record")})]})]}),e.jsx("div",{className:re.searchBox,children:e.jsx(p.default,{placeholder:ne("Sök")+"...",value:I,onChange:ye,recentSearches:je,onCommitSearch:we,onRemoveRecentSearch:Ce})}),!U&&e.jsxs("div",{className:re.recordsCount,children:[ne("Total"),": ",Be," ",ne("records"),Be!==N.length&&` (${ne("filtered from")} ${N.length})`]}),e.jsxs("div",{className:re.listContainer,children:[U&&e.jsx("p",{children:ne("Söker")+"..."}),!U&&e.jsx(m.default,{data:ve,columns:ie,canEdit:M,onEdit:(e,t)=>{const r=N[t];r&&(ue({data:{...r.data,_fileIds:r.fileIds??[]},index:t}),ce(!0))},onDelete:(e,t)=>{const r=N[t];r&&(ge({index:t,id:r.id}),pe(!0))},onToggleCheckbox:async(e,t,r,n)=>{const a=N[n];if(a){const e=Array.isArray(t)?t[0]:t,i={...a.data,[e]:String(r)},o=await $.updateDataMessage(a.id,i);L((e=>{const t=[...e];return t[n]={...a,id:o,data:i},t})),s.dataCache.clearPattern(A)}},onEnumChange:async(e,t,r,n)=>{const a=N[n];if(a){const e=Array.isArray(t)?t[0]:t,i={...a.data,[e]:r},o=await $.updateDataMessage(a.id,i);L((e=>{const t=[...e];return t[n]={...a,id:o,data:i},t})),s.dataCache.clearPattern(A)}},onFilteredDataChange:De})]}),O&&!I.trim()&&e.jsx("div",{className:re.loadMoreContainer,children:e.jsx(r.Button,{appearance:"primary",size:"medium",onClick:be,disabled:W,children:W?ne("Laddar..."):ne("Visa fler")+` (${N.length} ${ne("laddade")})`})}),e.jsx(g.RecordModal,{open:oe,onClose:()=>ce(!1),onSave:async e=>{const t=new Set(ie.filter((e=>"thumbnail"===e.type)).map((e=>Array.isArray(e.key)?e.key[0]:e.key)));if(le){const r=N[le.index];if(r){const n={};for(const r in e){if(t.has(r))continue;const a=e[r];n[r]=null==a?"":String(a)}const a=await $.updateDataMessage(r.id,n);L((e=>{const t=[...e];return t[le.index]={...r,id:a,data:n},t})),s.dataCache.clearPattern(A)}}else{const r={};for(const n in e){if(t.has(n))continue;const a=e[n];r[n]=null==a?"":String(a)}const n={id:await $.createDataMessage(A,r),data:r,createdAt:(new Date).toISOString(),fileId:null,fileIds:[],tags:[A],mentions:[$.codicent]};L((e=>[n,...e])),s.dataCache.clearPattern(A)}},columns:ie,initialData:le?.data}),e.jsx(r.Dialog,{open:de,onOpenChange:(e,t)=>!t.open&&Me(),children:e.jsx(r.DialogSurface,{children:e.jsxs(r.DialogBody,{children:[e.jsx(r.DialogTitle,{children:ne("Confirm Delete")}),e.jsx(r.DialogContent,{children:ne("Are you sure you want to delete this record? This action cannot be undone.")}),e.jsxs(r.DialogActions,{children:[e.jsx(r.Button,{appearance:"secondary",onClick:Me,children:ne("Cancel")}),e.jsx(r.Button,{appearance:"primary",onClick:async()=>{if(me)try{await $.deleteDataMessage(me.id),L((e=>e.filter(((e,t)=>t!==me.index)))),s.dataCache.clearPattern(A)}catch(e){console.error("Failed to delete record:",e)}finally{pe(!1),ge(null)}},children:ne("Delete")})]})]})})})]})})};exports.ListPage=q,exports.default=q;
|
|
@@ -13,6 +13,55 @@ export interface ListPageProps {
|
|
|
13
13
|
/** Optional data filter applied after fetch. Useful for app-specific filtering (e.g. empty notes). */
|
|
14
14
|
filterData?: (data: DataMessage[], tag: string) => DataMessage[];
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* ListPage — data fetch & cache behaviour
|
|
18
|
+
*
|
|
19
|
+
* ## Fetch key guard
|
|
20
|
+
* A `fetchKey` string (`tag-contentTitle-loadAll-refreshKey-debouncedSearch`) is stored in
|
|
21
|
+
* `lastFetchKey` (ref). The fetch effect bails out early if the key hasn't changed, preventing
|
|
22
|
+
* duplicate calls when unrelated state updates re-render the component.
|
|
23
|
+
* The ref is reset to `""` on effect cleanup (unmount / dep change) so that re-navigation
|
|
24
|
+
* always triggers a fresh fetch.
|
|
25
|
+
*
|
|
26
|
+
* ## Race-condition protection
|
|
27
|
+
* `activeFetchId` (ref) is incremented each time a new fetch starts. Each in-flight promise
|
|
28
|
+
* captures its own `myFetchId` and checks `isActive()` before writing state. A superseded
|
|
29
|
+
* fetch (e.g. from rapid search typing or quick navigation) silently discards its result
|
|
30
|
+
* without calling `setSearching(false)`, so the spinner stays visible until the winning fetch
|
|
31
|
+
* completes.
|
|
32
|
+
*
|
|
33
|
+
* ## Cache strategy
|
|
34
|
+
* | Visit | Cache present? | Behaviour |
|
|
35
|
+
* |-------|---------------|-----------|
|
|
36
|
+
* | First | No | `searching=true` → API call → populate cache → show list |
|
|
37
|
+
* | First | Yes | Instant render from cache (no spinner) → background delta-fetch for records newer than the latest cached timestamp → silently prepend new rows |
|
|
38
|
+
* | Back-navigation | Yes | Unmount cleanup resets fetchKey → same as "First, cache present" |
|
|
39
|
+
* | Back-navigation | Expired | Same as "First, no cache" |
|
|
40
|
+
* | Refresh button | Any | Clears cache for tag + resets fetchKey + increments `refreshKey` → full API call |
|
|
41
|
+
*
|
|
42
|
+
* ## Search / filter flow
|
|
43
|
+
* - Typing triggers a 500 ms debounce before `debouncedSearch` updates.
|
|
44
|
+
* - `debouncedSearch` change → new fetchKey → new `activeFetchId` → previous in-flight
|
|
45
|
+
* fetch is superseded and its result discarded.
|
|
46
|
+
* - `filter_<field>=<value>` URL params are applied client-side after fetch (not sent to API)
|
|
47
|
+
* except for `filter_<tag>=<value>` which seeds `debouncedSearch` for server-side filtering.
|
|
48
|
+
* - `q=` URL param is kept in sync with the search box via a debounced `setSearchParams`
|
|
49
|
+
* call that preserves all other existing params (`canAdd`, `canEdit`, `filter_*`, etc.).
|
|
50
|
+
*
|
|
51
|
+
* ## URL params (all optional)
|
|
52
|
+
* | Param | Type | Default | Purpose |
|
|
53
|
+
* |-------|------|---------|---------|
|
|
54
|
+
* | `tag` | string | `"logbook"` | Data tag to query |
|
|
55
|
+
* | `title` | string | `"Loggbok"` | Page heading |
|
|
56
|
+
* | `contentTitle` | string | — | If set, fetches raw messages and maps this field as the display column |
|
|
57
|
+
* | `loadAll` | `"true"` | — | Removes PAGE_SIZE cap; disables Load-more |
|
|
58
|
+
* | `canAdd` | `"true"` | — | Shows Add Record button (opens RecordModal) |
|
|
59
|
+
* | `canEdit` | `"true"` | — | Shows per-row Edit/Delete buttons |
|
|
60
|
+
* | `canExport` | `"false"` to hide | visible | Shows Export to Excel button |
|
|
61
|
+
* | `addUrl` | string | — | External URL opened on Add click (alternative to modal) |
|
|
62
|
+
* | `q` | string | — | Initial search term; kept in sync with search box |
|
|
63
|
+
* | `filter_<field>` | string | — | Client-side row filter; `%USERNAME%` resolves to current user |
|
|
64
|
+
*/
|
|
16
65
|
export declare const ListPage: ({ state, getColumnDefs, getListConfig, filterData }: ListPageProps) => import("react/jsx-runtime").JSX.Element;
|
|
17
66
|
export default ListPage;
|
|
18
67
|
//# sourceMappingURL=ListPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListPage.d.ts","sourceRoot":"","sources":["../../../src/pages/ListPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAuB3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkE3D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,gBAAgB,CAAC;IACxB,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACxD,sGAAsG;IACtG,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;CAClE;AAED,eAAO,MAAM,QAAQ,wDAAyD,aAAa,
|
|
1
|
+
{"version":3,"file":"ListPage.d.ts","sourceRoot":"","sources":["../../../src/pages/ListPage.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAuB3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAkE3D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,gBAAgB,CAAC;IACxB,+FAA+F;IAC/F,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,gBAAgB,EAAE,CAAC;IACpD;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,UAAU,GAAG,SAAS,CAAC;IACxD,sGAAsG;IACtG,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,eAAO,MAAM,QAAQ,wDAAyD,aAAa,4CA+mB1F,CAAC;AAEF,eAAe,QAAQ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsx as t,jsxs as e}from"react/jsx-runtime";import{useState as o,useRef as n,useMemo as r,useEffect as i,useCallback as a}from"react";import"../components/Markdown.js";import"../components/Textarea.js";import"../components/Button.js";import"../components/CompoundButton.js";import{makeStyles as s,Button as c,Dialog as l,DialogSurface as d,DialogBody as m,DialogTitle as p,DialogContent as u,DialogActions as g}from"@fluentui/react-components";import"../components/Spinner.js";import f from"../components/TextHeader.js";import"../components/TypingIndicator.js";import"../components/Dialog.js";import"../components/ChatInput.js";import"../components/CombinedPlaceholderDialog.js";import"../components/ChatMessage.js";import"../components/Header.js";import{ArrowLeft24Regular as h,ArrowClockwise24Regular as j,ArrowDownload24Regular as x,Add24Regular as y}from"@fluentui/react-icons";import"../services/codicent.js";import{dataCache as w}from"../services/dataCache.js";import S from"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"../_virtual/index.js";import"../config/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import{exportToExcel as A}from"../utils/excelExport.js";import"./AppFrame.js";import"./Canvas.js";import"./Chat.js";import"./Compose.js";import"./Snap.js";import"./Search.js";import"./Menu.js";import"./Log.js";import"./Login.js";import"./Home.js";import"./CrmPage.js";import"./CrmPagePersistent.js";import"./ImageView.js";import"./FormInvite.js";import"./FormAccept.js";import"./Sales.js";import{useSearchParams as C,useNavigate as k}from"react-router-dom";import"./Purchase.js";import"../components/Content.js";import{Page as v}from"../components/Page.js";import"./QrScan.js";import b from"../hooks/useLocalization.js";import"../components/FileThumbnail.js";import"react-dom/client";import"../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js";import"../hooks/useAppStyles.js";import{useUserRoles as D}from"../hooks/useUserRoles.js";import"../components/MessageInput.js";import"../components/UploadFile.js";import"../components/SnapFooter.js";import"../components/Profile.js";import"../components/MessageItem.js";import"../components/AiInput.js";import I from"../components/SearchBox.js";import"../components/DataMessagePicker.js";import"../components/HtmlView.js";import"../components/Footer.js";import"../components/QrCodeDialog.js";import"../components/QrScanner.js";import"../components/OfflineMessage.js";import"../components/LanguageSelector.js";import M from"../components/ListView.js";import{RecordModal as _}from"../components/RecordModal.js";import"../components/BulkUploadDialog.js";import"../components/CookieBanner.js";import"../components/audit/AuditCircularProgress.js";import"../components/audit/AuditHorizontalProgress.js";import"../components/audit/AuditRoleIndicator.js";import"../components/audit/AuditUnitSwitcher.js";import"../components/audit/AuditAnswerCell.js";import"../components/audit/AuditSearchBar.js";import"../components/audit/AuditFilterChips.js";import"../components/audit/AuditFilterBar.js";import"../components/audit/AuditGroupsProgress.js";import"../components/audit/AuditSummaryDashboard.js";import"../components/audit/AuditRequirementDialog.js";import"../components/audit/AuditUnitExportDialog.js";import"../components/audit/AuditUnitImportDialog.js";import"../components/audit/AuditBulkExportDialog.js";import"../components/audit/AuditBulkUploadDialog.js";import"../components/audit/AuditSortPresets.js";import{l as P}from"../node_modules/lodash/lodash.js";const $=s({container:{width:"100%",flex:1,maxWidth:"100%",height:"100%",minHeight:0,backgroundColor:"#ffffff",display:"flex",flexDirection:"column",margin:"0 auto",overflow:"hidden","@media (max-width: 768px)":{maxWidth:"100%",borderRadius:"0"},touchAction:"pan-y",paddingLeft:"0",paddingRight:"0"},listContainer:{overflow:"hidden",flex:1,minHeight:0,marginTop:"10px"},headerRow:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"10px",flexWrap:"wrap"},buttonGroup:{display:"flex",gap:"8px",flexWrap:"wrap"},addButton:{minWidth:"auto",whiteSpace:"nowrap"},recordsCount:{fontSize:"14px",color:"#666",marginTop:"8px"},backButton:{marginBottom:"10px"},searchBox:{marginTop:"8px"},loadMoreContainer:{display:"flex",justifyContent:"center",padding:"16px 0",borderTop:"1px solid #e0e0e0",marginTop:"4px"}}),T=500,B=({state:s,getColumnDefs:B,getListConfig:E,filterData:N})=>{const[R,L]=C(),F=R.get("tag")||"logbook",U=R.get("title")||"",H=R.get("contentTitle"),O="true"===R.get("loadAll"),W="true"===R.get("canAdd"),q="true"===R.get("canEdit"),z="false"!==R.get("canExport"),V=R.get("addUrl")||"",G=R.get("q")||"",[J,Q]=o(G),[K,X]=o(G),{service:Y}=s,[Z,tt]=o([]),[et,ot]=o(!1),[nt,rt]=o(!1),[it,at]=o(!1),st=n(null),ct=n(G),lt=n(""),dt=n(0),mt=n(Y);mt.current=Y;const pt=!!Y,[ut,gt]=o(0),[ft,ht]=o(null),jt=$(),{t:xt}=b(),yt=k(),{getCurrentUserName:wt}=D(Y,s.context.nickname,s.context.selectedApp),St=r((()=>(H?[{key:H,title:H,maxWidth:250}]:E?E(F)?.columns??[]:B?B(F):[]).map((t=>({...t,title:t.title?xt(t.title):void 0})))),[H,F,xt,B,E]),[At,Ct]=o(!1),[kt,vt]=o(null),[bt,Dt]=o(!1),[It,Mt]=o(null),_t=r((()=>`listpage_searches_${F}`),[F]),Pt=t=>{try{const e=localStorage.getItem(t);return e?JSON.parse(e):[]}catch{return[]}},[$t,Tt]=o((()=>Pt(_t)));i((()=>{Tt(Pt(_t))}),[_t]);const Bt=a((t=>{const e=t.trim();e&&Tt((t=>{const o=[e,...t.filter((t=>t!==e))].slice(0,20);try{localStorage.setItem(_t,JSON.stringify(o))}catch{}return o}))}),[_t]),Et=a((t=>{Tt((e=>{const o=e.filter((e=>e!==t));try{localStorage.setItem(_t,JSON.stringify(o))}catch{}return o}))}),[_t]),Nt=r((()=>P.debounce((t=>X(t)),500)),[X]);i((()=>()=>{Nt.cancel()}),[Nt]);const Rt=a((t=>{Q(t),Nt(t)}),[Nt]),Lt=a((t=>{Bt(t)}),[Bt]);i((()=>{if(!R.get("q")){const t=R.get(`filter_${F}`);if(t){let e=t;if("%USERNAME%"===e){const t=wt();if(!t)return;e=t}Q(e),X(e),ct.current=e}}}),[F,R,wt]),i((()=>{const t=R.get("q")||"";t!==ct.current&&(Q(t),X(t),ct.current=t,lt.current="")}),[R]),i((()=>(st.current&&clearTimeout(st.current),st.current=setTimeout((()=>{if(ct.current!==J){const t=new URLSearchParams(R);J?t.set("q",J):t.delete("q"),L(t,{replace:!0}),ct.current=J}}),500),()=>{st.current&&clearTimeout(st.current)})),[J,R,L]),i((()=>{if(!mt.current)return;const t=`${F}-${H}-${O}-${ut}-${K}`;if(t===lt.current)return;lt.current=t;const e=++dt.current,o=()=>e===dt.current,n=H?`${F}-${K}-${H}`:`${F}-${K||"all"}`,r=w.get(n);if(r){if(tt(r),rt(!O&&!H&&r.length>=T),!H&&!K&&r.length>0){const t=r.reduce(((t,e)=>e.createdAt>t?e.createdAt:t),r[0].createdAt);mt.current.readDataMessages(F,"",void 0,void 0,void 0,t).then((t=>{o()&&tt((e=>{const o=new Set(e.map((t=>t.id))),r=t.filter((t=>!o.has(t.id)));if(0===r.length)return e;const i=[...r,...e];return w.set(n,i),i}))})).catch(console.warn)}}else if(rt(!1),ot(!0),H)mt.current.getMessagesFast([F],K).then((t=>{if(!o())return;const e=t.map((t=>{const e=new Date(t.createdAt).toLocaleString();return{id:t.id,data:{[H]:new S(t.content).content,createdAt:e},fileId:null,fileIds:[],tags:[F],mentions:[mt.current.codicent],createdAt:e}}));w.set(n,e),tt(e)})).catch(console.warn).finally((()=>{o()&&ot(!1)}));else{const t=K||"",e=O||t?void 0:T;mt.current.readDataMessages(F,t,void 0,0,e).then((e=>{o()&&(w.set(n,e),tt(e),rt(!O&&!t&&e.length===T))})).catch(console.warn).finally((()=>{o()&&ot(!1)}))}}),[F,H,O,K,ut,pt]);const Ft=r((()=>{const t={};R.forEach(((e,o)=>{o.startsWith("filter_")&&(t[o.replace("filter_","")]=e)}));let e=Z.map(((t,e)=>({item:t,index:e})));for(const[o,n]of Object.entries(t)){const t=n.replace("%USERNAME%",wt()),r=s.context.nickname||"";e=e.filter((({item:e})=>{const n=e.data[o];return null!=n&&(String(n).includes(t)||String(n).includes(r))}))}if(N){const t=N(e.map((({item:t})=>t)),F),o=new Set(t.map((t=>t.id)));e=e.filter((({item:t})=>o.has(t.id)))}const o=e.map((({item:t,index:e})=>({...t.data,createdAt:t.createdAt,_id:t.id,originalMessageId:t.originalMessageId||t.id,_fileIds:t.fileIds??[],_index:e}))),n=E?.(F)?.deduplicateBy;if(n){const t=Array.isArray(n)?n:[n],e=e=>{for(const o of t){const t=e[o];if(null!=t&&""!==String(t).trim())return String(t)}},r=new Set;return o.filter((t=>{const o=e(t);return void 0===o||!r.has(o)&&(r.add(o),!0)}))}return o}),[Z,R,s.context.nickname,wt,N,F,E]),Ut=r((()=>{const t=(J||"").trim().toLowerCase();if(!t)return Ft;const e=St.filter((t=>!t.hidden)).map((t=>t.key));return Ft.filter((o=>{for(const n of e)if(Array.isArray(n))for(const e of n){const n=o[e];if(null!=n&&String(n).toLowerCase().includes(t))return!0}else{const e=o[n];if(null!=e&&String(e).toLowerCase().includes(t))return!0}return!1}))}),[Ft,J,St]),Ht=a((()=>{w.clearPattern(F),lt.current="",gt((t=>t+1))}),[F]),Ot=a((async()=>{at(!0);try{const t=await mt.current.readDataMessages(F,"",void 0,Z.length,T),e=[...Z,...t];tt(e),w.set(`${F}-all`,e),rt(t.length===T)}catch(t){console.warn(t)}finally{at(!1)}}),[Z,F]),Wt=a((t=>{ht(t)}),[]),qt=()=>{Dt(!1),Mt(null)},zt=a((async()=>{const t=`${F}_${(new Date).toISOString().split("T")[0]}`.replace(/[/\\:*?"<>|]/g,"_");const e=null!==ft?ft:Ut;await A(e,St,t,xt)||console.warn("No data to export")}),[ft,Ut,St,F,xt]),Vt=r((()=>null!==ft?ft.length:Ut.length),[ft,Ut]);return t(v,{hideHeader:!0,children:e("div",{className:jt.container,children:[t("div",{className:jt.backButton,children:t(c,{appearance:"subtle",icon:t(h,{}),onClick:()=>yt(-1),children:xt("Tillbaka")})}),e("div",{className:jt.headerRow,children:[t(f,{title:xt(U||"Loggbok")}),e("div",{className:jt.buttonGroup,children:[t(c,{appearance:"subtle",icon:t(j,{}),onClick:Ht,title:xt("Refresh"),children:xt("Refresh")}),z&&t(c,{appearance:"subtle",icon:t(x,{}),onClick:zt,title:xt("Export to Excel"),children:xt("Export to Excel")}),(W||V)&&t(c,{appearance:"primary",icon:t(y,{}),onClick:W?()=>{vt(null),Ct(!0)}:()=>window.open(V,"_blank"),className:jt.addButton,children:xt("Add Record")})]})]}),t("div",{className:jt.searchBox,children:t(I,{placeholder:xt("Sök")+"...",value:J,onChange:Rt,recentSearches:$t,onCommitSearch:Lt,onRemoveRecentSearch:Et})}),!et&&e("div",{className:jt.recordsCount,children:[xt("Total"),": ",Vt," ",xt("records"),Vt!==Z.length&&` (${xt("filtered from")} ${Z.length})`]}),e("div",{className:jt.listContainer,children:[et&&t("p",{children:xt("Söker")+"..."}),!et&&t(M,{data:Ut,columns:St,canEdit:q,onEdit:(t,e)=>{const o=Z[e];o&&(vt({data:{...o.data,_fileIds:o.fileIds??[]},index:e}),Ct(!0))},onDelete:(t,e)=>{const o=Z[e];o&&(Mt({index:e,id:o.id}),Dt(!0))},onToggleCheckbox:async(t,e,o,n)=>{const r=Z[n];if(r){const t=Array.isArray(e)?e[0]:e,i={...r.data,[t]:String(o)},a=await Y.updateDataMessage(r.id,i);tt((t=>{const e=[...t];return e[n]={...r,id:a,data:i},e})),w.clearPattern(F)}},onEnumChange:async(t,e,o,n)=>{const r=Z[n];if(r){const t=Array.isArray(e)?e[0]:e,i={...r.data,[t]:o},a=await Y.updateDataMessage(r.id,i);tt((t=>{const e=[...t];return e[n]={...r,id:a,data:i},e})),w.clearPattern(F)}},onFilteredDataChange:Wt})]}),nt&&!J.trim()&&t("div",{className:jt.loadMoreContainer,children:t(c,{appearance:"primary",size:"medium",onClick:Ot,disabled:it,children:it?xt("Laddar..."):xt("Visa fler")+` (${Z.length} ${xt("laddade")})`})}),t(_,{open:At,onClose:()=>Ct(!1),onSave:async t=>{const e=new Set(St.filter((t=>"thumbnail"===t.type)).map((t=>Array.isArray(t.key)?t.key[0]:t.key)));if(kt){const o=Z[kt.index];if(o){const n={};for(const o in t){if(e.has(o))continue;const r=t[o];n[o]=null==r?"":String(r)}const r=await Y.updateDataMessage(o.id,n);tt((t=>{const e=[...t];return e[kt.index]={...o,id:r,data:n},e})),w.clearPattern(F)}}else{const o={};for(const n in t){if(e.has(n))continue;const r=t[n];o[n]=null==r?"":String(r)}const n={id:await Y.createDataMessage(F,o),data:o,createdAt:(new Date).toISOString(),fileId:null,fileIds:[],tags:[F],mentions:[Y.codicent]};tt((t=>[n,...t])),w.clearPattern(F)}},columns:St,initialData:kt?.data}),t(l,{open:bt,onOpenChange:(t,e)=>!e.open&&qt(),children:t(d,{children:e(m,{children:[t(p,{children:xt("Confirm Delete")}),t(u,{children:xt("Are you sure you want to delete this record? This action cannot be undone.")}),e(g,{children:[t(c,{appearance:"secondary",onClick:qt,children:xt("Cancel")}),t(c,{appearance:"primary",onClick:async()=>{if(It)try{await Y.deleteDataMessage(It.id),tt((t=>t.filter(((t,e)=>e!==It.index)))),w.clearPattern(F)}catch(t){console.error("Failed to delete record:",t)}finally{Dt(!1),Mt(null)}},children:xt("Delete")})]})]})})})]})})};export{B as ListPage,B as default};
|
|
1
|
+
import{jsx as t,jsxs as e}from"react/jsx-runtime";import{useState as o,useRef as n,useMemo as r,useEffect as i,useCallback as a}from"react";import"../components/Markdown.js";import"../components/Textarea.js";import"../components/Button.js";import"../components/CompoundButton.js";import{makeStyles as s,Button as c,Dialog as l,DialogSurface as d,DialogBody as m,DialogTitle as p,DialogContent as u,DialogActions as g}from"@fluentui/react-components";import"../components/Spinner.js";import f from"../components/TextHeader.js";import"../components/TypingIndicator.js";import"../components/Dialog.js";import"../components/ChatInput.js";import"../components/CombinedPlaceholderDialog.js";import"../components/ChatMessage.js";import"../components/Header.js";import{ArrowLeft24Regular as h,ArrowClockwise24Regular as j,ArrowDownload24Regular as x,Add24Regular as y}from"@fluentui/react-icons";import"../services/codicent.js";import{dataCache as w}from"../services/dataCache.js";import S from"../utils/MessageContent.js";import"../node_modules/tinycolor2/esm/tinycolor.js";import"../_virtual/index.js";import"../config/index.js";import"../utils/cacheManager.js";import"../lib/wavtools/lib/wav_packer.js";import"../lib/wavtools/lib/analysis/audio_analysis.js";import"../lib/wavtools/lib/wav_stream_player.js";import"../lib/wavtools/lib/wav_recorder.js";import{exportToExcel as A}from"../utils/excelExport.js";import"./AppFrame.js";import"./Canvas.js";import"./Chat.js";import"./Compose.js";import"./Snap.js";import"./Search.js";import"./Menu.js";import"./Log.js";import"./Login.js";import"./Home.js";import"./CrmPage.js";import"./CrmPagePersistent.js";import"./ImageView.js";import"./FormInvite.js";import"./FormAccept.js";import"./Sales.js";import{useSearchParams as C,useNavigate as k}from"react-router-dom";import"./Purchase.js";import"../components/Content.js";import{Page as v}from"../components/Page.js";import"./QrScan.js";import b from"../hooks/useLocalization.js";import"../components/FileThumbnail.js";import"react-dom/client";import"../node_modules/@auth0/auth0-react/dist/auth0-react.esm.js";import"../hooks/useAppStyles.js";import{useUserRoles as D}from"../hooks/useUserRoles.js";import"../components/MessageInput.js";import"../components/UploadFile.js";import"../components/SnapFooter.js";import"../components/Profile.js";import"../components/MessageItem.js";import"../components/AiInput.js";import I from"../components/SearchBox.js";import"../components/DataMessagePicker.js";import"../components/HtmlView.js";import"../components/Footer.js";import"../components/QrCodeDialog.js";import"../components/QrScanner.js";import"../components/OfflineMessage.js";import"../components/LanguageSelector.js";import M from"../components/ListView.js";import{RecordModal as _}from"../components/RecordModal.js";import"../components/BulkUploadDialog.js";import"../components/CookieBanner.js";import"../components/audit/AuditCircularProgress.js";import"../components/audit/AuditHorizontalProgress.js";import"../components/audit/AuditRoleIndicator.js";import"../components/audit/AuditUnitSwitcher.js";import"../components/audit/AuditAnswerCell.js";import"../components/audit/AuditSearchBar.js";import"../components/audit/AuditFilterChips.js";import"../components/audit/AuditFilterBar.js";import"../components/audit/AuditGroupsProgress.js";import"../components/audit/AuditSummaryDashboard.js";import"../components/audit/AuditRequirementDialog.js";import"../components/audit/AuditUnitExportDialog.js";import"../components/audit/AuditUnitImportDialog.js";import"../components/audit/AuditBulkExportDialog.js";import"../components/audit/AuditBulkUploadDialog.js";import"../components/audit/AuditSortPresets.js";import{l as P}from"../node_modules/lodash/lodash.js";const $=s({container:{width:"100%",flex:1,maxWidth:"100%",height:"100%",minHeight:0,backgroundColor:"#ffffff",display:"flex",flexDirection:"column",margin:"0 auto",overflow:"hidden","@media (max-width: 768px)":{maxWidth:"100%",borderRadius:"0"},touchAction:"pan-y",paddingLeft:"0",paddingRight:"0"},listContainer:{overflow:"hidden",flex:1,minHeight:0,marginTop:"10px"},headerRow:{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"10px",flexWrap:"wrap"},buttonGroup:{display:"flex",gap:"8px",flexWrap:"wrap"},addButton:{minWidth:"auto",whiteSpace:"nowrap"},recordsCount:{fontSize:"14px",color:"#666",marginTop:"8px"},backButton:{marginBottom:"10px"},searchBox:{marginTop:"8px"},loadMoreContainer:{display:"flex",justifyContent:"center",padding:"16px 0",borderTop:"1px solid #e0e0e0",marginTop:"4px"}}),T=500,B=({state:s,getColumnDefs:B,getListConfig:E,filterData:N})=>{const[R,L]=C(),F=R.get("tag")||"logbook",U=R.get("title")||"",H=R.get("contentTitle"),O="true"===R.get("loadAll"),W="true"===R.get("canAdd"),q="true"===R.get("canEdit"),z="false"!==R.get("canExport"),V=R.get("addUrl")||"",G=R.get("q")||"",[J,Q]=o(G),[K,X]=o(G),{service:Y}=s,[Z,tt]=o([]),[et,ot]=o(!1),[nt,rt]=o(!1),[it,at]=o(!1),st=n(null),ct=n(G),lt=n(""),dt=n(0),mt=n(Y);mt.current=Y;const pt=!!Y,[ut,gt]=o(0),[ft,ht]=o(null),jt=$(),{t:xt}=b(),yt=k(),{getCurrentUserName:wt}=D(Y,s.context.nickname,s.context.selectedApp),St=r((()=>(H?[{key:H,title:H,maxWidth:250}]:E?E(F)?.columns??[]:B?B(F):[]).map((t=>({...t,title:t.title?xt(t.title):void 0})))),[H,F,xt,B,E]),[At,Ct]=o(!1),[kt,vt]=o(null),[bt,Dt]=o(!1),[It,Mt]=o(null),_t=r((()=>`listpage_searches_${F}`),[F]),Pt=t=>{try{const e=localStorage.getItem(t);return e?JSON.parse(e):[]}catch{return[]}},[$t,Tt]=o((()=>Pt(_t)));i((()=>{Tt(Pt(_t))}),[_t]);const Bt=a((t=>{const e=t.trim();e&&Tt((t=>{const o=[e,...t.filter((t=>t!==e))].slice(0,20);try{localStorage.setItem(_t,JSON.stringify(o))}catch{}return o}))}),[_t]),Et=a((t=>{Tt((e=>{const o=e.filter((e=>e!==t));try{localStorage.setItem(_t,JSON.stringify(o))}catch{}return o}))}),[_t]),Nt=r((()=>P.debounce((t=>X(t)),500)),[X]);i((()=>()=>{Nt.cancel()}),[Nt]);const Rt=a((t=>{Q(t),Nt(t)}),[Nt]),Lt=a((t=>{Bt(t)}),[Bt]);i((()=>{if(!R.get("q")){const t=R.get(`filter_${F}`);if(t){let e=t;if("%USERNAME%"===e){const t=wt();if(!t)return;e=t}Q(e),X(e),ct.current=e}}}),[F,R,wt]),i((()=>{const t=R.get("q")||"";t!==ct.current&&t!==K&&(Q(t),X(t),ct.current=t,lt.current="")}),[R]),i((()=>(st.current&&clearTimeout(st.current),st.current=setTimeout((()=>{if(ct.current!==J){const t=new URLSearchParams(R);J?t.set("q",J):t.delete("q"),L(t,{replace:!0}),ct.current=J}}),500),()=>{st.current&&clearTimeout(st.current)})),[J,R,L]),i((()=>{if(!mt.current)return;const t=`${F}-${H}-${O}-${ut}-${K}`;if(t===lt.current)return;lt.current=t;const e=++dt.current,o=()=>e===dt.current,n=H?`${F}-${K}-${H}`:`${F}-${K||"all"}`,r=w.get(n);if(!r){if(rt(!1),ot(!0),H)mt.current.getMessagesFast([F],K).then((t=>{if(!o())return;const e=t.map((t=>{const e=new Date(t.createdAt).toLocaleString();return{id:t.id,data:{[H]:new S(t.content).content,createdAt:e},fileId:null,fileIds:[],tags:[F],mentions:[mt.current.codicent],createdAt:e}}));w.set(n,e),tt(e)})).catch(console.warn).finally((()=>{o()&&ot(!1)}));else{const t=K||"",e=O||t?void 0:T;mt.current.readDataMessages(F,t,void 0,0,e).then((e=>{o()&&(w.set(n,e),tt(e),rt(!O&&!t&&e.length===T))})).catch(console.warn).finally((()=>{o()&&ot(!1)}))}return()=>{lt.current=""}}if(tt(r),rt(!O&&!H&&r.length>=T),!H&&!K&&r.length>0){const t=r.reduce(((t,e)=>e.createdAt>t?e.createdAt:t),r[0].createdAt);mt.current.readDataMessages(F,"",void 0,void 0,void 0,t).then((t=>{o()&&tt((e=>{const o=new Set(e.map((t=>t.id))),r=t.filter((t=>!o.has(t.id)));if(0===r.length)return e;const i=[...r,...e];return w.set(n,i),i}))})).catch(console.warn)}}),[F,H,O,K,ut,pt]);const Ft=r((()=>{const t={};R.forEach(((e,o)=>{o.startsWith("filter_")&&(t[o.replace("filter_","")]=e)}));let e=Z.map(((t,e)=>({item:t,index:e})));for(const[o,n]of Object.entries(t)){const t=n.replace("%USERNAME%",wt()),r=s.context.nickname||"";e=e.filter((({item:e})=>{const n=e.data[o];return null!=n&&(String(n).includes(t)||String(n).includes(r))}))}if(N){const t=N(e.map((({item:t})=>t)),F),o=new Set(t.map((t=>t.id)));e=e.filter((({item:t})=>o.has(t.id)))}const o=e.map((({item:t,index:e})=>({...t.data,createdAt:t.createdAt,_id:t.id,originalMessageId:t.originalMessageId||t.id,_fileIds:t.fileIds??[],_index:e}))),n=E?.(F)?.deduplicateBy;if(n){const t=Array.isArray(n)?n:[n],e=e=>{for(const o of t){const t=e[o];if(null!=t&&""!==String(t).trim())return String(t)}},r=new Set;return o.filter((t=>{const o=e(t);return void 0===o||!r.has(o)&&(r.add(o),!0)}))}return o}),[Z,R,s.context.nickname,wt,N,F,E]),Ut=r((()=>{const t=(J||"").trim().toLowerCase();if(!t)return Ft;const e=St.filter((t=>!t.hidden)).map((t=>t.key));return Ft.filter((o=>{for(const n of e)if(Array.isArray(n))for(const e of n){const n=o[e];if(null!=n&&String(n).toLowerCase().includes(t))return!0}else{const e=o[n];if(null!=e&&String(e).toLowerCase().includes(t))return!0}return!1}))}),[Ft,J,St]),Ht=a((()=>{w.clearPattern(F),lt.current="",gt((t=>t+1))}),[F]),Ot=a((async()=>{at(!0);try{const t=await mt.current.readDataMessages(F,"",void 0,Z.length,T),e=[...Z,...t];tt(e),w.set(`${F}-all`,e),rt(t.length===T)}catch(t){console.warn(t)}finally{at(!1)}}),[Z,F]),Wt=a((t=>{ht(t)}),[]),qt=()=>{Dt(!1),Mt(null)},zt=a((async()=>{const t=`${F}_${(new Date).toISOString().split("T")[0]}`.replace(/[/\\:*?"<>|]/g,"_");const e=null!==ft?ft:Ut;await A(e,St,t,xt)||console.warn("No data to export")}),[ft,Ut,St,F,xt]),Vt=r((()=>null!==ft?ft.length:Ut.length),[ft,Ut]);return t(v,{hideHeader:!0,children:e("div",{className:jt.container,children:[t("div",{className:jt.backButton,children:t(c,{appearance:"subtle",icon:t(h,{}),onClick:()=>yt(-1),children:xt("Tillbaka")})}),e("div",{className:jt.headerRow,children:[t(f,{title:xt(U||"Loggbok")}),e("div",{className:jt.buttonGroup,children:[t(c,{appearance:"subtle",icon:t(j,{}),onClick:Ht,title:xt("Refresh"),children:xt("Refresh")}),z&&t(c,{appearance:"subtle",icon:t(x,{}),onClick:zt,title:xt("Export to Excel"),children:xt("Export to Excel")}),(W||V)&&t(c,{appearance:"primary",icon:t(y,{}),onClick:W?()=>{vt(null),Ct(!0)}:()=>window.open(V,"_blank"),className:jt.addButton,children:xt("Add Record")})]})]}),t("div",{className:jt.searchBox,children:t(I,{placeholder:xt("Sök")+"...",value:J,onChange:Rt,recentSearches:$t,onCommitSearch:Lt,onRemoveRecentSearch:Et})}),!et&&e("div",{className:jt.recordsCount,children:[xt("Total"),": ",Vt," ",xt("records"),Vt!==Z.length&&` (${xt("filtered from")} ${Z.length})`]}),e("div",{className:jt.listContainer,children:[et&&t("p",{children:xt("Söker")+"..."}),!et&&t(M,{data:Ut,columns:St,canEdit:q,onEdit:(t,e)=>{const o=Z[e];o&&(vt({data:{...o.data,_fileIds:o.fileIds??[]},index:e}),Ct(!0))},onDelete:(t,e)=>{const o=Z[e];o&&(Mt({index:e,id:o.id}),Dt(!0))},onToggleCheckbox:async(t,e,o,n)=>{const r=Z[n];if(r){const t=Array.isArray(e)?e[0]:e,i={...r.data,[t]:String(o)},a=await Y.updateDataMessage(r.id,i);tt((t=>{const e=[...t];return e[n]={...r,id:a,data:i},e})),w.clearPattern(F)}},onEnumChange:async(t,e,o,n)=>{const r=Z[n];if(r){const t=Array.isArray(e)?e[0]:e,i={...r.data,[t]:o},a=await Y.updateDataMessage(r.id,i);tt((t=>{const e=[...t];return e[n]={...r,id:a,data:i},e})),w.clearPattern(F)}},onFilteredDataChange:Wt})]}),nt&&!J.trim()&&t("div",{className:jt.loadMoreContainer,children:t(c,{appearance:"primary",size:"medium",onClick:Ot,disabled:it,children:it?xt("Laddar..."):xt("Visa fler")+` (${Z.length} ${xt("laddade")})`})}),t(_,{open:At,onClose:()=>Ct(!1),onSave:async t=>{const e=new Set(St.filter((t=>"thumbnail"===t.type)).map((t=>Array.isArray(t.key)?t.key[0]:t.key)));if(kt){const o=Z[kt.index];if(o){const n={};for(const o in t){if(e.has(o))continue;const r=t[o];n[o]=null==r?"":String(r)}const r=await Y.updateDataMessage(o.id,n);tt((t=>{const e=[...t];return e[kt.index]={...o,id:r,data:n},e})),w.clearPattern(F)}}else{const o={};for(const n in t){if(e.has(n))continue;const r=t[n];o[n]=null==r?"":String(r)}const n={id:await Y.createDataMessage(F,o),data:o,createdAt:(new Date).toISOString(),fileId:null,fileIds:[],tags:[F],mentions:[Y.codicent]};tt((t=>[n,...t])),w.clearPattern(F)}},columns:St,initialData:kt?.data}),t(l,{open:bt,onOpenChange:(t,e)=>!e.open&&qt(),children:t(d,{children:e(m,{children:[t(p,{children:xt("Confirm Delete")}),t(u,{children:xt("Are you sure you want to delete this record? This action cannot be undone.")}),e(g,{children:[t(c,{appearance:"secondary",onClick:qt,children:xt("Cancel")}),t(c,{appearance:"primary",onClick:async()=>{if(It)try{await Y.deleteDataMessage(It.id),tt((t=>t.filter(((t,e)=>e!==It.index)))),w.clearPattern(F)}catch(t){console.error("Failed to delete record:",t)}finally{Dt(!1),Mt(null)}},children:xt("Delete")})]})]})})})]})})};export{B as ListPage,B as default};
|
package/dist/index.d.ts
CHANGED
|
@@ -2562,6 +2562,55 @@ interface ListPageProps {
|
|
|
2562
2562
|
/** Optional data filter applied after fetch. Useful for app-specific filtering (e.g. empty notes). */
|
|
2563
2563
|
filterData?: (data: DataMessage[], tag: string) => DataMessage[];
|
|
2564
2564
|
}
|
|
2565
|
+
/**
|
|
2566
|
+
* ListPage — data fetch & cache behaviour
|
|
2567
|
+
*
|
|
2568
|
+
* ## Fetch key guard
|
|
2569
|
+
* A `fetchKey` string (`tag-contentTitle-loadAll-refreshKey-debouncedSearch`) is stored in
|
|
2570
|
+
* `lastFetchKey` (ref). The fetch effect bails out early if the key hasn't changed, preventing
|
|
2571
|
+
* duplicate calls when unrelated state updates re-render the component.
|
|
2572
|
+
* The ref is reset to `""` on effect cleanup (unmount / dep change) so that re-navigation
|
|
2573
|
+
* always triggers a fresh fetch.
|
|
2574
|
+
*
|
|
2575
|
+
* ## Race-condition protection
|
|
2576
|
+
* `activeFetchId` (ref) is incremented each time a new fetch starts. Each in-flight promise
|
|
2577
|
+
* captures its own `myFetchId` and checks `isActive()` before writing state. A superseded
|
|
2578
|
+
* fetch (e.g. from rapid search typing or quick navigation) silently discards its result
|
|
2579
|
+
* without calling `setSearching(false)`, so the spinner stays visible until the winning fetch
|
|
2580
|
+
* completes.
|
|
2581
|
+
*
|
|
2582
|
+
* ## Cache strategy
|
|
2583
|
+
* | Visit | Cache present? | Behaviour |
|
|
2584
|
+
* |-------|---------------|-----------|
|
|
2585
|
+
* | First | No | `searching=true` → API call → populate cache → show list |
|
|
2586
|
+
* | First | Yes | Instant render from cache (no spinner) → background delta-fetch for records newer than the latest cached timestamp → silently prepend new rows |
|
|
2587
|
+
* | Back-navigation | Yes | Unmount cleanup resets fetchKey → same as "First, cache present" |
|
|
2588
|
+
* | Back-navigation | Expired | Same as "First, no cache" |
|
|
2589
|
+
* | Refresh button | Any | Clears cache for tag + resets fetchKey + increments `refreshKey` → full API call |
|
|
2590
|
+
*
|
|
2591
|
+
* ## Search / filter flow
|
|
2592
|
+
* - Typing triggers a 500 ms debounce before `debouncedSearch` updates.
|
|
2593
|
+
* - `debouncedSearch` change → new fetchKey → new `activeFetchId` → previous in-flight
|
|
2594
|
+
* fetch is superseded and its result discarded.
|
|
2595
|
+
* - `filter_<field>=<value>` URL params are applied client-side after fetch (not sent to API)
|
|
2596
|
+
* except for `filter_<tag>=<value>` which seeds `debouncedSearch` for server-side filtering.
|
|
2597
|
+
* - `q=` URL param is kept in sync with the search box via a debounced `setSearchParams`
|
|
2598
|
+
* call that preserves all other existing params (`canAdd`, `canEdit`, `filter_*`, etc.).
|
|
2599
|
+
*
|
|
2600
|
+
* ## URL params (all optional)
|
|
2601
|
+
* | Param | Type | Default | Purpose |
|
|
2602
|
+
* |-------|------|---------|---------|
|
|
2603
|
+
* | `tag` | string | `"logbook"` | Data tag to query |
|
|
2604
|
+
* | `title` | string | `"Loggbok"` | Page heading |
|
|
2605
|
+
* | `contentTitle` | string | — | If set, fetches raw messages and maps this field as the display column |
|
|
2606
|
+
* | `loadAll` | `"true"` | — | Removes PAGE_SIZE cap; disables Load-more |
|
|
2607
|
+
* | `canAdd` | `"true"` | — | Shows Add Record button (opens RecordModal) |
|
|
2608
|
+
* | `canEdit` | `"true"` | — | Shows per-row Edit/Delete buttons |
|
|
2609
|
+
* | `canExport` | `"false"` to hide | visible | Shows Export to Excel button |
|
|
2610
|
+
* | `addUrl` | string | — | External URL opened on Add click (alternative to modal) |
|
|
2611
|
+
* | `q` | string | — | Initial search term; kept in sync with search box |
|
|
2612
|
+
* | `filter_<field>` | string | — | Client-side row filter; `%USERNAME%` resolves to current user |
|
|
2613
|
+
*/
|
|
2565
2614
|
declare const ListPage: ({ state, getColumnDefs, getListConfig, filterData }: ListPageProps) => react_jsx_runtime.JSX.Element;
|
|
2566
2615
|
|
|
2567
2616
|
declare const CrmPage: React__default.FC<{
|