0){o=!0;continue}r++}}return o||a>2||a>0&&(r+a)%4!==0||r%4===1?cT(e):oT(r)},dT=(e,n)=>{if(!e||typeof e!="string"||!e.startsWith("data:"))return 0;const r=e.indexOf(",");if(r<0)return 0;const a=e.slice(5,r),o=e.slice(r+1);if(/;base64/i.test(a))return n(o);let f=0;for(let m=0,p=o.length;m=55296&&y<=56319&&m+1
=56320&&g<=57343?(f+=4,m++):f+=3}else f+=3}return f};function fT(e){const n=typeof e=="string"?e.indexOf("#"):-1;return dT(n===-1?e:e.slice(0,n),uT)}const Qp="1.20.0",$w=64*1024,hT={cache:"default",redirect:"follow",referrer:"about:client",referrerPolicy:"",mode:"cors",integrity:"",keepalive:!1,priority:"auto",window:null},{isFunction:_d}=M,mT=e=>encodeURIComponent(e).replace(/%([0-9A-F]{2})/gi,(n,r)=>String.fromCharCode(parseInt(r,16))),Fw=e=>{if(!M.isString(e))return e;try{return decodeURIComponent(e)}catch{return e}},Vw=(e,...n)=>{try{return!!e(...n)}catch{return!1}},pT=e=>{const n=e.indexOf("://");let r=e;return n!==-1&&(r=r.slice(n+3)),r.includes("@")||r.includes(":")},gT=e=>{const n=M.global!==void 0&&M.global!==null?M.global:globalThis,{ReadableStream:r,TextEncoder:a}=n;e=M.merge.call({skipUndefined:!0},{Request:n.Request,Response:n.Response},e);const{fetch:o,Request:u,Response:f}=e,m=o?_d(o):typeof fetch=="function",p=_d(u),y=_d(f);if(!m)return!1;const g=m&&_d(r),x=m&&(typeof a=="function"?(N=>j=>N.encode(j))(new a):async N=>new Uint8Array(await new u(N).arrayBuffer())),v=p&&g&&Vw(()=>{let N=!1;const j=new u(fn.origin,{body:new r,method:"POST",get duplex(){return N=!0,"half"}}),F=j.headers.has("Content-Type");return j.body!=null&&j.body.cancel(),N&&!F}),S=y&&g&&Vw(()=>M.isReadableStream(new f("").body)),A={stream:S&&(N=>N.body)};m&&["text","arrayBuffer","blob","formData","stream"].forEach(N=>{!A[N]&&(A[N]=(j,F)=>{let q=j&&j[N];if(q)return q.call(j);throw new ye(`Response type '${N}' is not supported`,ye.ERR_NOT_SUPPORT,F)})});const C=async N=>{if(N==null)return 0;if(M.isBlob(N))return N.size;if(M.isSpecCompliantForm(N))return(await new u(fn.origin,{method:"POST",body:N}).arrayBuffer()).byteLength;if(M.isArrayBufferView(N)||M.isArrayBuffer(N))return N.byteLength;if(M.isURLSearchParams(N)&&(N=N+""),M.isString(N))return(await x(N)).byteLength},O=async(N,j)=>{const F=M.toFiniteNumber(N.getContentLength());return F??C(j)};return async N=>{let{url:j,method:F,data:q,signal:B,cancelToken:re,timeout:Q,onDownloadProgress:X,onUploadProgress:se,responseType:fe,headers:we,withCredentials:Ce="same-origin",fetchOptions:Z,maxContentLength:W,maxBodyLength:oe,maxRedirects:ee}=ZS(N);const z=M.isNumber(W)&&W>-1,ue=M.isNumber(oe)&&oe>-1,Me=ce=>M.hasOwnProp(N,ce)?N[ce]:void 0;let Ae=o||fetch;fe=fe?(fe+"").toLowerCase():"text";let Ye=nT([B,re&&re.toAbortSignal()],Q),$e=null;const ut=Ye&&Ye.unsubscribe&&(()=>{Ye.unsubscribe()});let _,Y=null;const he=()=>new ye("Request body larger than maxBodyLength limit",ye.ERR_BAD_REQUEST,N,$e);try{let ce;const Oe=Me("auth");if(Oe){const pe=M.getSafeProp(Oe,"username")||"",$=M.getSafeProp(Oe,"password")||"";ce={username:pe,password:$}}if(pT(j)){const pe=new URL(j,fn.origin);if(!ce&&(pe.username||pe.password)){const $=Fw(pe.username),Ge=Fw(pe.password);ce={username:$,password:Ge}}(pe.username||pe.password)&&(pe.username="",pe.password="",j=pe.href)}if(ce&&(we.delete("authorization"),we.set("Authorization","Basic "+btoa(mT((ce.username||"")+":"+(ce.password||""))))),z&&typeof j=="string"&&j.startsWith("data:")&&fT(j)>W)throw new ye("maxContentLength size of "+W+" exceeded",ye.ERR_BAD_RESPONSE,N,$e);if(ue&&F!=="get"&&F!=="head"){const pe=await C(q);if(typeof pe=="number"&&isFinite(pe)&&(_=pe,pe>oe))throw he()}const Pe=ue&&(M.isReadableStream(q)||M.isStream(q)),qe=(pe,$,Ge)=>Hw(pe,$w,rt=>{if(ue&&rt>oe)throw Y=he();$&&$(rt)},Ge);if(v&&F!=="get"&&F!=="head"&&(se||Pe)){if(_=_??await O(we,q),_!==0||Pe){let pe=new u(j,{method:"POST",body:q,duplex:"half"}),$;if(M.isFormData(q)&&($=pe.headers.get("content-type"))&&we.setContentType($),pe.body){const[Ge,rt]=se&&Uw(_,Qd(Iw(se)))||[];q=qe(pe.body,Ge,rt)}}}else if(Pe&&!p&&g&&F!=="get"&&F!=="head")q=qe(q);else if(Pe&&p&&!v&&F!=="get"&&F!=="head")throw new ye("Stream request bodies are not supported by the current fetch implementation",ye.ERR_NOT_SUPPORT,N,$e);M.isString(Ce)||(Ce=Ce?"include":"omit");const _e=p&&"credentials"in u.prototype;if(M.isFormData(q)){const pe=we.getContentType();pe&&/^multipart\/form-data/i.test(pe)&&!/boundary=/i.test(pe)&&we.delete("content-type")}we.set("User-Agent","axios/"+Qp,!1);const je=Z==null?Z:Object.assign(Object.create(null),Z);je&&(delete je.body,delete je.headers,delete je.method,delete je.signal,delete je.duplex,delete je.credentials);const at=Object.assign(Object.create(null),je,{signal:Ye,method:F.toUpperCase(),headers:kS(we.normalize()),body:q,duplex:"half",credentials:_e?Ce:void 0});p&&(M.forEach(hT,(pe,$)=>{at[$]===void 0&&(at[$]=pe)}),at.signal===void 0&&(at.signal=null),at.body===void 0&&(at.body=null)),ee===0&&(at.redirect="manual",je&&(je.redirect="manual")),$e=p&&new u(j,at);let Tt=await(p?Ae($e,je):Ae(j,at));const Zt=On.from(Tt.headers);if(z){const pe=M.toFiniteNumber(Zt.getContentLength());if(pe!=null&&pe>W)throw new ye("maxContentLength size of "+W+" exceeded",ye.ERR_BAD_RESPONSE,N,$e)}const J=S&&(fe==="stream"||fe==="response");if(S&&Tt.body&&(X||z||J&&ut)){const pe={};["status","statusText","headers"].forEach(Sn=>{pe[Sn]=Tt[Sn]});const $=M.toFiniteNumber(Zt.getContentLength()),[Ge,rt]=X&&Uw($,Qd(Iw(X),!0))||[];let qt=0;const Sr=Sn=>{if(z&&(qt=Sn,qt>W))throw new ye("maxContentLength size of "+W+" exceeded",ye.ERR_BAD_RESPONSE,N,$e);Ge&&Ge(Sn)};Tt=new f(Hw(Tt.body,$w,Sr,()=>{rt&&rt(),ut&&ut()}),pe)}fe=fe||"text";let Re=await A[M.findKey(A,fe)||"text"](Tt,N);if(z&&!S&&!J){let pe;if(Re!=null&&(typeof Re.byteLength=="number"?pe=Re.byteLength:typeof Re.size=="number"?pe=Re.size:typeof Re=="string"&&(pe=typeof a=="function"?new a().encode(Re).byteLength:Re.length)),typeof pe=="number"&&pe>W)throw new ye("maxContentLength size of "+W+" exceeded",ye.ERR_BAD_RESPONSE,N,$e)}return!J&&ut&&ut(),await new Promise((pe,$)=>{$S(pe,$,{data:Re,headers:On.from(Tt.headers),status:Tt.status,statusText:Tt.statusText,config:N,request:$e})})}catch(ce){if(ut&&ut(),Ye&&Ye.aborted&&Ye.reason instanceof ye){const Oe=Ye.reason;throw Oe.config=N,$e&&(Oe.request=$e),ce!==Oe&&Object.defineProperty(Oe,"cause",{__proto__:null,value:ce,writable:!0,enumerable:!1,configurable:!0}),Oe}if(Y)throw $e&&!Y.request&&(Y.request=$e),Y;if(ce instanceof ye)throw $e&&!ce.request&&(ce.request=$e),ce;if(ce&&ce.name==="TypeError"&&/Load failed|fetch/i.test(ce.message)){const Oe=new ye("Network Error",ye.ERR_NETWORK,N,$e,ce&&ce.response);throw Object.defineProperty(Oe,"cause",{__proto__:null,value:ce.cause||ce,writable:!0,enumerable:!1,configurable:!0}),Oe}throw ye.from(ce,ce&&ce.code,N,$e,ce&&ce.response)}}},xT=new Map,QS=e=>{let n=e&&e.env||{};const{fetch:r,Request:a,Response:o}=n,u=[a,o,r];let f=u.length,m=f,p,y,g=xT;for(;m--;)p=u[m],y=g.get(p),y===void 0&&g.set(p,y=m?new Map:gT(n)),g=y;return y};QS();const Kp={http:NR,xhr:tT,fetch:{get:QS}};M.forEach(Kp,(e,n)=>{if(e){try{Object.defineProperty(e,"name",{__proto__:null,value:n})}catch{}Object.defineProperty(e,"adapterName",{__proto__:null,value:n})}});const Zw=e=>`- ${e}`,bT=e=>M.isFunction(e)||e===null||e===!1;function yT(e,n){e=M.isArray(e)?e:[e];const{length:r}=e;let a,o;const u={};for(let f=0;f`adapter ${p} `+(y===!1?"is not supported by the environment":"is not available in the build"));let m=r?f.length>1?`since :
+`+f.map(Zw).join(`
+`):" "+Zw(f[0]):"as no adapter specified";throw new ye("There is no suitable adapter to dispatch the request "+m,ye.ERR_NOT_SUPPORT)}return o}const KS={getAdapter:yT,adapters:Kp};function b0(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new Bc(null,e)}function y0(e){const n=M.toSafeFlatObject(e);return b0(n),n.headers=On.from(M.getSafeProp(n,"headers")),n.data=g0.call(n,n.transformRequest),["post","put","patch"].indexOf(n.method)!==-1&&n.headers.setContentType("application/x-www-form-urlencoded",!1),KS.getAdapter(n.adapter||qc.adapter,n)(n).then(function(o){b0(n),n.response=o;try{o.data=g0.call(n,n.transformResponse,o)}finally{delete n.response}return o.headers=On.from(o.headers),o},function(o){if(!BS(o)&&(b0(n),o&&o.response)){n.response=o.response;try{o.response.data=g0.call(n,n.transformResponse,o.response)}finally{delete n.response}o.response.headers=On.from(o.response.headers)}return Promise.reject(o)})}const uf={};["object","boolean","number","function","string","symbol"].forEach((e,n)=>{uf[e]=function(a){return typeof a===e||"a"+(n<1?"n ":" ")+e}});const Gw={};uf.transitional=function(n,r,a){function o(u,f){return"[Axios v"+Qp+"] Transitional option '"+u+"'"+f+(a?". "+a:"")}return(u,f,m)=>{if(n===!1)throw new ye(o(f," has been removed"+(r?" in "+r:"")),ye.ERR_DEPRECATED);return r&&!Gw[f]&&(Gw[f]=!0,console.warn(o(f," has been deprecated since v"+r+" and will be removed in the near future"))),n?n(u,f,m):!0}};uf.spelling=function(n){return(r,a)=>(console.warn(`${a} is likely a misspelling of ${n}`),!0)};function vT(e,n,r){if(typeof e!="object"||e===null)throw new ye("options must be an object",ye.ERR_BAD_OPTION_VALUE);const a=Object.keys(e);let o=a.length;for(;o-- >0;){const u=a[o],f=Object.prototype.hasOwnProperty.call(n,u)?n[u]:void 0;if(f){const m=e[u],p=m===void 0||f(m,u,e);if(p!==!0)throw new ye("option "+u+" must be "+p,ye.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new ye("Unknown option "+u,ye.ERR_BAD_OPTION)}}const Id={assertOptions:vT,validators:uf},jn=Id.validators;let Oa=class{constructor(n){this.defaults=n||{},this.interceptors={request:new Mw,response:new Mw}}async request(n,r){try{return await this._request(n,r)}catch(a){if(a instanceof Error)try{let o={};Error.captureStackTrace?Error.captureStackTrace(o):o=new Error;const u=o.stack;let f="";if(typeof u=="string"){const m=u.indexOf(`
+`);f=m===-1?"":u.slice(m+1)}if(!a.stack)a.stack=f;else if(f){const m=f.indexOf(`
+`),p=m===-1?-1:f.indexOf(`
+`,m+1),y=p===-1?"":f.slice(p+1);String(a.stack).endsWith(y)||(a.stack+=`
+`+f)}}catch{}throw a}}_request(n,r){typeof n=="string"?(r=r||{},r.url=n):r=n||{},r=Ua(this.defaults,r);const{transitional:a,paramsSerializer:o,headers:u}=r;a!==void 0&&Id.assertOptions(a,{silentJSONParsing:jn.transitional(jn.boolean),forcedJSONParsing:jn.transitional(jn.boolean),clarifyTimeoutError:jn.transitional(jn.boolean),legacyInterceptorReqResOrdering:jn.transitional(jn.boolean),advertiseZstdAcceptEncoding:jn.transitional(jn.boolean),validateStatusUndefinedResolves:jn.transitional(jn.boolean)},!1),o!=null&&(M.isFunction(o)?r.paramsSerializer={serialize:o}:Id.assertOptions(o,{encode:jn.function,serialize:jn.function},!0)),r.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?r.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:r.allowAbsoluteUrls=!0),Id.assertOptions(r,{baseUrl:jn.spelling("baseURL"),withXsrfToken:jn.spelling("withXSRFToken")},!0),r.method=(M.getSafeProp(r,"method")||M.getSafeProp(this.defaults,"method")||"get").toLowerCase();let f=u&&M.merge(u.common,u[r.method]);u&&M.forEach(qS.concat("common"),A=>{delete u[A]}),r.headers=On.concat(f,u);const m=[];let p=!0;this.interceptors.request.forEach(function(C){if(typeof C.runWhen=="function"&&C.runWhen(r)===!1)return;p=p&&C.synchronous;const O=r.transitional||Zp;O&&O.legacyInterceptorReqResOrdering?m.unshift(C.fulfilled,C.rejected):m.push(C.fulfilled,C.rejected)});const y=[];this.interceptors.response.forEach(function(C){y.push(C.fulfilled,C.rejected)});let g,x=0,v;if(!p){const A=[y0.bind(this),void 0];for(A.unshift(...m),A.push(...y),v=A.length,g=Promise.resolve(r);xy0.call(this,S)))}catch(N){g=Promise.reject(N)}break}}if(!g)try{g=y0.call(this,S)}catch(A){g=Promise.reject(A)}for(x=0,v=y.length;x{if(!a._listeners)return;let u=a._listeners.length;for(;u-- >0;)a._listeners[u](o);a._listeners=null}),this.promise.then=o=>{let u;const f=new Promise(m=>{a.subscribe(m),u=m}).then(o);return f.cancel=function(){a.unsubscribe(u)},f},n(function(u,f,m){a.reason||(a.reason=new Bc(u,f,m),r(a.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(n){if(this.reason){n(this.reason);return}this._listeners?this._listeners.push(n):this._listeners=[n]}unsubscribe(n){if(!this._listeners)return;const r=this._listeners.indexOf(n);r!==-1&&this._listeners.splice(r,1)}toAbortSignal(){const n=new AbortController,r=a=>{n.abort(a)};return this.subscribe(r),n.signal.unsubscribe=()=>this.unsubscribe(r),n.signal}static source(){let n;return{token:new YS(function(o){n=o}),cancel:n}}};function _T(e){return function(r){return e.apply(null,r)}}function ST(e){return M.isObject(e)&&e.isAxiosError===!0}const Ld={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,ContentTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,UnprocessableContent:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerReturnsAnUnknownError:520,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(Ld).forEach(([e,n])=>{Ld[n]===void 0&&(Ld[n]=e)});function JS(e){const n=new Oa(e),r=wS(Oa.prototype.request,n);return M.extend(r,Oa.prototype,n,{allOwnKeys:!0}),M.extend(r,n,null,{allOwnKeys:!0}),r.create=function(o){return JS(Ua(e,o))},r}const Vt=JS(qc);Vt.Axios=Oa;Vt.CanceledError=Bc;Vt.CancelToken=wT;Vt.isCancel=BS;Vt.VERSION=Qp;Vt.toFormData=cf;Vt.AxiosError=ye;Vt.Cancel=Vt.CanceledError;Vt.all=function(n){return Promise.all(n)};Vt.spread=_T;Vt.isAxiosError=ST;Vt.mergeConfig=Ua;Vt.AxiosHeaders=On;Vt.formToJSON=e=>HS(M.isHTMLForm(e)?new FormData(e):e);Vt.getAdapter=KS.getAdapter;Vt.HttpStatusCode=Ld;Vt.default=Vt;const{Axios:VU,AxiosError:ZU,CanceledError:GU,isCancel:QU,CancelToken:KU,VERSION:YU,all:JU,Cancel:XU,isAxiosError:WU,spread:eI,toFormData:tI,AxiosHeaders:nI,HttpStatusCode:sI,formToJSON:rI,getAdapter:iI,mergeConfig:aI,create:lI}=Vt,NT=iS(),ze=Vt.create({baseURL:NT,headers:{"Content-Type":"application/json; version=1",Accept:"application/json; version=1"},timeout:15e3});ze.interceptors.request.use(e=>{e.baseURL=iS();const n=localStorage.getItem("pfm_access_token");return n&&e.headers&&(e.headers.Authorization=`Bearer ${n}`),e},e=>Promise.reject(e));var fs=(e=>(e[e.GeneralError=0]="GeneralError",e[e.AuthenticationError=1]="AuthenticationError",e[e.ProtocolError=2]="ProtocolError",e[e.Connected=3]="Connected",e[e.Disconnected=4]="Disconnected",e[e.TooManyConnections=5]="TooManyConnections",e[e.ConnectionAborted=6]="ConnectionAborted",e[e.Success=7]="Success",e))(fs||{}),us=(e=>(e[e.Pending=1]="Pending",e[e.Queued=2]="Queued",e[e.Processing=3]="Processing",e[e.Sent=4]="Sent",e[e.Delivered=5]="Delivered",e[e.Read=6]="Read",e[e.Failed=7]="Failed",e[e.Cancelled=8]="Cancelled",e[e.Bounced=9]="Bounced",e))(us||{}),As=(e=>(e[e.Email=1]="Email",e[e.SMS=2]="SMS",e[e.WhatsApp=3]="WhatsApp",e[e.PushNotification=4]="PushNotification",e[e.InApp=5]="InApp",e[e.Teams=6]="Teams",e[e.Slack=7]="Slack",e[e.Webhook=8]="Webhook",e[e.Other=99]="Other",e))(As||{}),In=(e=>(e[e.AttendanceRegister=1]="AttendanceRegister",e[e.BankStatement=2]="BankStatement",e[e.CompletionCertificate=3]="CompletionCertificate",e[e.FinancialInvoiceOrReceipt=4]="FinancialInvoiceOrReceipt",e[e.GeotaggedPhoto=5]="GeotaggedPhoto",e[e.InspectionReport=6]="InspectionReport",e[e.MeetingMinutes=7]="MeetingMinutes",e[e.PayrollOrStipendRegister=8]="PayrollOrStipendRegister",e[e.ProcurementDocument=9]="ProcurementDocument",e[e.TechnicalSpecificationOrDesign=10]="TechnicalSpecificationOrDesign",e[e.VideoRecording=11]="VideoRecording",e[e.Other=99]="Other",e[e.ProgressReport=1]="ProgressReport",e[e.FinancialStatement=2]="FinancialStatement",e[e.Invoice=4]="Invoice",e[e.DeliverableProof=10]="DeliverableProof",e[e.Certification=3]="Certification",e[e.AuditReport=6]="AuditReport",e))(In||{}),cs=(e=>(e[e.CapacityBuildingAndTraining=1]="CapacityBuildingAndTraining",e[e.CapitalExpenditureAndInfrastructure=2]="CapitalExpenditureAndInfrastructure",e[e.CommercializationAndScaling=3]="CommercializationAndScaling",e[e.FeasibilityAndTechnicalAssistance=4]="FeasibilityAndTechnicalAssistance",e[e.OperationalGrantAndSubsidy=5]="OperationalGrantAndSubsidy",e[e.ProductDevelopmentAndPrototyping=6]="ProductDevelopmentAndPrototyping",e[e.ResearchAndDevelopment=7]="ResearchAndDevelopment",e[e.SocialImpactAndCommunityDevelopment=8]="SocialImpactAndCommunityDevelopment",e[e.TechnologyTransferAndAdoption=9]="TechnologyTransferAndAdoption",e[e.Innovation=10]="Innovation",e[e.Other=99]="Other",e[e.Grant=5]="Grant",e[e.BlendedFinance=3]="BlendedFinance",e[e.TechnicalAssistance=4]="TechnicalAssistance",e))(cs||{}),Jt=(e=>(e[e.AgricultureAndAgroProcessing=1]="AgricultureAndAgroProcessing",e[e.AutomotiveAndTransportEquipment=2]="AutomotiveAndTransportEquipment",e[e.ChemicalsAndPharmaceuticals=3]="ChemicalsAndPharmaceuticals",e[e.ConstructionAndInfrastructure=4]="ConstructionAndInfrastructure",e[e.CreativeIndustriesAndMedia=5]="CreativeIndustriesAndMedia",e[e.DefenseAndAerospace=6]="DefenseAndAerospace",e[e.EducationAndSkillsDevelopment=7]="EducationAndSkillsDevelopment",e[e.EnergyAndUtilities=8]="EnergyAndUtilities",e[e.FinancialServices=9]="FinancialServices",e[e.HealthcareAndLifeSciences=10]="HealthcareAndLifeSciences",e[e.InformationCommunicationTechnology=11]="InformationCommunicationTechnology",e[e.ManufacturingAndIndustrial=12]="ManufacturingAndIndustrial",e[e.MiningAndMetals=13]="MiningAndMetals",e[e.RealEstateAndHousing=14]="RealEstateAndHousing",e[e.RetailAndConsumerGoods=15]="RetailAndConsumerGoods",e[e.TourismAndHospitality=16]="TourismAndHospitality",e[e.WaterAndSanitation=17]="WaterAndSanitation",e[e.Other=99]="Other",e[e.AgriTech=1]="AgriTech",e[e.CleanEnergy=8]="CleanEnergy",e[e.HealthTech=10]="HealthTech",e[e.Education=7]="Education",e[e.ICTDigital=11]="ICTDigital",e[e.Manufacturing=12]="Manufacturing",e[e.SocialDevelopment=15]="SocialDevelopment",e))(Jt||{}),ot=(e=>(e[e.NonGovernmentalOrganisation=1]="NonGovernmentalOrganisation",e[e.CivilSocietyOrganisation=2]="CivilSocietyOrganisation",e[e.Trust=3]="Trust",e[e.FaithBasedOrganisation=4]="FaithBasedOrganisation",e[e.PrivateLimitedCompany=5]="PrivateLimitedCompany",e[e.PublicLimitedCompany=6]="PublicLimitedCompany",e[e.SoleProprietorship=7]="SoleProprietorship",e[e.Partnership=8]="Partnership",e[e.SocialEnterprise=9]="SocialEnterprise",e[e.NationalGovernmentMinistry=10]="NationalGovernmentMinistry",e[e.ProvincialGovernmentEntity=11]="ProvincialGovernmentEntity",e[e.LocalGovernmentMunicipality=12]="LocalGovernmentMunicipality",e[e.StateOwnedEnterprise=13]="StateOwnedEnterprise",e[e.PublicAgency=14]="PublicAgency",e[e.PublicUniversity=15]="PublicUniversity",e[e.PrivateUniversity=16]="PrivateUniversity",e[e.ResearchInstitute=17]="ResearchInstitute",e[e.TechnicalVocationalCollege=18]="TechnicalVocationalCollege",e[e.Cooperative=19]="Cooperative",e[e.MicrofinanceInstitution=20]="MicrofinanceInstitution",e[e.CommercialBank=21]="CommercialBank",e[e.DevelopmentFinanceInstitution=22]="DevelopmentFinanceInstitution",e[e.UnitedNationsAgency=23]="UnitedNationsAgency",e[e.IntergovernmentalOrganisation=24]="IntergovernmentalOrganisation",e[e.InternationalNGO=25]="InternationalNGO",e[e.Consortium=26]="Consortium",e[e.SpecialPurposeVehicle=27]="SpecialPurposeVehicle",e[e.GovernmentDepartment=10]="GovernmentDepartment",e[e.CorporateCSI=3]="CorporateCSI",e[e.Enterprise=5]="Enterprise",e))(ot||{}),yr=(e=>(e[e.Low=1]="Low",e[e.Medium=2]="Medium",e[e.High=3]="High",e[e.Critical=4]="Critical",e))(yr||{}),$s=(e=>(e[e.Count=1]="Count",e[e.Percentage=2]="Percentage",e[e.Currency=3]="Currency",e[e.Area=4]="Area",e[e.Weight=5]="Weight",e[e.Volume=6]="Volume",e[e.DurationDays=7]="DurationDays",e[e.DurationMonths=8]="DurationMonths",e[e.Ratio=9]="Ratio",e[e.CurrencyZAR=3]="CurrencyZAR",e))($s||{}),dn=(e=>(e[e.Draft=1]="Draft",e[e.PendingApproval=2]="PendingApproval",e[e.Active=3]="Active",e[e.Expired=4]="Expired",e[e.Terminated=5]="Terminated",e[e.Amended=6]="Amended",e[e.UnderReview=2]="UnderReview",e[e.Executed=3]="Executed",e))(dn||{}),ma=(e=>(e[e.Rare=1]="Rare",e[e.Unlikely=2]="Unlikely",e[e.Possible=3]="Possible",e[e.Likely=4]="Likely",e[e.AlmostCertain=5]="AlmostCertain",e[e.VeryLow=1]="VeryLow",e[e.Low=2]="Low",e[e.Medium=3]="Medium",e[e.High=4]="High",e[e.VeryHigh=5]="VeryHigh",e))(ma||{}),pa=(e=>(e[e.Minor=1]="Minor",e[e.Moderate=2]="Moderate",e[e.Major=3]="Major",e[e.Critical=4]="Critical",e[e.Severe=5]="Severe",e[e.Insignificant=1]="Insignificant",e))(pa||{}),bt=(e=>(e[e.Pending=1]="Pending",e[e.Review=2]="Review",e[e.Approved=3]="Approved",e[e.Rejected=4]="Rejected",e[e.Removed=5]="Removed",e[e.Draft=1]="Draft",e[e.PendingReview=2]="PendingReview",e))(bt||{}),Cn=(e=>(e[e.Active=1]="Active",e[e.Inactive=2]="Inactive",e[e.Disabled=3]="Disabled",e[e.Suspended=3]="Suspended",e))(Cn||{});const jT=async()=>(await ze.get("/v1/api/governance/dashboard")).data,df=async(e=1,n=100)=>(await ze.get("/v1/api/governance/organisations",{params:{page:e,pageSize:n}})).data||[],ET=async e=>{const n=Ht();return(await ze.post("/v1/api/governance/organisations",{...e,createdBy:n||void 0})).data},AT=async e=>{const n=Ht();return(await ze.put("/v1/api/governance/organisations",{...e,updatedBy:n||void 0})).data},CT=async e=>{const n=await Jn.resolveOrganisationId(e);return(await ze.get(`/v1/api/governance/organisations/${n}/portfolio`)).data||[]},XS=async()=>{try{const e=await df(1,10);if(e.length>0){const a=(await Promise.allSettled(e.map(o=>CT(o.id)))).filter(o=>o.status==="fulfilled").flatMap(o=>o.value);if(a.length>0)return a}return(await ze.get("/v1/api/governance/organisations/portfolio")).data||[]}catch{return[]}},OT=async e=>{const n=await Jn.resolvePortfolioId(e);return(await ze.get(`/v1/api/governance/portfolios/${n}/programmes`)).data||[]},RT=async e=>{const n=Ht();return(await ze.post("/v1/api/governance/organisations/portfolio",{...e,ownerId:e.ownerId||n||void 0,createdBy:n||void 0})).data},TT=async e=>{const n=Ht();return(await ze.put("/v1/api/governance/organisations/portfolio",{...e,ownerId:e.ownerId||n||void 0,updatedBy:n||void 0})).data},kT=async e=>{const n=await Jn.resolveProgrammeId(e);return(await ze.get(`/v1/api/governance/instruments/${n}`)).data||[]},ff=async(e=1,n=100)=>(await ze.get(`/v1/api/projects/programme/${e}/${n}`)).data||[],DT=async e=>{const n=await Jn.resolveProgrammeId(e);return(await ze.get(`/v1/api/projects/${n}/programme`)).data||[]},MT=async e=>{const n=Ht();return(await ze.post("/v1/api/projects/programme",{...e,ownerId:e.ownerId||n||void 0,createdBy:n||void 0})).data},zT=async e=>{const n=Ht();return(await ze.put("/v1/api/projects/programme",{...e,ownerId:e.ownerId||n||void 0,updatedBy:n||void 0})).data},hf=async(e=1,n=100)=>(await ze.get("/v1/api/projects",{params:{page:e,pageSize:n}})).data||[],UT=async e=>{const n=Ht();return(await ze.post("/v1/api/projects",{...e,createdBy:n||void 0})).data},IT=async e=>{const n=Ht();await ze.put("/v1/api/projects/status",{...e,updatedBy:n||void 0})},LT=async e=>{const n=await Jn.resolveProjectId(e);return(await ze.get(`/v1/api/projects/${n}/activities`)).data||[]},PT=async e=>{const n=Ht();return(await ze.post("/v1/api/projects/activities",{...e,createdBy:n||void 0})).data},HT=async e=>{const n=await Jn.resolveProjectId(e);return(await ze.get(`/v1/api/projects/${n}/milestones`)).data||[]},WS=async(e=1,n=100)=>(await ze.get(`/v1/api/projects/milestones/${e}/${n}`)).data||[],qT=async e=>{const n=Ht();return(await ze.post("/v1/api/evidence/milestones",{...e,createdBy:n||void 0})).data},BT=async e=>{const n=await Jn.resolveProjectId(e);return(await ze.get(`/v1/api/projects/indicators/project/${n}`)).data||[]},$T=async e=>{const n=await Jn.resolveProjectId(e);return(await ze.get(`/v1/api/projects/${n}/risks`)).data||[]},Yp=async(e=1,n=100)=>(await ze.get("/v1/api/grants/awards",{params:{page:e,pageSize:n}})).data||[],FT=async e=>{const n=Ht();return(await ze.post("/v1/api/grants/awards",{...e,createdBy:n||void 0})).data},eN=async(e=1,n=100)=>(await ze.get("/v1/api/grants/contracts",{params:{page:e,pageSize:n}})).data||[],VT=async e=>{const n=Ht();return(await ze.post("/v1/api/grants/contracts",{...e,createdBy:n||void 0})).data},ZT=async e=>{await ze.put("/v1/api/grants/contracts",e)},GT=async(e,n)=>{const r=await Jn.resolveContractId(e),a=n instanceof FormData?n:new FormData;n instanceof File&&a.append("file",n),await ze.post(`/v1/api/grants/contracts/${r}`,a,{headers:{"Content-Type":"multipart/form-data"}})},QT=async e=>{const n=await Jn.resolveContractId(e);return(await ze.get(`/v1/api/grants/contracts/${n}/securelink`)).data},KT=async e=>{const n=Ht();await ze.post("/v1/api/grants/contracts/execute",{...e,executedBy:n||void 0})},tN=async(e=1,n=100)=>(await ze.get("/v1/api/grants/budgets",{params:{page:e,pageSize:n}})).data||[],YT=async e=>{const n=Ht();return(await ze.post("/v1/api/grants/budgets",{...e,createdBy:n||void 0})).data},nN=async(e=1,n=100)=>(await ze.get("/v1/api/execution/disbursements",{params:{page:e,pageSize:n}})).data||[],JT=async e=>{const n=Ht();return(await ze.post("/v1/api/execution/disbursements",{...e,createdBy:n||void 0})).data},XT=async e=>{const n=await Jn.encodeId(e),r=Ht();await ze.post(`/v1/api/execution/disbursements/${n}/approve`,{approvedBy:r||void 0})},sN=async(e=1,n=100)=>(await ze.get("/v1/api/execution/risks",{params:{page:e,pageSize:n}})).data||[],WT=async e=>{const n=Ht();return(await ze.post("/v1/api/execution/risks",{...e,createdBy:n||void 0})).data},ek=async(e=1,n=100)=>(await ze.get(`/v1/api/evidence/indicators/${e}/${n}`)).data||[],tk=async e=>{const n=Ht();return(await ze.post("/v1/api/evidence/indicators",{...e,createdBy:n||void 0})).data},nk=async(e,n)=>{const r=await Jn.encodeId(e),a=Ht();await ze.post(`/v1/api/evidence/indicators/${r}/actuals/${n}`,{recordedBy:a||void 0})},sk=async(e=1,n=100)=>(await ze.get(`/v1/api/evidence/files/${e}/${n}`)).data||[],rk=async(e=1,n=100)=>sk(e,n),ik=async e=>{const n=await Jn.encodeId(e);return(await ze.get(`/v1/api/evidence/${n}/securelink`)).data},ak=async e=>{const n=await Jn.encodeId(e);return(await ze.get(`/v1/api/evidence/milestone/${n}`)).data||[]},lk=async e=>{const n=Ht();return n&&!e.get("userId")&&e.append("userId",n),(await ze.put("/v1/api/evidence/file",e,{headers:{"Content-Type":"multipart/form-data"}})).data},ok=async e=>{const n=Ht();await ze.put("/v1/api/evidence",{...e,updatedBy:n||void 0})},ck=async(e=1,n=100,r={})=>(await ze.post(`/v1/api/users/${e}/${n}`,r)).data||[],uk=async(e,n)=>{await ze.get(`/v1/api/users/${e}/status/${n}`)},dk=async()=>{await ze.post("/v1/api/integration/sync")},fk=async e=>{const n=Ht();return(await ze.post("/v1/api/communications/invite",{...e,createdBy:n||void 0})).data},hk=async(e=1,n=100)=>(await ze.get(`/v1/api/communications/invites/${e}/${n}`)).data||[],mk=async e=>(await ze.get(`/v1/api/communications/invites/${e}`)).data||[],pk=async()=>{try{return(await ze.get("/v1/api/governance/audit")).data||[]}catch{return[]}},ji=e=>new Intl.NumberFormat("en-ZA",{style:"currency",currency:"ZAR",maximumFractionDigits:0}).format(e),Ei=e=>e>=1e9?`R ${(e/1e9).toFixed(2)}bn`:e>=1e6?`R ${(e/1e6).toFixed(1)}m`:e>=1e3?`R ${(e/1e3).toFixed(0)}k`:ji(e),Kn=e=>{if(!e)return"—";try{return new Date(e).toLocaleDateString("en-GB",{day:"numeric",month:"short",year:"numeric"})}catch{return e}},rN=[{value:fs.GeneralError,label:"General Error"},{value:fs.AuthenticationError,label:"Authentication Error"},{value:fs.ProtocolError,label:"Protocol Error"},{value:fs.Connected,label:"Connected"},{value:fs.Disconnected,label:"Disconnected"},{value:fs.TooManyConnections,label:"Too Many Connections"},{value:fs.ConnectionAborted,label:"Connection Aborted"},{value:fs.Success,label:"Success"}],gk=rN;us.Pending,us.Queued,us.Processing,us.Sent,us.Delivered,us.Read,us.Failed,us.Cancelled,us.Bounced;const iN=[{value:As.Email,label:"Email"},{value:As.SMS,label:"SMS"},{value:As.WhatsApp,label:"WhatsApp"},{value:As.PushNotification,label:"Push Notification"},{value:As.InApp,label:"In App"},{value:As.Teams,label:"Teams"},{value:As.Slack,label:"Slack"},{value:As.Webhook,label:"Webhook"},{value:As.Other,label:"Other"}],aN=[{value:In.AttendanceRegister,label:"Attendance Register"},{value:In.BankStatement,label:"Bank Statement"},{value:In.CompletionCertificate,label:"Completion Certificate"},{value:In.FinancialInvoiceOrReceipt,label:"Financial Invoice Or Receipt"},{value:In.GeotaggedPhoto,label:"Geotagged Photo"},{value:In.InspectionReport,label:"Inspection Report"},{value:In.MeetingMinutes,label:"Meeting Minutes"},{value:In.PayrollOrStipendRegister,label:"Payroll Or Stipend Register"},{value:In.ProcurementDocument,label:"Procurement Document"},{value:In.TechnicalSpecificationOrDesign,label:"Technical Specification Or Design"},{value:In.VideoRecording,label:"Video Recording"},{value:In.Other,label:"Other"}],lN=[{value:cs.CapacityBuildingAndTraining,label:"Capacity Building And Training"},{value:cs.CapitalExpenditureAndInfrastructure,label:"Capital Expenditure And Infrastructure"},{value:cs.CommercializationAndScaling,label:"Commercialization And Scaling"},{value:cs.FeasibilityAndTechnicalAssistance,label:"Feasibility And Technical Assistance"},{value:cs.OperationalGrantAndSubsidy,label:"Operational Grant And Subsidy"},{value:cs.ProductDevelopmentAndPrototyping,label:"Product Development And Prototyping"},{value:cs.ResearchAndDevelopment,label:"Research And Development"},{value:cs.SocialImpactAndCommunityDevelopment,label:"Social Impact And Community Development"},{value:cs.TechnologyTransferAndAdoption,label:"Technology Transfer And Adoption"},{value:cs.Innovation,label:"Innovation"},{value:cs.Other,label:"Other"}],oN=[{value:Jt.AgricultureAndAgroProcessing,label:"Agriculture And Agro Processing"},{value:Jt.AutomotiveAndTransportEquipment,label:"Automotive And Transport Equipment"},{value:Jt.ChemicalsAndPharmaceuticals,label:"Chemicals And Pharmaceuticals"},{value:Jt.ConstructionAndInfrastructure,label:"Construction And Infrastructure"},{value:Jt.CreativeIndustriesAndMedia,label:"Creative Industries And Media"},{value:Jt.DefenseAndAerospace,label:"Defense And Aerospace"},{value:Jt.EducationAndSkillsDevelopment,label:"Education And Skills Development"},{value:Jt.EnergyAndUtilities,label:"Energy And Utilities"},{value:Jt.FinancialServices,label:"Financial Services"},{value:Jt.HealthcareAndLifeSciences,label:"Healthcare And Life Sciences"},{value:Jt.InformationCommunicationTechnology,label:"Information Communication Technology"},{value:Jt.ManufacturingAndIndustrial,label:"Manufacturing And Industrial"},{value:Jt.MiningAndMetals,label:"Mining And Metals"},{value:Jt.RealEstateAndHousing,label:"Real Estate And Housing"},{value:Jt.RetailAndConsumerGoods,label:"Retail And Consumer Goods"},{value:Jt.TourismAndHospitality,label:"Tourism And Hospitality"},{value:Jt.WaterAndSanitation,label:"Water And Sanitation"},{value:Jt.Other,label:"Other"}],cN=[{value:ot.NonGovernmentalOrganisation,label:"Non-Governmental Organisation"},{value:ot.CivilSocietyOrganisation,label:"Civil Society Organisation"},{value:ot.Trust,label:"Trust"},{value:ot.FaithBasedOrganisation,label:"Faith-Based Organisation"},{value:ot.PrivateLimitedCompany,label:"Private Limited Company"},{value:ot.PublicLimitedCompany,label:"Public Limited Company"},{value:ot.SoleProprietorship,label:"Sole Proprietorship"},{value:ot.Partnership,label:"Partnership"},{value:ot.SocialEnterprise,label:"Social Enterprise"},{value:ot.NationalGovernmentMinistry,label:"National Government Ministry"},{value:ot.ProvincialGovernmentEntity,label:"Provincial Government Entity"},{value:ot.LocalGovernmentMunicipality,label:"Local Government Municipality"},{value:ot.StateOwnedEnterprise,label:"State-Owned Enterprise"},{value:ot.PublicAgency,label:"Public Agency"},{value:ot.PublicUniversity,label:"Public University"},{value:ot.PrivateUniversity,label:"Private University"},{value:ot.ResearchInstitute,label:"Research Institute"},{value:ot.TechnicalVocationalCollege,label:"Technical Vocational College"},{value:ot.Cooperative,label:"Cooperative"},{value:ot.MicrofinanceInstitution,label:"Microfinance Institution"},{value:ot.CommercialBank,label:"Commercial Bank"},{value:ot.DevelopmentFinanceInstitution,label:"Development Finance Institution"},{value:ot.UnitedNationsAgency,label:"United Nations Agency"},{value:ot.IntergovernmentalOrganisation,label:"Intergovernmental Organisation"},{value:ot.InternationalNGO,label:"International NGO"},{value:ot.Consortium,label:"Consortium"},{value:ot.SpecialPurposeVehicle,label:"Special Purpose Vehicle"}],xk=[{value:yr.Low,label:"Low"},{value:yr.Medium,label:"Medium"},{value:yr.High,label:"High"},{value:yr.Critical,label:"Critical"}],uN=[{value:$s.Count,label:"Count"},{value:$s.Percentage,label:"Percentage"},{value:$s.Currency,label:"Currency"},{value:$s.Area,label:"Area"},{value:$s.Weight,label:"Weight"},{value:$s.Volume,label:"Volume"},{value:$s.DurationDays,label:"Duration (Days)"},{value:$s.DurationMonths,label:"Duration (Months)"},{value:$s.Ratio,label:"Ratio"}];dn.Draft,dn.PendingApproval,dn.Active,dn.Expired,dn.Terminated,dn.Amended;const dN=[{value:bt.Pending,label:"Pending"},{value:bt.Review,label:"Review"},{value:bt.Approved,label:"Approved"},{value:bt.Rejected,label:"Rejected"},{value:bt.Removed,label:"Removed"}],bk=[{value:Cn.Active,label:"Active"},{value:Cn.Inactive,label:"Inactive"},{value:Cn.Disabled,label:"Disabled"}],yk=[{value:ma.Rare,label:"Rare"},{value:ma.Unlikely,label:"Unlikely"},{value:ma.Possible,label:"Possible"},{value:ma.Likely,label:"Likely"},{value:ma.AlmostCertain,label:"Almost Certain"}],vk=[{value:pa.Minor,label:"Minor"},{value:pa.Moderate,label:"Moderate"},{value:pa.Major,label:"Major"},{value:pa.Critical,label:"Critical"},{value:pa.Severe,label:"Severe"}],Xl=e=>{switch(Number(e)){case bt.Approved:return{label:"Approved",className:"bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold",dot:"bg-emerald-500"};case bt.Review:return{label:"Review",className:"bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold",dot:"bg-amber-500"};case bt.Rejected:return{label:"Rejected",className:"bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold",dot:"bg-rose-500"};case bt.Removed:return{label:"Removed",className:"bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold",dot:"bg-slate-500"};case bt.Pending:default:return{label:"Pending",className:"bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold",dot:"bg-blue-500"}}},Qw=e=>{switch(Number(e)){case dn.Active:return{label:"Active",className:"bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold"};case dn.PendingApproval:return{label:"Pending Approval",className:"bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold"};case dn.Amended:return{label:"Amended",className:"bg-indigo-500/10 text-indigo-400 border-indigo-500/30 font-semibold"};case dn.Expired:return{label:"Expired",className:"bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold"};case dn.Terminated:return{label:"Terminated",className:"bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold"};case dn.Draft:default:return{label:"Draft",className:"bg-slate-800/80 text-slate-300 border-slate-700/80 font-semibold"}}},Ml=e=>{switch(Number(e)){case Cn.Active:return{label:"Active",className:"bg-emerald-500/10 text-emerald-400 border-emerald-500/30 font-semibold"};case Cn.Inactive:return{label:"Inactive",className:"bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold"};case Cn.Disabled:default:return{label:"Disabled",className:"bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold"}}},wk=e=>{switch(Number(e)){case yr.Critical:return{label:"Critical",className:"bg-rose-500/10 text-rose-400 border-rose-500/30 font-semibold"};case yr.High:return{label:"High",className:"bg-amber-500/10 text-amber-400 border-amber-500/30 font-semibold"};case yr.Medium:return{label:"Medium",className:"bg-blue-500/10 text-blue-400 border-blue-500/30 font-semibold"};case yr.Low:default:return{label:"Low",className:"bg-slate-800/80 text-slate-400 border-slate-700/80 font-semibold"}}},_k=e=>{const n=cN.find(r=>r.value===Number(e));return n?n.label:"Organisation"},fN=e=>{const n=oN.find(r=>r.value===Number(e));return n?n.label:"Industry Sector"},Sk=e=>{const n=lN.find(r=>r.value===Number(e));return n?n.label:"Project Type"},Nk=e=>{const n=aN.find(r=>r.value===Number(e));return n?n.label:"Evidence"},jk=e=>{if(e==null)return"Qty";const n=uN.find(r=>r.value===Number(e));return n?n.label:"Qty"},Kw=e=>{const n=iN.find(r=>r.value===Number(e));return n?n.label:"Platform"},Pd=e=>{const n=rN.find(r=>r.value===Number(e));return n?n.label:"Status"},Yw=e=>{const n=Number(e);switch(n){case fs.Connected:case fs.Success:return{label:Pd(n),className:"bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold"};case fs.Disconnected:return{label:Pd(n),className:"bg-slate-100 text-slate-700 border-slate-300 font-semibold"};default:return{label:Pd(n),className:"bg-rose-50 text-rose-800 border-rose-300 font-semibold"}}},Ek=(e,n)=>{const r=Number(e)*Number(n);return r>=15?{level:"High",color:"bg-rose-600 text-white",badge:"bg-rose-50 text-rose-800 border-rose-300 font-semibold"}:r>=8?{level:"Medium",color:"bg-amber-500 text-white",badge:"bg-amber-50 text-amber-900 border-amber-300 font-semibold"}:{level:"Low",color:"bg-emerald-600 text-white",badge:"bg-emerald-50 text-emerald-800 border-emerald-300 font-semibold"}},Jw={},ya=e=>e?e.includes("@")?e:Jw[e]?Jw[e]:e:"—",Ak=()=>{var Ce;const[e,n]=T.useState("All"),[r,a]=T.useState("All"),[o,u]=T.useState("FY 2026/27"),{data:f,isLoading:m,refetch:p,isFetching:y}=Ke({queryKey:["dashboard"],queryFn:jT}),{data:g=[]}=Ke({queryKey:["projects"],queryFn:()=>hf()}),{data:x=[]}=Ke({queryKey:["portfolios"],queryFn:()=>XS()}),{data:v=[]}=Ke({queryKey:["programmes"],queryFn:()=>ff()}),{data:S=[]}=Ke({queryKey:["awards"],queryFn:()=>Yp()}),{data:A=[]}=Ke({queryKey:["budgets"],queryFn:()=>tN()}),{data:C=[]}=Ke({queryKey:["disbursements"],queryFn:()=>nN()}),{data:O=[]}=Ke({queryKey:["risks"],queryFn:()=>sN()}),N=()=>{p()},j=T.useMemo(()=>{if(f!=null&&f.portfolioValue&&f.portfolioValue>0)return f.portfolioValue;const Z=A.reduce((oe,ee)=>oe+(ee.amount||0),0);return Z>0?Z:S.reduce((oe,ee)=>oe+(ee.amount||0),0)},[f,A,S]),F=(f==null?void 0:f.activeAwards)??(S.length||g.length),q=T.useMemo(()=>f!=null&&f.disbursed&&f.disbursed>0?f.disbursed:C.filter(Z=>Z.status===bt.Approved||Z.status===3).reduce((Z,W)=>Z+(W.amount||0),0),[f,C]),B=T.useMemo(()=>{if((f==null?void 0:f.onTrackPercentage)!==void 0&&f.onTrackPercentage>0)return f.onTrackPercentage;if(g.length===0)return 0;const Z=g.filter(W=>W.status===bt.Approved).length;return Math.round(Z/g.length*100)},[f,g]),re=T.useMemo(()=>(f==null?void 0:f.atRiskCount)!==void 0?f.atRiskCount:O.filter(Z=>Z.likelihood*Z.impact>=12).length,[f,O]),Q=(f==null?void 0:f.beneficiariesReached)??0,X=T.useMemo(()=>{if(!g||g.length===0)return[];const Z={};g.forEach(ee=>{const z=fN(ee.sector);Z[z]=(Z[z]||0)+1});const W=["bg-emerald-500","bg-teal-500","bg-blue-500","bg-indigo-500","bg-purple-500","bg-amber-500"],oe=g.length;return Object.entries(Z).map(([ee,z],ue)=>{const Me=Math.round(z/oe*100),Ae=j>0?Math.round(j/oe*z):0;return{name:ee,count:z,percentage:Me,amount:Ae,color:W[ue%W.length]}})},[g,j]),se=T.useMemo(()=>({highImpHighLik:O.filter(W=>W.impact>=4&&W.likelihood>=4).length,highImpMedLik:O.filter(W=>W.impact>=4&&W.likelihood===3).length,highImpLowLik:O.filter(W=>W.impact>=4&&W.likelihood<=2).length,medImpHighLik:O.filter(W=>W.impact===3&&W.likelihood>=4).length,medImpMedLik:O.filter(W=>W.impact===3&&W.likelihood===3).length,medImpLowLik:O.filter(W=>W.impact===3&&W.likelihood<=2).length,lowImpHighLik:O.filter(W=>W.impact<=2&&W.likelihood>=4).length,lowImpMedLik:O.filter(W=>W.impact<=2&&W.likelihood===3).length,lowImpLowLik:O.filter(W=>W.impact<=2&&W.likelihood<=2).length,flaggedCount:O.filter(W=>W.impact*W.likelihood>=12).length}),[O]),fe=T.useMemo(()=>{if(!C||C.length===0)return[{quarter:"Q1",amount:0,heightPct:15},{quarter:"Q2",amount:0,heightPct:15},{quarter:"Q3",amount:0,heightPct:15},{quarter:"Q4",amount:0,heightPct:15}];const Z={Q1:0,Q2:0,Q3:0,Q4:0};C.forEach(oe=>{const ee=oe.createdAt||oe.updatedAt;if(ee){const z=new Date(ee).getMonth();z<=2?Z.Q1+=oe.amount||0:z<=5?Z.Q2+=oe.amount||0:z<=8?Z.Q3+=oe.amount||0:Z.Q4+=oe.amount||0}else Z.Q1+=(oe.amount||0)*.25,Z.Q2+=(oe.amount||0)*.25,Z.Q3+=(oe.amount||0)*.25,Z.Q4+=(oe.amount||0)*.25});const W=Math.max(...Object.values(Z),1);return Object.entries(Z).map(([oe,ee])=>({quarter:oe,amount:ee,heightPct:Math.max(15,Math.min(100,Math.round(ee/W*100)))}))},[C]),we=T.useMemo(()=>!g||g.length===0?[]:g.filter(Z=>Z.status===bt.Rejected||Z.status===bt.Pending||Z.status===bt.Review).slice(0,5).map(Z=>{const W=Z.status===bt.Rejected,oe=Z.status===bt.Pending||Z.status===bt.Review;return{id:Z.id,name:Z.name,programme:Z.programmeName||`Programme ${Z.programmeId}`,status:W?"At Risk":oe?"Pending":"Active",risk:W?"RED":oe?"AMBER":"GREEN",financialVariance:W?"-15%":"0%",varianceType:W?"negative":"positive"}}),[g]);return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"flex flex-col md:flex-row md:items-center justify-between gap-4 bg-[#0d121f]/90 p-5 rounded-2xl border border-slate-800/80 backdrop-blur-md shadow-xl",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("h2",{className:"text-xl font-bold text-white tracking-tight",children:"Executive Portfolio Dashboard"}),c.jsx("span",{className:"text-[11px] font-semibold text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-full border border-blue-500/30",children:"Live Portfolio Feed"})]}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action."})]}),c.jsxs("div",{className:"flex flex-wrap items-center gap-3",children:[c.jsxs("div",{className:"flex items-center gap-1.5 text-xs text-slate-400",children:[c.jsx(CC,{className:"w-3.5 h-3.5 text-slate-500"}),c.jsx("span",{children:"Filter:"})]}),c.jsxs("select",{value:e,onChange:Z=>n(Z.target.value),className:"text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer",children:[c.jsx("option",{value:"All",children:"Programme: All"}),v.map(Z=>c.jsx("option",{value:Z.id,children:Z.name},Z.id))]}),c.jsxs("select",{value:r,onChange:Z=>a(Z.target.value),className:"text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer",children:[c.jsx("option",{value:"All",children:"Sector: All"}),X.map(Z=>c.jsx("option",{value:Z.name,children:Z.name},Z.name))]}),c.jsxs("select",{value:o,onChange:Z=>u(Z.target.value),className:"text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer",children:[c.jsx("option",{value:"FY 2026/27",children:"Period: FY 2026/27"}),c.jsx("option",{value:"FY 2025/26",children:"Period: FY 2025/26"}),c.jsx("option",{value:"All-Time",children:"All-Time Cumulative"})]}),c.jsx("button",{onClick:N,disabled:y,className:"p-2 text-slate-400 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs",title:"Refresh dashboard metrics",children:c.jsx(tp,{className:`w-4 h-4 ${y?"animate-spin text-blue-400":""}`})})]})]}),c.jsxs("div",{className:"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-blue-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"Portfolio Value"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-blue-500/10 text-blue-400 border border-blue-500/20 group-hover:scale-110 transition",children:c.jsx(Ic,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsx("p",{className:"text-2xl font-bold text-white tracking-tight",children:Ei(j)}),c.jsx("p",{className:"text-[10px] text-slate-400 font-mono mt-1",children:ji(j)})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-blue-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"Active Awards"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-110 transition",children:c.jsx(W0,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsx("p",{className:"text-2xl font-bold text-white tracking-tight",children:F}),c.jsxs("p",{className:"text-[10px] text-emerald-400 font-mono mt-1",children:["Across ",x.length," portfolio",x.length===1?"":"s"]})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-teal-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"Disbursed"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-teal-500/10 text-teal-400 border border-teal-500/20 group-hover:scale-110 transition",children:c.jsx(qp,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsx("p",{className:"text-2xl font-bold text-teal-400 tracking-tight",children:Ei(q)}),c.jsx("p",{className:"text-[10px] text-slate-400 font-mono mt-1",children:j>0?`${(q/j*100).toFixed(1)}% of commitments`:"0.0% of commitments"})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-emerald-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"On Track"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 group-hover:scale-110 transition",children:c.jsx(X0,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsxs("p",{className:"text-2xl font-bold text-emerald-400 tracking-tight",children:[B,"%"]}),c.jsx("p",{className:"text-[10px] text-slate-400 font-mono mt-1",children:"Milestones verified"})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-amber-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"At Risk"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-amber-500/10 text-amber-400 border border-amber-500/20 group-hover:scale-110 transition",children:c.jsx(aO,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsx("p",{className:"text-2xl font-bold text-amber-400 tracking-tight",children:re}),c.jsx("p",{className:"text-[10px] text-rose-400 font-mono mt-1",children:"Requires PM action"})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-indigo-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm",children:[c.jsxs("div",{className:"flex items-center justify-between text-slate-400",children:[c.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-wider text-slate-400",children:"Beneficiaries"}),c.jsx("div",{className:"p-1.5 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-110 transition",children:c.jsx(yS,{className:"w-3.5 h-3.5"})})]}),c.jsxs("div",{className:"mt-3",children:[c.jsx("p",{className:"text-2xl font-bold text-white tracking-tight",children:Q>=1e3?`${(Q/1e3).toFixed(0)}k`:Q}),c.jsx("p",{className:"text-[10px] text-slate-400 font-mono mt-1",children:"Directly impacted"})]})]})]}),c.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-3 gap-6",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 flex flex-col justify-between shadow-lg backdrop-blur-sm",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center justify-between border-b border-slate-800/80 pb-3",children:[c.jsx("h3",{className:"text-sm font-semibold text-white",children:"Funding by Sector"}),c.jsx("span",{className:"text-[11px] font-mono text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-md border border-blue-500/30",children:"API Calculated"})]}),c.jsx("div",{className:"py-6 flex items-center justify-center",children:c.jsx("div",{className:"relative w-36 h-36 rounded-full border-8 border-slate-800 flex items-center justify-center",children:c.jsxs("div",{className:"w-24 h-24 rounded-full border-4 border-blue-500 flex flex-col items-center justify-center bg-[#090d16] shadow-inner",children:[c.jsx("span",{className:"text-xl font-bold text-white",children:X.length}),c.jsx("span",{className:"text-[9px] uppercase tracking-wider text-slate-400",children:X.length===1?"Sector":"Sectors"})]})})}),c.jsx("div",{className:"space-y-2.5",children:X.length===0?c.jsx("p",{className:"text-xs text-center text-slate-500 py-3",children:"No active project sectors assigned yet."}):X.map(Z=>c.jsxs("div",{className:"space-y-1",children:[c.jsxs("div",{className:"flex justify-between text-xs",children:[c.jsxs("span",{className:"text-slate-300 font-medium truncate",children:[Z.name," (",Z.count,")"]}),c.jsxs("span",{className:"font-semibold text-white",children:[Z.percentage,"%"]})]}),c.jsx("div",{className:"w-full h-1.5 bg-slate-800 rounded-full overflow-hidden",children:c.jsx("div",{className:`h-full ${Z.color} rounded-full`,style:{width:`${Z.percentage}%`}})})]},Z.name))})]}),c.jsxs("div",{className:"pt-4 border-t border-slate-800/80 mt-4 text-[11px] text-slate-400 flex justify-between",children:[c.jsxs("span",{children:["Primary Focus: ",((Ce=X[0])==null?void 0:Ce.name)||"All Sectors"]}),c.jsxs(Qs,{to:"/governance",className:"text-blue-400 font-medium hover:underline flex items-center gap-1",children:["View Portfolios ",c.jsx(wd,{className:"w-3 h-3"})]})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 flex flex-col justify-between shadow-lg backdrop-blur-sm",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center justify-between border-b border-slate-800/80 pb-3",children:[c.jsx("h3",{className:"text-sm font-semibold text-white",children:"Portfolio Risk Heatmap"}),c.jsxs("span",{className:"text-[11px] font-mono text-rose-400 bg-rose-500/10 px-2 py-0.5 rounded-md border border-rose-500/30 font-semibold",children:[se.flaggedCount," Flagged"]})]}),c.jsx("p",{className:"text-[11px] text-slate-400 my-2",children:"Matrix distribution of project risks by Likelihood (1–5) and Impact (1–5)."}),c.jsxs("div",{className:"grid grid-cols-3 gap-2 my-4",children:[c.jsxs("div",{className:"bg-amber-950/40 text-amber-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-amber-800/40",children:[c.jsx("span",{className:"text-[10px] block text-amber-400 font-normal",children:"Moderate"}),se.highImpLowLik," ",se.highImpLowLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-rose-950/50 text-rose-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-rose-800/60",children:[c.jsx("span",{className:"text-[10px] block text-rose-400 font-normal",children:"High"}),se.highImpMedLik," ",se.highImpMedLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-rose-900/80 text-rose-100 p-2.5 rounded-xl text-center text-xs font-bold border border-rose-500 shadow-md",children:[c.jsx("span",{className:"text-[10px] block text-rose-300 font-normal",children:"Critical"}),se.highImpHighLik," ",se.highImpHighLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-emerald-950/40 text-emerald-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-emerald-800/40",children:[c.jsx("span",{className:"text-[10px] block text-emerald-400 font-normal",children:"Low"}),se.medImpLowLik," ",se.medImpLowLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-amber-950/40 text-amber-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-amber-800/40",children:[c.jsx("span",{className:"text-[10px] block text-amber-400 font-normal",children:"Moderate"}),se.medImpMedLik," ",se.medImpMedLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-rose-950/40 text-rose-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-rose-800/50",children:[c.jsx("span",{className:"text-[10px] block text-rose-400 font-normal",children:"High"}),se.medImpHighLik," ",se.medImpHighLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-slate-900/80 text-slate-300 p-2.5 rounded-xl text-center text-xs font-semibold border border-slate-800",children:[c.jsx("span",{className:"text-[10px] block text-slate-500 font-normal",children:"Trivial"}),se.lowImpLowLik," ",se.lowImpLowLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-emerald-950/40 text-emerald-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-emerald-800/40",children:[c.jsx("span",{className:"text-[10px] block text-emerald-400 font-normal",children:"Low"}),se.lowImpMedLik," ",se.lowImpMedLik===1?"Risk":"Risks"]}),c.jsxs("div",{className:"bg-amber-950/40 text-amber-200 p-2.5 rounded-xl text-center text-xs font-semibold border border-amber-800/40",children:[c.jsx("span",{className:"text-[10px] block text-amber-400 font-normal",children:"Moderate"}),se.lowImpHighLik," ",se.lowImpHighLik===1?"Risk":"Risks"]})]})]}),c.jsxs("div",{className:"pt-4 border-t border-slate-800/80 text-[11px] text-slate-400 flex justify-between items-center",children:[c.jsxs("span",{children:["Total Registered Risks: ",O.length]}),c.jsxs(Qs,{to:"/monitoring",className:"text-blue-400 font-medium hover:underline flex items-center gap-1",children:["Risk Register ",c.jsx(wd,{className:"w-3 h-3"})]})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 flex flex-col justify-between shadow-lg backdrop-blur-sm",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center justify-between border-b border-slate-800/80 pb-3",children:[c.jsx("h3",{className:"text-sm font-semibold text-white",children:"Disbursement Distribution"}),c.jsxs("span",{className:"text-[11px] font-mono text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/30 font-semibold",children:[C.length," Tranche",C.length===1?"":"s"]})]}),c.jsx("p",{className:"text-[11px] text-slate-400 my-2",children:"Quarterly release distribution across active portfolios and milestone verifications."}),c.jsx("div",{className:"h-44 flex items-end gap-3 pt-6 pb-2 px-2 border-b border-slate-800/80",children:fe.map(Z=>c.jsxs("div",{className:"flex-1 flex flex-col items-center gap-1.5 h-full justify-end",children:[c.jsx("span",{className:"text-[10px] font-mono text-slate-400",children:Z.quarter}),c.jsx("div",{className:"w-full bg-slate-800 rounded-t-md",style:{height:`${Math.max(10,Z.heightPct*.7)}%`},title:`Commitment: ${Ei(Z.amount)}`}),c.jsx("div",{className:"w-full bg-gradient-to-t from-blue-600 to-indigo-500 rounded-t-md shadow-xs shadow-blue-500/20",style:{height:`${Z.heightPct}%`},title:`Disbursed: ${Ei(Z.amount)}`})]},Z.quarter))}),c.jsxs("div",{className:"flex items-center justify-between text-[11px] text-slate-400 pt-3",children:[c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("span",{className:"h-2 w-3 bg-slate-800 rounded-xs"}),c.jsx("span",{children:"Commitment Allocation"})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("span",{className:"h-2 w-3 bg-blue-500 rounded-xs"}),c.jsx("span",{children:"Actual Disbursed"})]})]})]}),c.jsxs("div",{className:"pt-4 border-t border-slate-800/80 text-[11px] text-slate-400 flex justify-between items-center",children:[c.jsxs("span",{children:["Total Disbursed: ",Ei(q)]}),c.jsxs(Qs,{to:"/grants",className:"text-blue-400 font-medium hover:underline flex items-center gap-1",children:["Financials ",c.jsx(wd,{className:"w-3 h-3"})]})]})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 overflow-hidden shadow-lg backdrop-blur-sm",children:[c.jsxs("div",{className:"p-5 border-b border-slate-800/80 flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-[#0d121f]/90",children:[c.jsxs("div",{children:[c.jsx("h3",{className:"text-base font-semibold text-white",children:"Projects Requiring Attention"}),c.jsx("p",{className:"text-xs text-slate-400 mt-0.5",children:"Prioritized by risk severity, delivery delay, or financial variance threshold breach."})]}),c.jsx(Qs,{to:"/projects",className:"text-xs font-semibold text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-3.5 py-1.5 rounded-xl transition self-start sm:self-auto shadow-xs",children:"Open Project Directory →"})]}),c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-[#0d121f]/95 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold text-[11px]",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Project"}),c.jsx("th",{className:"py-3.5 px-4",children:"Programme"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-4 text-center",children:"Risk"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Financial Variance"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Action"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/60",children:we.length===0?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-xs text-slate-500",children:"No active projects currently flagged for executive attention. All projects are operating within target thresholds."})}):we.map(Z=>c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5",children:[c.jsx("span",{className:"font-semibold text-white block text-xs",children:Z.name}),c.jsxs("span",{className:"text-[10px] text-slate-400 font-mono",children:["ID: ",Z.id]})]}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300 font-medium",children:Z.programme}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${Z.status==="At Risk"?"bg-rose-500/10 text-rose-400 border-rose-500/30":Z.status==="Pending"?"bg-amber-500/10 text-amber-400 border-amber-500/30":"bg-emerald-500/10 text-emerald-400 border-emerald-500/30"}`,children:Z.status})}),c.jsx("td",{className:"py-3.5 px-4 text-center",children:c.jsx("span",{className:`inline-flex items-center justify-center font-bold px-2.5 py-0.5 rounded-md text-[10px] shadow-xs ${Z.risk==="RED"?"bg-rose-600 text-white":Z.risk==="AMBER"?"bg-amber-500 text-white":"bg-emerald-600 text-white"}`,children:Z.risk})}),c.jsx("td",{className:"py-3.5 px-4 text-right",children:c.jsx("span",{className:`font-mono font-bold text-xs ${Z.varianceType==="negative"?"text-rose-400":"text-emerald-400"}`,children:Z.financialVariance})}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:c.jsxs(Qs,{to:"/projects",className:"inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-3 py-1.5 rounded-lg transition shadow-xs",children:["Review",c.jsx(wd,{className:"w-3.5 h-3.5"})]})})]},Z.id))})]})})]})]})};var $c=e=>e.type==="checkbox",mf=e=>e.type==="file",zi=e=>e instanceof Date,cn=e=>e==null;const Jp=e=>typeof e=="object";var Pt=e=>!cn(e)&&!Array.isArray(e)&&Jp(e)&&!zi(e),Ck=e=>Pt(e)&&e.target?$c(e.target)?e.target.checked:mf(e.target)?e.target.files:e.target.value:e;const Ok=/[.[\]'"]/;var Wl=e=>e.split(Ok).filter(Boolean),Rk=(e,n)=>n.split(".").some((r,a,o)=>!isNaN(Number(r))&&e.has(o.slice(0,a).join("."))),pf=typeof window<"u"&&typeof window.HTMLElement<"u"&&typeof document<"u";function Ct(e){if(e===null||typeof e!="object")return e;if(e instanceof Date)return new Date(e);const n=typeof FileList<"u"&&e instanceof FileList;if(pf&&(e instanceof Blob||n))return e;const r=Array.isArray(e);if(!r&&e.constructor!==Object)return e;const a=r?[]:Object.create(Object.getPrototypeOf(e));for(const o in e)Object.prototype.hasOwnProperty.call(e,o)&&(a[o]=Ct(e[o]));return a}const kl={BLUR:"blur",FOCUS_OUT:"focusout",SUBMIT:"submit",TRIGGER:"trigger",VALID:"valid"},Ks={onBlur:"onBlur",onChange:"onChange",onSubmit:"onSubmit",onTouched:"onTouched",all:"all"},Fs={max:"max",min:"min",maxLength:"maxLength",minLength:"minLength",pattern:"pattern",required:"required",validate:"validate"},hN="root",Xp=["__proto__","constructor","prototype"],Tk=/^\w*$/;var Ia=e=>Tk.test(e),xt=e=>e===void 0,Se=(e,n,r)=>{if(!n||!Pt(e))return r;const a=Ia(n)?[n]:Wl(n);if(a.some(u=>Xp.includes(u)))return r;const o=a.reduce((u,f)=>cn(u)?void 0:u[f],e);return xt(o)||o===e?xt(e[n])?r:e[n]:o},pr=e=>typeof e=="boolean",Os=e=>typeof e=="function",gt=(e,n,r)=>{let a=-1;const o=Ia(n)?[n]:Wl(n),u=o.length,f=u-1;for(;++a{const o={};for(const u in e)Object.defineProperty(o,u,{get:()=>{const f=u;return n._proxyFormState[f]!==Ks.all&&(n._proxyFormState[f]=!a||Ks.all),e[f]}});return o};const Mk=pf?It.useLayoutEffect:It.useEffect;var zk=e=>{const n=e.constructor&&e.constructor.prototype;return Pt(n)&&n.hasOwnProperty("isPrototypeOf")},ip=e=>cn(e)||!Jp(e);const Xw=(e,n)=>n.length===0&&!Array.isArray(e)&&!zk(e);function Gs(e,n,r=new WeakMap){if(e===n)return!0;if(ip(e)||ip(n))return Object.is(e,n);if(zi(e)&&zi(n))return Object.is(e.getTime(),n.getTime());const a=Object.keys(e),o=Object.keys(n);if(a.length!==o.length)return!1;if(Xw(e,a)||Xw(n,o))return Object.is(e,n);if(!a.length&&Array.isArray(e)!==Array.isArray(n))return!1;const u=r.get(e);if(u&&u.has(n))return!0;if(u)u.add(n);else{const f=new WeakSet;f.add(n),r.set(e,f)}for(const f of a){const m=e[f];if(!(f in n))return!1;if(f!=="ref"){const p=n[f];if(zi(m)&&zi(p)||(Pt(m)||Array.isArray(m))&&(Pt(p)||Array.isArray(p))?!Gs(m,p,r):!Object.is(m,p))return!1}}return!0}function Uk(e){const n=It.useRef(!1),r=It.useRef(!1),a=It.useRef(void 0),o=It.useRef(0);o.current++,r.current;const u=It.useCallback((m,p,y)=>{if(m&&(n.current||r.current&&o.current>1)){const g=p();Gs(a.current,g)||y(g)}n.current=!0},[]),f=It.useCallback((m,p)=>{m&&(a.current=Ct(p()))},[]);return{resyncIfNeeded:u,snapshot:f}}var un=e=>typeof e=="string",Ik=(e,n,r,a,o)=>un(e)?(a&&n.watch.add(e),Se(r,e,o)):Array.isArray(e)?e.map(u=>(a&&n.watch.add(u),Se(r,u,Se(o,u)))):(a&&(n.watchAll=!0),r),Sd=e=>({isOnSubmit:!e||e===Ks.onSubmit,isOnBlur:e===Ks.onBlur,isOnChange:e===Ks.onChange,isOnAll:e===Ks.all,isOnTouch:e===Ks.onTouched}),v0=(e,n,r)=>{if(r)return!1;if(n.watchAll||n.watch.has(e))return!0;for(const a of n.watch)if(e.startsWith(a)&&e.charAt(a.length)===".")return!0;return!1};const vc=(e,n,r,a)=>{for(const o of r||Object.keys(e)){if(o==="_f")continue;const u=r?Se(e,o):e[o];if(u){const{_f:f}=u;if(f){if(f.refs&&f.refs[0]&&n(f.refs[0],o)&&!a)return!0;if(f.ref&&n(f.ref,f.name)&&!a)return!0;if(vc(u,n))break}else if((Pt(u)||Array.isArray(u))&&vc(u,n))break}}};var Ww=(e,n,r)=>{const a=Se(e,r),o=Array.isArray(a)?a:[];return gt(o,hN,n[r]),gt(e,r,o),e},wn=e=>Pt(e)&&!Object.keys(e).length,Kd=e=>{if(!pf)return!1;const n=e?e.ownerDocument:0;return e instanceof(n&&n.defaultView?n.defaultView.HTMLElement:HTMLElement)},Wp=e=>e.type==="radio",Yd=e=>e instanceof RegExp,eg=(e,n,r,a,o)=>n?{...r[e],types:{...r[e]&&r[e].types?r[e].types:{},[a]:o||!0}}:{};const e1={value:!1,isValid:!1},t1={value:!0,isValid:!0};var mN=e=>{if(!Array.isArray(e))return e1;if(e.length>1){const r=e.filter(a=>a&&a.checked&&!a.disabled).map(a=>a.value);return{value:r,isValid:!!r.length}}const n=e[0];return!n||!n.checked||n.disabled?e1:!n.attributes||!("value"in n.attributes)||xt(n.value)||n.value===""?t1:{value:n.value,isValid:!0}};const n1={isValid:!1,value:null};var pN=e=>Array.isArray(e)?e.reduce((n,r)=>r&&r.checked&&!r.disabled?{isValid:!0,value:r.value}:n,n1):n1;function s1(e,n,r="validate"){if(un(e)||Array.isArray(e)&&e.every(un)||pr(e)&&!e)return{type:r,message:un(e)?e:"",ref:n}}var Dl=e=>Pt(e)&&!Yd(e)?e:{value:e,message:""},r1=async(e,n,r,a,o,u)=>{const{ref:f,refs:m,required:p,maxLength:y,minLength:g,min:x,max:v,pattern:S,validate:A,name:C,valueAsNumber:O,mount:N}=e._f,j=Se(r,C);if(!N||n.has(C))return{};const F=m?m[0]:f,q=Z=>{if(o&&F.reportValidity){const W=pr(Z)?"":Z||"";m?m.forEach(oe=>oe.setCustomValidity(W)):F.setCustomValidity(W),F.reportValidity()}},B={},re=Wp(f),Q=$c(f),X=re||Q,se=(O||mf(f))&&xt(f.value)&&xt(j)||Kd(f)&&f.value===""||j===""||Array.isArray(j)&&!j.length,fe=eg.bind(null,C,a,B),we=(Z,W,oe,ee=Fs.maxLength,z=Fs.minLength)=>{const ue=Z?W:oe;B[C]={type:Z?ee:z,message:ue,ref:f,...fe(Z?ee:z,ue)}};if(u?!Array.isArray(j)||!j.length:p&&(!X&&(se||cn(j))||pr(j)&&!j||Q&&!mN(m).isValid||re&&!pN(m).isValid)){const{value:Z,message:W}=un(p)?{value:!!p,message:p}:Dl(p);if(Z&&(B[C]={type:Fs.required,message:W,ref:F,...fe(Fs.required,W)},!a))return q(W),B}if(!se&&(!cn(x)||!cn(v))){let Z,W;const oe=Dl(v),ee=Dl(x);if(!cn(j)&&!zi(j)&&!isNaN(j)){const z=f.valueAsNumber||j&&+j;cn(oe.value)||(Z=z>oe.value),cn(ee.value)||(W=znew Date(new Date().toDateString()+" "+Ye),Me=f.type=="time",Ae=f.type=="week";un(oe.value)&&j&&(Z=Me?ue(j)>ue(oe.value):Ae?j>oe.value:z>new Date(oe.value)),un(ee.value)&&j&&(W=Me?ue(j)+Z.value,ee=!cn(W.value)&&j.length<+W.value;if((oe||ee)&&(we(oe,Z.message,W.message),!a))return q(B[C].message),B}if(S&&!se&&un(j)){const{value:Z,message:W}=Dl(S);if(Yd(Z)&&!j.match(Z)&&(B[C]={type:Fs.pattern,message:W,ref:f,...fe(Fs.pattern,W)},!a))return q(W),B}if(A){if(Os(A)){const Z=await A(j,r),W=s1(Z,F);if(W&&(B[C]={...W,...fe(Fs.validate,W.message)},!a))return q(W.message),B}else if(Pt(A)){let Z={};for(const W in A){if(!wn(Z)&&!a)break;const oe=s1(await A[W](j,r),F,W);oe&&(Z={...oe,...fe(W,oe.message)},q(oe.message),a&&(B[C]=Z))}if(!wn(Z)&&(B[C]={ref:F,...Z},!a))return B}}const Ce=B[C];return q(Ce?Ce.message:!0),B},Hd=e=>Array.isArray(e)?e:[e],gN=e=>Array.isArray(e)?e.filter(Boolean):[];function Lk(e,n){const r=n.length-1;let a=0;for(;aXp.includes(String(f))))return e;const a=r.length===1?e:Lk(e,r),o=r.length-1,u=r[o];return a&&delete a[u],o!==0&&(Pt(a)&&wn(a)||Array.isArray(a)&&Pk(a))&&$t(e,r.slice(0,-1)),e}const Hk=It.createContext(null);Hk.displayName="HookFormContext";var i1=()=>{let e=[];return{get observers(){return e},next:o=>{for(const u of e)u.next&&u.next(o)},subscribe:o=>(e.push(o),{unsubscribe:()=>{e=e.filter(u=>u!==o)}}),unsubscribe:()=>{e=[]}}};function xN(e,n){const r={};for(const a in e)if(e.hasOwnProperty(a)){const o=e[a],u=n[a];if(o&&Pt(o)&&u){const f=xN(o,u);Pt(f)&&(r[a]=f)}else e[a]&&(r[a]=u)}return r}const a1=(e,n)=>e!==null&&Jp(e)&&Object.prototype.hasOwnProperty.call(e,n);var qk=(e,n)=>{if(!n)return!1;let r=e;for(const a of Ia(n)?[n]:Wl(n)){if(!a1(r,a))return a1(e,n);r=r[a]}return!0},bN=e=>e.type==="select-multiple",Bk=e=>Wp(e)||$c(e),w0=e=>Kd(e)&&e.isConnected;function l1(e){return Array.isArray(e)||Pt(e)}function yN(e,n,r="",a=[]){for(const o in e){const u=r?`${r}.${o}`:o,f=e[o];l1(f)&&l1(Se(n,u))?yN(f,n,u,a):a.push(u)}return a}var $k=e=>{for(const n in e)if(Os(e[n]))return!0;return!1};function vN(e){return Array.isArray(e)||Pt(e)&&!$k(e)}function wN(e){return!!(e&&"_f"in e)}function _N(e){return Array.isArray(e)?!e.some(n=>!xt(n)):!Object.keys(e).length}function ap(e,n){Array.isArray(e)?e[n]=void 0:delete e[n]}function lp(e,n={},r){for(const a in e){const o=e[a],u=r&&r[a];vN(o)&&(!Array.isArray(o)||!wN(u))?(n[a]=Array.isArray(o)?[]:{},lp(o,n[a],u),_N(n[a])&&ap(n,a)):xt(o)||(n[a]=!0)}return n}function ha(e,n,r,a){r||(r=lp(n,{},a));for(const o in e){const u=e[o],f=a&&a[o];vN(u)&&(!Array.isArray(u)||!wN(f))?(xt(n)||ip(r[o])?r[o]=lp(u,Array.isArray(u)?[]:{},f):ha(u,cn(n)?{}:n[o],r[o],f),_N(r[o])&&ap(r,o)):Gs(u,n[o])?ap(r,o):r[o]=!0}return r}var Fk=(e,n)=>{const r=n.split("."),a=[];let o=r[0];for(let u=1;uxt(e)?e:n?e===""?NaN:e&&+e:r&&un(e)?new Date(e):a?a(e):e;function o1(e){const n=e.ref;return mf(n)?n.files:Wp(n)?pN(e.refs).value:bN(n)?[...n.selectedOptions].map(({value:r})=>r):$c(n)?mN(e.refs).value:SN(n.value,e)}var Vk=(e,n,r,a)=>{const o={};for(const u of e){const f=Se(n,u);f&>(o,u,f._f)}return{criteriaMode:r,names:[...e],fields:o,shouldUseNativeValidation:a}},fc=e=>xt(e)?e:Yd(e)?e.source:Pt(e)?Yd(e.value)?e.value.source:e.value:e;const c1="AsyncFunction";var Zk=e=>{if(!e||!e.validate)return!1;if(Os(e.validate))return e.validate.constructor.name===c1;if(Pt(e.validate)){for(const n in e.validate)if(e.validate[n].constructor.name===c1)return!0}return!1},Gk=e=>e.mount&&(e.required||!xt(e.required)&&e.required!==!1||!xt(e.min)||!xt(e.max)||!xt(e.maxLength)||!xt(e.minLength)||e.pattern||e.validate);function u1(e,n,r){const a=Se(e,r);if(a||Ia(r))return{error:a,name:r};const o=r.split(".");for(;o.length;){const u=o.join("."),f=Se(n,u),m=Se(e,u);if(f&&!Array.isArray(f)&&r!==u)return{name:r};if(m&&m.type)return{name:u,error:m};if(m&&m.root&&m.root.type)return{name:`${u}.root`,error:m.root};o.pop()}return{name:r}}var Qk=(e,n,r,a)=>{r(e);const o=Object.keys(e).filter(u=>u!=="name");return!o.length||a&&o.length>=Object.keys(n).length||o.find(u=>n[u]===(!a||Ks.all))},Kk=(e,n,r)=>!e||!n||e===n||Hd(e).some(a=>a&&(r?a===n||a.startsWith(n+"."):a.startsWith(n)||n.startsWith(a))),Yk=(e,n,r,a,o)=>o.isOnAll?!1:!r&&o.isOnTouch?!(n||e):(r?a.isOnBlur:o.isOnBlur)?!e:(r?a.isOnChange:o.isOnChange)?e:!0,Jk=(e,n)=>{const r=Se(e,n);!gN(r).length&&!(r!=null&&r.root)&&$t(e,n)};const Xk={mode:Ks.onSubmit,reValidateMode:Ks.onChange,shouldFocusError:!0},_0="form",Wk=(e,n)=>{for(const r in e)r in n||delete e[r];Object.assign(e,n)},NN={submitCount:0,isDirty:!1,isReady:!1,isValidating:!1,isSubmitted:!1,isSubmitting:!1,isSubmitSuccessful:!1,isValid:!1,touchedFields:{},dirtyFields:{},validatingFields:{}};function e8(e={}){let n={...Xk,...e},r={...Ct(NN),isLoading:Os(n.defaultValues),errors:n.errors||{},disabled:n.disabled||!1},a={},o=Pt(n.defaultValues)||Pt(n.values)?Ct(n.defaultValues||n.values)||{}:{},u=n.shouldUnregister?{}:Ct(o),f={action:!1,actionArrayLengths:new Map,mount:!1,watch:!1,keepIsValid:!1},m={mount:new Set,disabled:new Set,unMount:new Set,array:new Set,watch:new Set,registerName:new Set};const p={},y={};let g=0,x=Sd(n.mode),v=Sd(n.reValidateMode);const S={isDirty:!1,dirtyFields:!1,validatingFields:!1,touchedFields:!1,isValidating:!1,isValid:!1,errors:!1},A={...S};let C={...A};const O={array:i1(),state:i1()};let N=0;const j=n.criteriaMode===Ks.all,F=(E,H)=>I=>{clearTimeout(y[E]),y[E]=setTimeout(H,I)},q=async E=>{if(!f.keepIsValid&&!n.disabled&&(A.isValid||C.isValid||E)){const H=++N;let I;n.resolver?(I=wn((await oe()).errors),H===N&&B()):I=await ue({fields:a,onlyCheckValid:!0,eventType:kl.VALID}),H===N&&I!==r.isValid&&O.state.next({isValid:I})}},B=(E,H)=>{!n.disabled&&(A.isValidating||A.validatingFields||C.isValidating||C.validatingFields)&&((E||m.mount).forEach(I=>{I&&(H?gt(r.validatingFields,I,H):$t(r.validatingFields,I))}),O.state.next({validatingFields:r.validatingFields,isValidating:!wn(r.validatingFields)}))},re=()=>{r.dirtyFields=ha(o,u,void 0,a)},Q=(E,H=[],I,le,de=!0,xe=!0)=>{if(le&&I&&!n.disabled){f.action=!0;const me=Se(a,E);if(f.actionArrayLengths.has(E)||f.actionArrayLengths.set(E,Array.isArray(me)?me.length:0),xe&&Array.isArray(me)){const Ue=I(me,le.argA,le.argB);de&>(a,E,Ue)}const be=Se(r.errors,E);if(xe&&Array.isArray(be)){const Ue=be.root,vt=I(be,le.argA,le.argB)||be;Ue&&(vt.root=Ue),de&>(r.errors,E,vt),Jk(r.errors,E)}const Te=Se(r.touchedFields,E);if((A.touchedFields||C.touchedFields)&&xe&&Array.isArray(Te)){const Ue=I(Te,le.argA,le.argB);de&>(r.touchedFields,E,Ue)}(A.dirtyFields||C.dirtyFields)&&re(),O.state.next({name:E,isDirty:Ae(E,H),dirtyFields:r.dirtyFields,errors:r.errors,isValid:r.isValid})}else gt(u,E,H)},X=(E,H)=>{gt(r.errors,E,H),r.errors={...r.errors},O.state.next({errors:r.errors})},se=E=>{r.errors=E,O.state.next({errors:r.errors,isValid:!1})},fe=E=>{const H=Ia(E)?[E]:Wl(E);let I=u,le=o;for(let de=0;de{if(!f.actionArrayLengths.size)return!1;const H=Ia(E)?[E]:Wl(E);let I=u,le="",de=-1,xe=0;for(let me=0;me=I.length)return de===-1?!1:me===de?+be{const de=Se(a,E);if(de){if(fe(E)||we(E))return;const xe=xt(Se(u,E)),me=Se(u,E,xt(I)?Se(o,E):I);xt(me)||le&&le.defaultChecked||H?gt(u,E,H?me:o1(de._f)):ut(E,me),f.mount&&!f.action&&(q(),xe&&r.isDirty&&(A.isDirty||C.isDirty)&&(Ae()||(r.isDirty=!1,O.state.next({...r}))),e.shouldUnregister&&xe&&!xt(Se(u,E))&&v0(E,m)&&(f.watch=!0))}},Z=(E,H,I,le,de)=>{let xe=!1,me=!1;const be={name:E};if(!n.disabled||le===!0){if(!I||le){const Te=Gs(Se(o,E),H);(A.isDirty||C.isDirty)&&(me=r.isDirty,r.isDirty=be.isDirty=!Te||Ae(),xe=me!==be.isDirty),me=!!Se(r.dirtyFields,E),Te!==r.isDirty?Wk(r.dirtyFields,ha(o,u,void 0,a)):Te?$t(r.dirtyFields,E):gt(r.dirtyFields,E,!0),be.dirtyFields=r.dirtyFields,xe=xe||(A.dirtyFields||C.dirtyFields)&&me!==!Te}if(I){const Te=Se(r.touchedFields,E);Te||(gt(r.touchedFields,E,I),be.touchedFields=r.touchedFields,xe=xe||(A.touchedFields||C.touchedFields)&&Te!==I)}xe&&de&&O.state.next(be)}return xe?be:{}},W=(E,H,I,le)=>{const de=Se(r.errors,E),xe=(A.isValid||C.isValid)&&pr(H)&&r.isValid!==H;if(n.delayError&&I?(p[E]=F(E,()=>X(E,I)),p[E](n.delayError)):(clearTimeout(y[E]),delete p[E],I?gt(r.errors,E,I):$t(r.errors,E),r.errors={...r.errors}),(I?!Gs(de,I):de)||!wn(le)||xe){const me={...le,...xe&&pr(H)?{isValid:H}:{},errors:r.errors,name:E};O.state.next(me)}},oe=async E=>(B(E,!0),await n.resolver(u,n.context,Vk(E||m.mount,a,n.criteriaMode,n.shouldUseNativeValidation))),ee=async E=>{const{errors:H}=await oe(E);if(B(E),E){for(const I of E){const le=Se(H,I);le?m.array.has(I)&&Pt(le)&&!Object.keys(le).some(de=>!Number.isNaN(Number(de)))?Ww(r.errors,{[I]:le},I):gt(r.errors,I,le):$t(r.errors,I)}r.errors={...r.errors}}else r.errors=H;return H},z=async({name:E,eventType:H})=>{if(e.validate){const I=await e.validate({formValues:u,formState:r,name:E,eventType:H});if(Pt(I))for(const le in I){const de=I[le];de&&Zt(`${_0}.${le}`,{message:un(de.message)?de.message:"",type:de.type||Fs.validate})}else un(I)||!I?Zt(_0,{message:I||"",type:Fs.validate}):Tt(_0);return I}return!0},ue=async({fields:E,onlyCheckValid:H,name:I,eventType:le,context:de={valid:!0,runRootValidation:!1}})=>{if(e.validate&&(de.runRootValidation=!0,!await z({name:I,eventType:le})&&(de.valid=!1,H)))return de.valid;for(const xe in E){const me=E[xe];if(me){const{_f:be,...Te}=me;if(be){const Ue=m.array.has(be.name),vt=me._f&&Zk(me._f),Rn=A.validatingFields||A.isValidating||C.validatingFields||C.isValidating;vt&&Rn&&B([be.name],!0);const Bn=await r1(me,m.disabled,u,j,n.shouldUseNativeValidation&&!H,Ue);if(vt&&Rn&&B([be.name]),Bn[be.name]&&(de.valid=!1,H)||(!H&&(Se(Bn,be.name)?Ue?Ww(r.errors,Bn,be.name):gt(r.errors,be.name,Bn[be.name]):$t(r.errors,be.name)),e.shouldUseNativeValidation&&Bn[be.name]))break}!wn(Te)&&await ue({context:de,onlyCheckValid:H,fields:Te,name:xe,eventType:le})}}return de.valid},Me=()=>{for(const E of m.unMount){const H=Se(a,E);H&&(H._f.refs?H._f.refs.every(I=>!w0(I)):!w0(H._f.ref))&&$(E)}m.unMount=new Set},Ae=(E,H)=>(E&&H&>(u,E,H),!Gs(f.mount?u:o,o)),Ye=(E,H,I)=>Ik(E,m,{...f.mount?u:xt(H)||un(E)?o:H},I,H),$e=E=>gN(Se(f.mount?u:o,E,n.shouldUnregister?Se(o,E,[]):[])),ut=(E,H,I={},le=!1,de=!1,xe=!1)=>{const me=Se(a,E);let be=H;if(me){const Te=me._f;Te&&(!Te.disabled&>(u,E,SN(H,Te)),be=Kd(Te.ref)&&cn(H)?"":H,bN(Te.ref)?[...Te.ref.options].forEach(Ue=>Ue.selected=be.includes(Ue.value)):Te.refs?$c(Te.ref)?Te.refs.forEach(Ue=>{(!Ue.defaultChecked||!Ue.disabled)&&(Array.isArray(be)?Ue.checked=!!be.find(vt=>vt===Ue.value):Ue.checked=be===Ue.value||!!be)}):Te.refs.forEach(Ue=>Ue.checked=Ue.value===be):mf(Te.ref)?Te.ref.value="":(Te.ref.value=be,!Te.ref.type&&!de&&!xe&&O.state.next({name:E,values:le?u:Ct(u)})))}(I.shouldDirty||I.shouldTouch)&&Z(E,be,I.shouldTouch,I.shouldDirty,!de),I.shouldValidate&&qe(E,{delayError:I.delayError})},_=(E,H,I,le=!1,de=!1,xe=!1)=>{m.array.has(E)&&O.array.next({name:E,values:le?u:Ct(u)});for(const me in H){if(!H.hasOwnProperty(me))continue;const be=H[me],Te=E+"."+me,Ue=Se(a,Te);(m.array.has(E)||Pt(be)||Ue&&!Ue._f)&&!zi(be)?_(Te,be,I,le,de,xe):ut(Te,be,I,le,de,xe)}},Y=(E,H,I,le,de=!1)=>{const xe=Se(a,E),me=m.array.has(E),be=le?H:Ct(H),Te=Se(u,E),Ue=Gs(Te,be);if(Ue||gt(u,E,be),me)O.array.next({name:E,values:le?u:Ct(u)}),(A.isDirty||A.dirtyFields||C.isDirty||C.dirtyFields)&&I.shouldDirty&&(re(),de||O.state.next({name:E,dirtyFields:r.dirtyFields,isDirty:Ae(E,be)}));else{const vt=Array.isArray(be)&&!be.length||wn(be),Rn=!Ue&&!de;!xe||xe._f||cn(be)||vt?ut(E,be,I,le,de,Rn):_(E,be,I,le,de,Rn)}if(!Ue&&!de){const vt=v0(E,m),Rn=le?u:Ct(u);if(O.state.next({...vt&&r,name:f.mount||vt?E:void 0,values:Rn}),!me)for(const Bn of Fk(m.array,E))O.state.next({name:Bn,values:Rn})}},he=(E,H,I={})=>Y(E,H,I,!1),ce=(E,H={})=>{const I=Os(E)?E(u):E;if(!Gs(u,I)){u={...u,...I};for(const le of m.mount)qk(I,le)&&Y(le,Se(I,le),H,!0,!0);O.state.next({...r,name:void 0,type:void 0,...g?{values:u}:{}}),H.shouldValidate&&q()}},Oe=async E=>{f.mount=!0;const H=E.target;let I=H.name,le=!0;const de=Se(a,I),xe=me=>{le=Number.isNaN(me)||zi(me)&&isNaN(me.getTime())||Gs(me,Se(u,I,me))};if(de){let me,be;const Te=H.type?o1(de._f):Ck(E),Ue=E.type===kl.BLUR||E.type===kl.FOCUS_OUT,vt=!Gk(de._f)&&!e.validate&&!n.resolver&&!Se(r.errors,I)&&!de._f.deps,Rn=vt||Yk(Ue,Se(r.touchedFields,I),r.isSubmitted,v,x),Bn=v0(I,m,Ue);if(gt(u,I,Ct(Te)),Ue){if(!H||!H.readOnly){de._f.onBlur&&de._f.onBlur(E);const Ds=p[I];Ds&&Ds(0)}}else de._f.onChange&&de._f.onChange(E);const $i=Z(I,Te,Ue),vf=!wn($i)||Bn;if(!Ue&&O.state.next({name:I,type:E.type,...g?{values:Ct(u)}:{}}),Rn)return(!vt||!r.isValid)&&(A.isValid||C.isValid)&&(n.mode==="onBlur"?Ue&&q():Ue||q()),vf&&O.state.next({name:I,...Bn?{}:$i});if(!n.resolver&&e.validate&&await z({name:I,eventType:E.type}),!Ue&&Bn&&O.state.next({...r}),n.resolver){const{errors:Ds}=await oe([I]);if(B([I]),xe(Te),!le){!wn($i)&&O.state.next($i);return}const Tn=u1(r.errors,a,I),Ms=u1(Ds,a,Tn.name||I);me=Ms.error,I=Ms.name,be=wn(Ds)}else B([I],!0),me=(await r1(de,m.disabled,u,j,n.shouldUseNativeValidation))[I],B([I]),xe(Te),le&&(me?be=!1:(A.isValid||C.isValid)&&(be=await ue({fields:a,onlyCheckValid:!0,name:I,eventType:E.type})));le&&(de._f.deps&&(!Array.isArray(de._f.deps)||de._f.deps.length>0)&&qe(de._f.deps),W(I,be,me,$i))}},Pe=(E,H)=>{if(Se(r.errors,H)&&E.focus)return E.focus(),1},qe=async(E,H={})=>{let I,le;const de=Hd(E);if(n.resolver){const xe=await ee(xt(E)?E:de);I=wn(xe),le=E?!de.some(me=>Se(xe,me)):I}else E?(le=(await Promise.all(de.map(async xe=>{const me=Se(a,xe);return await ue({fields:me&&me._f?{[xe]:me}:me,eventType:kl.TRIGGER})}))).every(Boolean),!(!le&&!r.isValid)&&q()):le=I=await ue({fields:a,name:E,eventType:kl.TRIGGER});if(H.delayError&&n.delayError&&un(E)){const xe=Se(r.errors,E);xe?($t(r.errors,E),p[E]=F(E,()=>X(E,xe)),p[E](n.delayError)):(clearTimeout(y[E]),delete p[E])}if(H.shouldTouch)for(const xe of E?de:m.mount)!m.array.has(xe)&>(r.touchedFields,xe,!0);return O.state.next({...!un(E)||(A.isValid||C.isValid)&&I!==r.isValid?{}:{name:E},...n.resolver||!E?{isValid:I}:{},...H.shouldTouch&&(A.touchedFields||C.touchedFields)?{touchedFields:r.touchedFields}:{},errors:r.errors}),H.shouldFocus&&!le&&vc(a,Pe,E?de:m.mount),le},_e=(E,H)=>{let I={...f.mount?u:o};return H&&(I=xN(H.dirtyFields?r.dirtyFields:r.touchedFields,I)),xt(E)?I:un(E)?Se(I,E):E.map(le=>Se(I,le))},je=E=>xt(E)?{...r.errors}:un(E)?Se(r.errors,E):E.map(H=>Se(r.errors,H)),at=(E,H)=>{const I=H||r,le=Se(I.errors,E);return{invalid:!!le,isDirty:!!Se(I.dirtyFields,E),error:le,isValidating:!!Se(r.validatingFields,E),isTouched:!!Se(I.touchedFields,E)}},Tt=E=>{const H=E?Hd(E):void 0;H==null||H.forEach(I=>$t(r.errors,I)),H?H.forEach(I=>{O.state.next({name:I,errors:r.errors})}):(r.errors={},O.state.next({errors:r.errors}))},Zt=(E,H,I)=>{const le=(Se(a,E,{_f:{}})._f||{}).ref,de=Se(r.errors,E)||{},{ref:xe,message:me,type:be,...Te}=de;gt(r.errors,E,{...Te,...H,ref:le}),O.state.next({name:E,errors:r.errors,isValid:!1}),I&&I.shouldFocus&&le&&le.focus&&le.focus()},J=(E,H)=>{if(Os(E)){g++;const{unsubscribe:I}=O.state.subscribe({next:de=>"values"in de&&E(de.values||Ye(void 0,H),de)});let le=!1;return{unsubscribe:()=>{le||(le=!0,g--,I())}}}return Ye(E,H,!0)},Re=E=>{var H;const I=!!(!((H=E.formState)===null||H===void 0)&&H.values);I&&g++;const{unsubscribe:le}=O.state.subscribe({next:xe=>{if(Kk(E.name,xe.name,E.exact)&&Qk(xe,E.formState||A,ae,E.reRenderRoot)){const me={...u};E.callback({values:me,...r,...xe,defaultValues:o})}}});if(!I)return le;let de=!1;return()=>{de||(de=!0,g--,le())}},pe=E=>(f.mount=!0,C={...C,...E.formState},Re({...E,formState:{...S,...E.formState}})),$=(E,H={})=>{for(const I of E?Hd(E):m.mount)m.mount.delete(I),m.array.delete(I),H.keepValue||($t(a,I),$t(u,I)),!H.keepError&&$t(r.errors,I),!H.keepDirty&&$t(r.dirtyFields,I),!H.keepTouched&&$t(r.touchedFields,I),!H.keepIsValidating&&$t(r.validatingFields,I),!n.shouldUnregister&&!H.keepDefaultValue&&$t(o,I);g&&O.state.next({values:Ct(u)}),O.state.next({...r,...H.keepDirty?{}:{isDirty:Ae()}}),!H.keepIsValid&&q()},Ge=({disabled:E,name:H})=>{if(pr(E)&&f.mount||E||m.disabled.has(H)){const de=m.disabled.has(H)!==!!E;E?m.disabled.add(H):m.disabled.delete(H),de&&f.mount&&!f.action&&q()}},rt=(E,H={})=>{let I=Se(a,E);const le=pr(H.disabled)||pr(n.disabled),de=!m.registerName.has(E)&&I&&I._f&&!I._f.mount;return gt(a,E,{...I||{},_f:{...I&&I._f?I._f:{ref:{name:E}},name:E,mount:!0,...H}}),m.mount.add(E),I&&!de?Ge({disabled:pr(H.disabled)?H.disabled:n.disabled,name:E}):Ce(E,!0,H.value),{...le?{disabled:H.disabled||n.disabled}:{},...n.progressive?{required:!!H.required,min:fc(H.min),max:fc(H.max),minLength:fc(H.minLength),maxLength:fc(H.maxLength),pattern:fc(H.pattern)}:{},name:E,onChange:Oe,onBlur:Oe,ref:xe=>{if(xe){m.registerName.add(E),rt(E,H),m.registerName.delete(E),I=Se(a,E);const me=xt(xe.value)&&xe.querySelectorAll&&xe.querySelectorAll("input,select,textarea")[0]||xe,be=Bk(me),Te=I._f.refs||[];if(be?Te.find(vt=>vt===me):me===I._f.ref)return;const Ue={...I._f};be?(Ue.refs=[...Te.filter(w0),me,...Array.isArray(Se(o,E))?[{}]:[]],Ue.ref={type:me.type,name:E}):(Ue.ref=me,delete Ue.refs),gt(a,E,{_f:Ue}),Ce(E,!1,void 0,me)}else I=Se(a,E,{}),I._f&&(I._f.mount=!1),(n.shouldUnregister||H.shouldUnregister)&&!(Rk(m.array,E)&&f.action)&&m.unMount.add(E)}}},qt=()=>n.shouldFocusError&&!n.shouldUseNativeValidation&&vc(a,Pe,m.mount),Sr=E=>{pr(E)&&(O.state.next({disabled:E}),vc(a,(H,I)=>{const le=Se(a,I);le&&(H.disabled=le._f.disabled||E,Array.isArray(le._f.refs)&&le._f.refs.forEach(de=>{de.disabled=le._f.disabled||E}))},0,!1))},Sn=(E,H)=>async I=>{let le,de;I&&(I.preventDefault&&I.preventDefault(),I.persist&&I.persist());let xe=Ct(u);if(O.state.next({isSubmitting:!0}),n.resolver){const{errors:me,values:be}=await oe();B(),r.errors=me,xe=Ct(be)}else await ue({fields:a,eventType:kl.SUBMIT});if(m.disabled.size)for(const me of m.disabled)$t(xe,me);if($t(r.errors,hN),wn(r.errors)){O.state.next({errors:{}});try{le=await E(xe,I)}catch(me){de=me}}else H&&await H({...r.errors},I),qt(),setTimeout(qt);if(O.state.next({isSubmitted:!0,isSubmitting:!1,isSubmitSuccessful:wn(r.errors)&&!de,submitCount:r.submitCount+1,errors:r.errors}),de)throw de;return le},Hi=(E,H={})=>{Se(a,E)&&(xt(H.defaultValue)?he(E,Ct(Se(o,E))):(he(E,H.defaultValue),gt(o,E,Ct(H.defaultValue))),H.keepTouched||$t(r.touchedFields,E),H.keepDirty||($t(r.dirtyFields,E),r.isDirty=H.defaultValue?Ae(E,Ct(Se(o,E))):Ae()),H.keepError||($t(r.errors,E),q()),O.state.next({...r}))},Kr=(E,H={})=>{const I=E?Ct(E):o,le=Ct(I),de=wn(E),xe=le,me=a;if(H.keepDefaultValues||(o=I),!H.keepValues){if(H.keepDirtyValues){const be=new Set([...m.mount,...yN(ha(o,u,void 0,me),r.dirtyFields)]);for(const Te of be){const Ue=Se(r.dirtyFields,Te),vt=Se(u,Te),Rn=Se(xe,Te);Ue&&!xt(vt)?gt(xe,Te,vt):!Ue&&!xt(Rn)&&he(Te,Rn)}}else{if(pf&&xt(E))for(const be of m.mount){const Te=Se(a,be);if(Te&&Te._f){const Ue=Array.isArray(Te._f.refs)?Te._f.refs[0]:Te._f.ref;if(Kd(Ue)){const vt=Ue.closest("form");if(vt){vt.reset();break}}}}if(H.keepFieldsRef)for(const be of m.mount)he(be,Se(xe,be));else a={}}if(n.shouldUnregister){if(u=H.keepDefaultValues?Ct(o):{},H.keepFieldsRef)for(const be of m.mount)gt(u,be,Se(xe,be))}else u=Ct(xe);O.array.next({values:{...xe}}),O.state.next({name:void 0,type:void 0,values:{...xe}})}m={mount:H.keepDirtyValues?m.mount:new Set,unMount:new Set,array:new Set,registerName:new Set,disabled:new Set,watch:new Set,watchAll:!1,focus:""},f.mount=!A.isValid||!!H.keepIsValid||!!H.keepDirtyValues||!n.shouldUnregister&&!wn(xe),f.watch=!!n.shouldUnregister,f.keepIsValid=!!H.keepIsValid,f.action=!1,f.actionArrayLengths.clear(),H.keepErrors||(r.errors={}),O.state.next({submitCount:H.keepSubmitCount?r.submitCount:0,isDirty:de?!1:H.keepDirty?r.isDirty:H.keepValues?Ae():!!(H.keepDefaultValues&&!Gs(E,o)),isSubmitted:H.keepIsSubmitted?r.isSubmitted:!1,dirtyFields:de?{}:H.keepDirtyValues?H.keepDefaultValues&&u?ha(o,u,void 0,me):r.dirtyFields:H.keepDefaultValues&&E?ha(o,E,void 0,me):H.keepDirty?r.dirtyFields:{},touchedFields:H.keepTouched?r.touchedFields:{},errors:H.keepErrors?r.errors:{},isSubmitSuccessful:H.keepIsSubmitSuccessful?r.isSubmitSuccessful:!1,isSubmitting:!1,defaultValues:o})},er=(E,H)=>Kr(Os(E)?E(u):E,{...n.resetOptions,...H}),qa=(E,H={})=>{const I=Se(a,E),le=I&&I._f;if(le){const de=le.refs?le.refs[0]:le.ref;de.focus&&setTimeout(()=>{de.focus(),H.shouldSelect&&Os(de.select)&&de.select()})}},ae=E=>{const{name:H,type:I,values:le,...de}=E;r={...r,...de}};O.state.subscribe({next:ae});const Bi={control:{register:rt,unregister:$,getFieldState:at,handleSubmit:Sn,setError:Zt,_subscribe:Re,_runSchema:oe,_updateIsValidating:B,_focusError:qt,_getWatch:Ye,_getDirty:Ae,_setValid:q,_setFieldArray:Q,_setDisabledField:Ge,_setErrors:se,_getFieldArray:$e,_reset:Kr,_resetDefaultValues:()=>Os(n.defaultValues)&&n.defaultValues().then(E=>{er(E,n.resetOptions),O.state.next({isLoading:!1})}),_removeUnmounted:Me,_disableForm:Sr,_subjects:O,_proxyFormState:A,get _fields(){return a},get _formValues(){return u},get _state(){return f},set _state(E){f=E},get _defaultValues(){return o},get _names(){return m},set _names(E){m=E},get _formState(){return r},get _options(){return n},set _options(E){n={...n,...E},x=Sd(n.mode),v=Sd(n.reValidateMode)}},subscribe:pe,trigger:qe,register:rt,handleSubmit:Sn,watch:J,setValue:he,setValues:ce,getValues:_e,getErrors:je,reset:er,resetField:Hi,resetDefaultValues:(E,H={})=>{if(o=Ct(E),!H.keepDirty){const I=ha(o,u,void 0,a);r.dirtyFields=I,r.isDirty=!wn(I)}H.keepIsValid||q(),O.state.next({...r,defaultValues:o})},clearErrors:Tt,unregister:$,setError:Zt,setFocus:qa,getFieldState:at};return{...Bi,formControl:Bi}}function hs(e={}){const n=It.useRef(void 0),r=It.useRef(void 0),a=It.useRef(e.formControl),[o,u]=It.useState(()=>({...Ct(NN),isLoading:Os(e.defaultValues),errors:e.errors||{},disabled:e.disabled||!1,defaultValues:Os(e.defaultValues)?void 0:e.defaultValues}));if(!n.current||e.formControl&&a.current!==e.formControl)if(a.current=e.formControl,e.formControl)n.current={...e.formControl,formState:o},e.defaultValues&&!Os(e.defaultValues)&&e.formControl.reset(e.defaultValues,e.resetOptions);else{const{formControl:y,...g}=e8(e);n.current={...g,formState:o}}const f=n.current.control;f._options=e;const{resyncIfNeeded:m,snapshot:p}=Uk();return Mk(()=>{const y=()=>({...f._formState,defaultValues:f._defaultValues});m(!0,y,u);const g=f._subscribe({formState:f._proxyFormState,callback:()=>u({...f._formState,defaultValues:f._defaultValues}),reRenderRoot:!0});return u(x=>({...x,isReady:!0})),f._formState.isReady=!0,()=>{g(),p(!0,y)}},[f,m,p]),It.useEffect(()=>f._disableForm(e.disabled),[f,e.disabled]),It.useEffect(()=>{e.mode&&(f._options.mode=e.mode),e.reValidateMode&&(f._options.reValidateMode=e.reValidateMode)},[f,e.mode,e.reValidateMode]),It.useEffect(()=>{e.errors&&(f._setErrors(e.errors),f._focusError())},[f,e.errors]),It.useEffect(()=>{e.shouldUnregister&&f._subjects.state.next({values:f._getWatch()})},[f,e.shouldUnregister]),It.useEffect(()=>{if(f._proxyFormState.isDirty){const y=f._getDirty();y!==o.isDirty&&f._subjects.state.next({isDirty:y})}},[f,o.isDirty]),It.useEffect(()=>{var y;e.values&&!Gs(e.values,r.current)?(f._reset(e.values,{keepFieldsRef:!0,...f._options.resetOptions}),!((y=f._options.resetOptions)===null||y===void 0)&&y.keepIsValid||f._setValid(),r.current=e.values,u(g=>({...g}))):f._resetDefaultValues()},[f,e.values]),It.useEffect(()=>{f._state.mount||(f._setValid(),f._state.mount=!0),f._state.watch&&(f._state.watch=!1,f._subjects.state.next({...f._formState})),f._removeUnmounted()}),n.current.formState=It.useMemo(()=>Dk(o,f),[f,o]),n.current}const d1=(e,n,r)=>{if(e&&"reportValidity"in e){const a=Se(r,n);e.setCustomValidity(a&&a.message||""),e.reportValidity()}},op=(e,n)=>{for(const r in n.fields){const a=n.fields[r];a&&a.ref&&"reportValidity"in a.ref?d1(a.ref,r,e):a&&a.refs&&a.refs.forEach(o=>d1(o,r,e))}},f1=(e,n)=>{n.shouldUseNativeValidation&&op(e,n);const r={};for(const a in e){const o=Se(n.fields,a),u=Object.assign(e[a]||{},{ref:o&&o.refs?o.refs[0]:o&&o.ref});if(t8(n.names||Object.keys(e),a)){const f=Object.assign({},Se(r,a));gt(f,"root",u),gt(r,a,f)}else gt(r,a,u)}return r},t8=(e,n)=>{const r=h1(n).replace(/[.*+?^${}()|\\]/g,"\\$&");return e.some(a=>h1(a).match(`^${r}\\.\\d+`))};function h1(e){return e.replace(/\[(\d+)]/g,".$1").replace(/[[\]]/g,"")}function cp(){return cp=Object.assign?Object.assign.bind():function(e){for(var n=1;n0){var p,y,g=o.errors.reduce(function(S,A){return A.lengthtypeof a=="number");return Object.entries(e).filter(([a,o])=>n.indexOf(+a)===-1).map(([a,o])=>o)}function p1(e,n="|"){return e.map(r=>RN(r)).join(n)}function dp(e,n){return typeof n=="bigint"?n.toString():n}class r8{constructor(n){this._getter=n,this._value=void 0}get value(){const n=this._getter;return n!==void 0&&(this._value=n(),this._getter=void 0),this._value}}function tg(e){return new r8(e)}function i8(e){return e==null}function ng(e){const n=e.startsWith("^")?1:0,r=e.endsWith("$")?e.length-1:e.length;return e.slice(n,r)}function a8(e,n){const r=e/n,a=Math.round(r),o=4*Number.EPSILON*Math.max(Math.abs(r),1);return Math.abs(r-a){const m=n._zod.def.shape[u];return a?a(m,u):m}):AN(e,u,a?a(f.value,u):f.value))}}function l8(e,n){for(const r of Reflect.ownKeys(n)){const a=Object.getOwnPropertyDescriptor(n,r);a.enumerable&&(a.get?EN(e,r,()=>n[r]):AN(e,r,a.value))}}function ps(...e){const n={};for(const r of e){const a=Object.getOwnPropertyDescriptors(r);Object.assign(n,a)}return Object.defineProperties({},n)}function o8(e){return JSON.stringify(e)}function c8(e){return e.toLowerCase().trim().replace(/[^\w\s-]/g,"").replace(/[\s_-]+/g,"-").replace(/^-+|-+$/g,"")}const CN="captureStackTrace"in Error?Error.captureStackTrace:(...e)=>{};function Jd(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}const u8=tg(()=>{var e;if(_r.jitless||typeof navigator<"u"&&((e=navigator==null?void 0:navigator.userAgent)!=null&&e.includes("Cloudflare")))return!1;try{const n=Function;return new n(""),!0}catch{return!1}});function Ac(e){if(Jd(e)===!1)return!1;const n=e.constructor;if(n===void 0||typeof n!="function")return!0;const r=n.prototype;return!(Jd(r)===!1||Object.prototype.hasOwnProperty.call(r,"isPrototypeOf")===!1)}function ON(e){return Ac(e)?{...e}:Array.isArray(e)?[...e]:e instanceof Map?new Map(e):e instanceof Set?new Set(e):e}const d8=new Set(["string","number","symbol"]);function gf(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Li(e,n,r){const a=new e._zod.constr(n??e._zod.def);return(!n||r!=null&&r.parent)&&(a._zod.parent=e),a}function Ie(e){const n=e;if(!n)return{};if(typeof n=="string")return{error:()=>n};if((n==null?void 0:n.message)!==void 0){if((n==null?void 0:n.error)!==void 0)throw new Error("Cannot specify both `message` and `error` params");n.error=n.message}return delete n.message,typeof n.error=="string"?{...n,error:()=>n.error}:n}function RN(e){return typeof e=="bigint"?e.toString()+"n":typeof e=="string"?`"${e}"`:`${e}`}function f8(e){return Object.keys(e).filter(n=>e[n]._zod.optin!==void 0&&e[n]._zod.optout==="optional")}const TN={safeint:[Number.MIN_SAFE_INTEGER,Number.MAX_SAFE_INTEGER],int32:[-2147483648,2147483647],uint32:[0,4294967295],float32:[-34028234663852886e22,34028234663852886e22],float64:[-Number.MAX_VALUE,Number.MAX_VALUE]},h8={int64:[BigInt("-9223372036854775808"),BigInt("9223372036854775807")],uint64:[BigInt(0),BigInt("18446744073709551615")]};function m8(e,n){const r=e._zod.def,a=r.checks;if(a&&a.length>0)throw new Error(".pick() cannot be used on object schemas containing refinements");const u={};return La(u,e,xf(e,n)),Li(e,ps(r,{shape:u,checks:[]}))}function xf(e,n){var o;const r=Qr(e),a=[];for(const u of Reflect.ownKeys(n)){if(!((o=Object.getOwnPropertyDescriptor(r,u))!=null&&o.enumerable))throw new Error(`Unrecognized key: "${String(u)}"`);n[u]&&a.push(u)}return a}function p8(e,n){const r=e._zod.def,a=r.checks;if(a&&a.length>0)throw new Error(".omit() cannot be used on object schemas containing refinements");const u=new Set(xf(e,n)),f={};return La(f,e,Reflect.ownKeys(Qr(e)).filter(m=>!u.has(m))),Li(e,ps(r,{shape:f,checks:[]}))}function g8(e,n){if(!Ac(n))throw new Error("Invalid input to extend: expected a plain object");const r=e._zod.def.checks;if(r&&r.length>0){const o=Qr(e);for(const u of Reflect.ownKeys(n))if(Object.getOwnPropertyDescriptor(o,u)!==void 0)throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.")}return Li(e,ps(e._zod.def,{shape:kN(e,n)}))}function kN(e,n){const r={};return La(r,e,Reflect.ownKeys(Qr(e))),l8(r,n),r}function x8(e,n){if(!Ac(n))throw new Error("Invalid input to safeExtend: expected a plain object");return Li(e,ps(e._zod.def,{shape:kN(e,n)}))}function b8(e,n){var o,u;if(!((o=n==null?void 0:n._zod)!=null&&o.def))throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.");if((u=e._zod.def.checks)!=null&&u.length)throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");const r={};La(r,e,Reflect.ownKeys(Qr(e))),La(r,n,Reflect.ownKeys(Qr(n)));const a=ps(e._zod.def,{shape:r,get catchall(){return n._zod.def.catchall},checks:n._zod.def.checks??[]});return Li(e,a)}function g1(e,n,r,a="partial"){const u=n._zod.def.checks;if(u&&u.length>0)throw new Error(`.${a}() cannot be used on object schemas containing refinements`);const m=r?new Set(xf(n,r)):void 0,p={};return La(p,n,Reflect.ownKeys(Qr(n)),e&&((y,g)=>m&&!m.has(g)?y:new e({type:"optional",innerType:y}))),Li(n,ps(n._zod.def,{shape:p,checks:[]}))}function y8(e,n,r){const a=r?new Set(xf(n,r)):void 0,o={};return La(o,n,Reflect.ownKeys(Qr(n)),(u,f)=>a&&!a.has(f)?u:new e({type:"nonoptional",innerType:u})),Li(n,ps(n._zod.def,{shape:o}))}function Vr(e,n=0){var r;if(e.aborted===!0)return!0;for(let a=n;a{var a;return(a=r).path??(a.path=[]),r.path.unshift(e),r})}function hc(e){return typeof e=="string"?e:e==null?void 0:e.message}function x1(e,n,r){var a;for(let o=n;oe;return n[A8]=!0,n}var y1;const N0={value:void 0,enumerable:!1};let v1="captureStackTrace"in Error?Error:null;function O8(e){const n=v1;if(n){const r=n.stackTraceLimit;if(typeof r=="number"){try{n.stackTraceLimit=0}catch{return v1=null,new e}try{return new e}finally{n.stackTraceLimit=r}}}return new e}function ie(e,n,r,a){const o={};function u(v){this.def=v,this.constr=x,this.traits=new Set}u.prototype=o;const f=r,m=f&&new WeakSet;function p(v,S){if(!v._zod){N0.value=new u(S);try{Object.defineProperty(v,"_zod",N0)}finally{N0.value=void 0}}if(v._zod.traits.has(e))return;if(v._zod.traits.add(e),n(v,S),m){const C=Object.getPrototypeOf(v),O=v._zod.constr.prototype;let N=C;for(;N&&N!==O;)N=Object.getPrototypeOf(N);const j=N??C;m.has(j)||(m.add(j),S8(j,f))}const A=x.prototype;for(const C in A)Object.prototype.hasOwnProperty.call(A,C)&&(C in v||(v[C]=A[C].bind(v)))}const y=(a==null?void 0:a.Parent)??Object;class g extends y{}Object.defineProperty(g,"name",{value:e});function x(v){var O;const S=a!=null&&a.Parent?O8(g):this;p(S,v);const A=S._zod.deferred;if(A){for(const N of A)N();S._zod.deferred=void 0}const C=(O=globalThis.__zod_globalConfig)==null?void 0:O.postProcessor;return C&&C(S),S}return Object.defineProperty(x,"init",{value:p}),Object.defineProperty(x,Symbol.hasInstance,{value:v=>{var S,A;return a!=null&&a.Parent&&v instanceof a.Parent?!0:(A=(S=v==null?void 0:v._zod)==null?void 0:S.traits)==null?void 0:A.has(e)}}),Object.defineProperty(x,"name",{value:e}),x}class Ra extends Error{constructor(){super("Encountered Promise during synchronous parse. Use .parseAsync() instead.")}}class UN extends Error{constructor(n){super(`Encountered unidirectional transform during encode: ${n}`),this.name="ZodEncodeError"}}(y1=globalThis).__zod_globalConfig??(y1.__zod_globalConfig={});const _r=globalThis.__zod_globalConfig;function Pi(e){return e&&Object.assign(_r,e),_r}function R8(){const e=this._zod;return e.message??(e.message=JSON.stringify(e.def,dp,2)),e.message}function T8(e){this._zod.message=e}const k8={get:R8,set:T8,enumerable:!0,configurable:!0},j0={value:void 0,enumerable:!1},w1=new WeakSet([Object.prototype,Error.prototype]),D8=(e,n)=>{e.name="$ZodError",j0.value=n,Object.defineProperty(e,"issues",j0),j0.value=void 0,Object.defineProperty(e,"message",k8);const r=Object.getPrototypeOf(e);w1.has(r)||(w1.add(r),Object.defineProperty(r,"toString",{configurable:!0,enumerable:!1,get(){const a=()=>this.message;return Object.defineProperty(this,"toString",{value:a,configurable:!0,writable:!0}),a},set(a){Object.defineProperty(this,"toString",{value:a,configurable:!0,writable:!0})}}))},M8=ie("$ZodError",D8);function z8(e,n,r){return Object.prototype.hasOwnProperty.call(e,n)||(n==="__proto__"?Object.defineProperty(e,n,{value:r(),writable:!0,enumerable:!0,configurable:!0}):e[n]=r()),e[n]}function U8(e,n=r=>r.message){const r={},a=[];for(const o of e.issues)o.path.length>0?z8(r,o.path[0],()=>[]).push(n(o)):a.push(n(o));return{formErrors:a,fieldErrors:r}}function I8(e,n=r=>r.message){const r={_errors:[]},a=(o,u=[])=>{for(const f of o.issues)if(f.code==="invalid_union"&&f.errors.length)f.errors.map(m=>a({issues:m},[...u,...f.path]));else if(f.code==="invalid_key")a({issues:f.issues},[...u,...f.path]);else if(f.code==="invalid_element")a({issues:f.issues},[...u,...f.path]);else{const m=[...u,...f.path];if(m.length===0)r._errors.push(n(f));else{let p=r,y=0;for(;y{const n=(r,a,o,u)=>{const f=o?{...o,async:!1}:{async:!1},m=r._zod.run({value:a,issues:[]},f);if(m instanceof Promise)throw new Ra;if(m.issues.length){const p=new((u==null?void 0:u.Err)??e)(m.issues.map(y=>oo(y,f,Pi())));throw CN(p,(u==null?void 0:u.callee)??n),p}return m.value};return n},ag=e=>{const n=async(r,a,o,u)=>{const f=o?{...o,async:!0}:{async:!0};let m=r._zod.run({value:a,issues:[]},f);if(m instanceof Promise&&(m=await m),m.issues.length){const p=new((u==null?void 0:u.Err)??e)(m.issues.map(y=>oo(y,f,Pi())));throw CN(p,(u==null?void 0:u.callee)??n),p}return m.value};return n},lg=e=>(n,r,a)=>{const o=a?{...a,async:!1}:{async:!1},u=n._zod.run({value:r,issues:[]},o);if(u instanceof Promise)throw new Ra;return u.issues.length?IN(e,u.issues,o):{success:!0,data:u.value}};function IN(e,n,r){let a;return{success:!1,get error(){return a||(a=new e(n.map(o=>oo(o,r,Pi()))),n=void 0,r=void 0),a},set error(o){a=o,n=void 0,r=void 0}}}const og=e=>async(n,r,a)=>{const o=a?{...a,async:!0}:{async:!0};let u=n._zod.run({value:r,issues:[]},o);return u instanceof Promise&&(u=await u),u.issues.length?IN(e,u.issues,o):{success:!0,data:u.value}},L8=Symbol.for("zod.compile.invalid"),P8=Symbol.for("zod.compile.fallback"),H8=((e,n,r)=>{const a=e._zod.bag.validator;if(a!==void 0){if(a(n)!==L8)return!0;if(a.definite===!0&&r===void 0)return!1}return q8(e,n,r)});function q8(e,n,r){const a=r?{...r,async:!1,abortEarly:!0}:{async:!1,abortEarly:!0},o=e._zod.bag.fallbackRun;let u;if(o?(a[P8]=!0,u=o({value:n,issues:[]},a)):u=e._zod.run({value:n,issues:[]},a),u instanceof Promise)throw new Ra;return u.issues.length===0}const B8=async(e,n,r)=>{const a=r?{...r,async:!0,abortEarly:!0}:{async:!0,abortEarly:!0};let o=e._zod.run({value:n,issues:[]},a);return o instanceof Promise&&(o=await o),o.issues.length===0},$8=e=>{const n=ig(e),r=(a,o,u,f)=>{const m=u?{...u,direction:"backward"}:{direction:"backward"};return n(a,o,m,bf(r,f))};return r},F8=e=>{const n=ig(e),r=(a,o,u,f)=>n(a,o,u,bf(r,f));return r},V8=e=>{const n=ag(e),r=async(a,o,u,f)=>{const m=u?{...u,direction:"backward"}:{direction:"backward"};return await n(a,o,m,bf(r,f))};return r},Z8=e=>{const n=ag(e),r=async(a,o,u,f)=>await n(a,o,u,bf(r,f));return r},G8=e=>(n,r,a)=>{const o=a?{...a,direction:"backward"}:{direction:"backward"};return lg(e)(n,r,o)},Q8=e=>(n,r,a)=>lg(e)(n,r,a),K8=e=>async(n,r,a)=>{const o=a?{...a,direction:"backward"}:{direction:"backward"};return og(e)(n,r,o)},Y8=e=>async(n,r,a)=>og(e)(n,r,a),J8=/^[cC][0-9a-z]{6,}$/,X8=/^[0-9a-z]+$/,W8=/^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/,eD=/^[0-9a-vA-V]{20}$/,tD=/^[A-Za-z0-9]{27}$/,nD=/^[a-zA-Z0-9_-]{21}$/;function sD(e){return new RegExp(`^[a-zA-Z0-9_-]{${e}}$`)}const rD=/^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/,iD=/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/,_1=e=>e?new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${e}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`):/^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/,aD=/^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/,lD="^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$";function oD(){return new RegExp(lD,"u")}const cD=/^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/,uD=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/,dD=/^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,fD=/^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,hD=/^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,mD=/^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/,pD=/^https?$/,gD=/^\+[1-9]\d{6,14}$/,LN="(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))";function xD(e){return new RegExp(`^${e}$`)}const bD=xD(LN);function fp(e){const n="(?:[01]\\d|2[0-3]):[0-5]\\d";return typeof e.precision=="number"?e.precision===-1?`${n}`:e.precision===0?`${n}:[0-5]\\d`:`${n}:[0-5]\\d\\.\\d{${e.precision}}`:e.seconds?`${n}:[0-5]\\d(?:\\.\\d+)?`:`${n}(?::[0-5]\\d(?:\\.\\d+)?)?`}function yD(e){return new RegExp(`^${fp(e)}$`)}function vD(e){const n=["Z"];e.offset&&n.push("([+-](?:[01]\\d|2[0-3]):[0-5]\\d)");const r=`${fp({precision:e.precision,seconds:!0})}(?:${n.join("|")})`,a=e.local?`${r}|${fp({precision:e.precision})}`:r;return new RegExp(`^${LN}T(?:${a})$`)}const wD=/^[\s\S]{0,}$/,_D=/^-?\d+(?:\.\d+)?$/,SD=/^[^A-Z]*$/,ND=/^[^a-z]*$/,Xn=ie("$ZodCheck",(e,n)=>{var r;e._zod??(e._zod={}),e._zod.def=n,(r=e._zod).onattach??(r.onattach=[])}),cg=e=>{const n=e.value;return!i8(n)&&n.length!==void 0},Xd={number:"number",bigint:"bigint",object:"date"},PN=ie("$ZodCheckLessThan",(e,n)=>{Xn.init(e,n);const r=Xd[typeof n.value];e._zod.check=a=>{(n.inclusive?a.value<=n.value:a.value{Xn.init(e,n);const r=Xd[typeof n.value];e._zod.check=a=>{(n.inclusive?a.value>=n.value:a.value>n.value)||a.issues.push({origin:Xd[typeof a.value]??r,code:"too_small",minimum:typeof n.value=="object"?n.value.getTime():n.value,input:a.value,inclusive:n.inclusive,inst:e,continue:!n.abort})}}),jD=ie("$ZodCheckMultipleOf",(e,n)=>{Xn.init(e,n),e._zod.check=r=>{if(typeof r.value!=typeof n.value)throw new Error("Cannot mix number and bigint in multiple_of check.");(typeof r.value=="bigint"?n.value!==BigInt(0)&&r.value%n.value===BigInt(0):a8(r.value,n.value)===0)||r.issues.push({origin:typeof r.value,code:"not_multiple_of",divisor:n.value,input:r.value,inst:e,continue:!n.abort})}}),ED=ie("$ZodCheckNumberFormat",(e,n)=>{var f;Xn.init(e,n),n.format=n.format||"float64";const r=(f=n.format)==null?void 0:f.includes("int"),a=r?"int":"number",[o,u]=TN[n.format];e._zod.check=m=>{const p=m.value;if(r){if(!Number.isInteger(p)){m.issues.push({expected:a,format:n.format,code:"invalid_type",continue:!1,input:p,inst:e});return}if(!Number.isSafeInteger(p)){p>0?m.issues.push({input:p,code:"too_big",maximum:Number.MAX_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:a,inclusive:!0,continue:!n.abort}):m.issues.push({input:p,code:"too_small",minimum:Number.MIN_SAFE_INTEGER,note:"Integers must be within the safe integer range.",inst:e,origin:a,inclusive:!0,continue:!n.abort});return}}pu&&m.issues.push({origin:"number",input:p,code:"too_big",maximum:u,inclusive:!0,inst:e,continue:!n.abort})}}),AD=ie("$ZodCheckMaxLength",(e,n)=>{var r;Xn.init(e,n),(r=e._zod.def).when??(r.when=cg),e._zod.check=a=>{const o=a.value,u=o.length;if((typeof o=="string"&&u>n.maximum?sg(o):u)<=n.maximum)return;const m=rg(o);a.issues.push({origin:m,code:"too_big",maximum:n.maximum,inclusive:!0,input:o,inst:e,continue:!n.abort})}}),CD=ie("$ZodCheckMinLength",(e,n)=>{var r;Xn.init(e,n),(r=e._zod.def).when??(r.when=cg),e._zod.check=a=>{const o=a.value,u=o.length;if((typeof o=="string"&&u>=n.minimum&&u=n.minimum)return;const m=rg(o);a.issues.push({origin:m,code:"too_small",minimum:n.minimum,inclusive:!0,input:o,inst:e,continue:!n.abort})}}),OD=ie("$ZodCheckLengthEquals",(e,n)=>{var r;Xn.init(e,n),(r=e._zod.def).when??(r.when=cg),e._zod.check=a=>{const o=a.value,u=o.length,f=typeof o=="string"&&u>=n.length&&u<=n.length*2?sg(o):u;if(f===n.length)return;const m=rg(o),p=f>n.length;a.issues.push({origin:m,...p?{code:"too_big",maximum:n.length}:{code:"too_small",minimum:n.length},inclusive:!0,exact:!0,input:a.value,inst:e,continue:!n.abort})}}),yf=ie("$ZodCheckStringFormat",(e,n)=>{var r,a;Xn.init(e,n),n.pattern?(r=e._zod).check??(r.check=o=>{n.pattern.lastIndex=0,!n.pattern.test(o.value)&&o.issues.push({origin:"string",code:"invalid_format",format:n.format,input:o.value,...n.pattern?{pattern:n.pattern.toString()}:{},inst:e,continue:!n.abort})}):(a=e._zod).check??(a.check=()=>{})}),RD=ie("$ZodCheckRegex",(e,n)=>{yf.init(e,n),e._zod.check=r=>{n.pattern.lastIndex=0,!n.pattern.test(r.value)&&r.issues.push({origin:"string",code:"invalid_format",format:"regex",input:r.value,pattern:n.pattern.toString(),inst:e,continue:!n.abort})}}),TD=ie("$ZodCheckLowerCase",(e,n)=>{n.pattern??(n.pattern=SD),yf.init(e,n)}),kD=ie("$ZodCheckUpperCase",(e,n)=>{n.pattern??(n.pattern=ND),yf.init(e,n)}),DD=ie("$ZodCheckIncludes",(e,n)=>{Xn.init(e,n);const r=gf(n.includes),a=new RegExp(typeof n.position=="number"?`^.{${n.position},}${r}`:r);n.pattern=a,e._zod.check=o=>{o.value.includes(n.includes,n.position)||o.issues.push({origin:"string",code:"invalid_format",format:"includes",includes:n.includes,input:o.value,inst:e,continue:!n.abort})}}),MD=ie("$ZodCheckStartsWith",(e,n)=>{Xn.init(e,n);const r=new RegExp(`^${gf(n.prefix)}.*`);n.pattern??(n.pattern=r),e._zod.check=a=>{a.value.startsWith(n.prefix)||a.issues.push({origin:"string",code:"invalid_format",format:"starts_with",prefix:n.prefix,input:a.value,inst:e,continue:!n.abort})}}),zD=ie("$ZodCheckEndsWith",(e,n)=>{Xn.init(e,n);const r=new RegExp(`.*${gf(n.suffix)}$`);n.pattern??(n.pattern=r),e._zod.check=a=>{a.value.endsWith(n.suffix)||a.issues.push({origin:"string",code:"invalid_format",format:"ends_with",suffix:n.suffix,input:a.value,inst:e,continue:!n.abort})}}),UD=ie("$ZodCheckOverwrite",(e,n)=>{Xn.init(e,n),e._zod.check=r=>{r.value=n.tx(r.value)}});class ID{constructor(n=[],r={}){this.content=[],this.indent=0,this.args=n,this.closed=r}indented(n){this.indent+=1;try{n(this)}finally{this.indent-=1}}write(n){if(typeof n=="function"){n(this,{execution:"sync"}),n(this,{execution:"async"});return}const a=n.split(`
+`).filter(f=>f),o=Math.min(...a.map(f=>f.length-f.trimStart().length)),u=a.map(f=>f.slice(o)).map(f=>" ".repeat(this.indent*2)+f);for(const f of u)this.content.push(f)}compile(){const n=Function,r=(this==null?void 0:this.content)??[""];return new n(...Object.keys(this.closed),`return function (${this.args.join(", ")}) {
+${r.join(`
+`)}
+};`)(...Object.values(this.closed))}}const LD={major:4,minor:6,patch:2},Wt=ie("$ZodType",(e,n)=>{var u;var r;e??(e={}),e._zod.def=n,e._zod.bag=e._zod.bag||{},e._zod.version=LD;const a=e._zod.def.checks,o=e._zod.traits.has("$ZodCheck")?[e,...a??[]]:a!=null&&a.length?[...a]:[];for(const f of o)for(const m of f._zod.onattach)m(e);if(o.length===0)(r=e._zod).deferred??(r.deferred=[]),(u=e._zod.deferred)==null||u.push(()=>{e._zod.run=e._zod.parse});else{const f=(p,y,g)=>{if(p.memo)return p;let x=Vr(p),v;for(const S of y){if(S._zod.def.when){if(v8(p)||!S._zod.def.when(p))continue}else if(x)continue;const A=p.issues.length,C=S._zod.check(p);if(C instanceof Promise&&(g==null?void 0:g.async)===!1)throw new Ra;if(v||C instanceof Promise)v=(v??Promise.resolve()).then(async()=>{await C,p.issues.length!==A&&(x1(p.issues,A,e),x||(x=Vr(p,A)))});else{if(p.issues.length===A)continue;x1(p.issues,A,e),x||(x=Vr(p,A))}}return v?v.then(()=>p):p},m=(p,y,g)=>{if(Vr(p))return p.aborted=!0,p;const x=f(y,o,g);if(x instanceof Promise){if(g.async===!1)throw new Ra;return x.then(v=>e._zod.parse(v,g))}return e._zod.parse(x,g)};e._zod.run=(p,y)=>{if(y.skipChecks)return e._zod.parse(p,y);if(y.direction==="backward"){const x=e._zod.parse({value:p.value,issues:[]},{...y,skipChecks:!0});return x instanceof Promise?x.then(v=>m(v,p,y)):m(x,p,y)}const g=e._zod.parse(p,y);if(g instanceof Promise){if(y.async===!1)throw new Ra;return g.then(x=>f(x,o,y))}return f(g,o,y)}}},{get"~standard"(){return MN(this,"~standard",BN(this))},set"~standard"(e){Ii(this,"~standard",e)}}),qN=(e,n)=>e.issues.length?{issues:e.issues.map(r=>oo(r,n,Pi()))}:{value:e.value};async function PD(e,n){const r={async:!0};return qN(await e._zod.run({value:n,issues:[]},r),r)}function BN(e){return{validate:n=>{const r={async:!1};try{const a=e._zod.run({value:n,issues:[]},r);if(!(a instanceof Promise))return qN(a,r)}catch{}return PD(e,n)},vendor:"zod",version:1}}const ug=ie("$ZodString",(e,n)=>{Wt.init(e,n),e._zod.pattern=n.pattern??wD,e._zod.parse=(r,a)=>{if(n.coerce)try{r.value=String(r.value)}catch{}return typeof r.value=="string"||r.issues.push({expected:"string",code:"invalid_type",input:r.value,inst:e}),r}}),Rt=ie("$ZodStringFormat",(e,n)=>{yf.init(e,n),ug.init(e,n)}),HD=ie("$ZodGUID",(e,n)=>{n.pattern??(n.pattern=iD),Rt.init(e,n)}),qD=ie("$ZodUUID",(e,n)=>{if(n.version){const a={v1:1,v2:2,v3:3,v4:4,v5:5,v6:6,v7:7,v8:8}[n.version];if(a===void 0)throw new Error(`Invalid UUID version: "${n.version}"`);n.pattern??(n.pattern=_1(a))}else n.pattern??(n.pattern=_1());Rt.init(e,n)}),BD=ie("$ZodEmail",(e,n)=>{n.pattern??(n.pattern=aD),Rt.init(e,n)}),$N=1,FN=2;function $D(e,n){var r;if(!n.normalize&&((r=n.protocol)==null?void 0:r.source)===pD.source&&!/^https?:\/\//i.test(e))return $N;try{return new URL(e)}catch{return FN}}const FD=/[\t\n\r]/g;function VD(e){return e.replace(FD,"")}function ZD(e,n){return n.lastIndex=0,n.test(e.hostname)}function GD(e,n){return n.lastIndex=0,n.test(e.protocol.endsWith(":")?e.protocol.slice(0,-1):e.protocol)}const QD=ie("$ZodURL",(e,n)=>{Rt.init(e,n),e._zod.check=r=>{try{const a=r.value.trim(),o=$D(a,n);if(o===$N){r.issues.push({code:"invalid_format",format:"url",note:"Invalid URL format",input:r.value,inst:e,continue:!n.abort});return}if(o===FN){r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!n.abort});return}n.hostname&&!ZD(o,n.hostname)&&r.issues.push({code:"invalid_format",format:"url",note:"Invalid hostname",pattern:n.hostname.source,input:r.value,inst:e,continue:!n.abort}),n.protocol&&!GD(o,n.protocol)&&r.issues.push({code:"invalid_format",format:"url",note:"Invalid protocol",pattern:n.protocol.source,input:r.value,inst:e,continue:!n.abort}),r.value=n.normalize?o.href:VD(a);return}catch{r.issues.push({code:"invalid_format",format:"url",input:r.value,inst:e,continue:!n.abort})}}}),KD=ie("$ZodEmoji",(e,n)=>{n.pattern??(n.pattern=oD()),Rt.init(e,n)}),YD=ie("$ZodNanoID",(e,n)=>{if(n.length!==void 0&&(!Number.isInteger(n.length)||n.length<1))throw new Error(`Invalid nanoid length: ${n.length}`);n.pattern??(n.pattern=n.length===void 0?nD:sD(n.length)),Rt.init(e,n)}),JD=ie("$ZodCUID",(e,n)=>{n.pattern??(n.pattern=J8),Rt.init(e,n)}),XD=ie("$ZodCUID2",(e,n)=>{n.pattern??(n.pattern=X8),Rt.init(e,n)}),WD=ie("$ZodULID",(e,n)=>{n.pattern??(n.pattern=W8),Rt.init(e,n)}),eM=ie("$ZodXID",(e,n)=>{n.pattern??(n.pattern=eD),Rt.init(e,n)}),tM=ie("$ZodKSUID",(e,n)=>{n.pattern??(n.pattern=tD),Rt.init(e,n)}),nM=ie("$ZodISODateTime",(e,n)=>{n.pattern??(n.pattern=vD(n)),Rt.init(e,n)}),sM=ie("$ZodISODate",(e,n)=>{n.pattern??(n.pattern=bD),Rt.init(e,n)}),rM=ie("$ZodISOTime",(e,n)=>{n.pattern??(n.pattern=yD(n)),Rt.init(e,n)}),iM=ie("$ZodISODuration",(e,n)=>{n.pattern??(n.pattern=rD),Rt.init(e,n)}),aM=ie("$ZodIPv4",(e,n)=>{n.pattern??(n.pattern=cD),Rt.init(e,n)}),lM=/^[0-9a-fA-F:.]+$/;function VN(e){if(!lM.test(e))return!1;try{return new URL(`http://[${e}]`),!0}catch{return!1}}const oM=ie("$ZodIPv6",(e,n)=>{n.pattern??(n.pattern=uD),Rt.init(e,n),e._zod.check=r=>{VN(r.value)||r.issues.push({code:"invalid_format",format:"ipv6",input:r.value,inst:e,continue:!n.abort})}}),cM=ie("$ZodCIDRv4",(e,n)=>{n.pattern??(n.pattern=dD),Rt.init(e,n)});function uM(e){const n=e.split("/");if(n.length!==2)return!1;const[r,a]=n;if(!a)return!1;const o=Number(a);return`${o}`!==a||o<0||o>128?!1:VN(r)}const dM=ie("$ZodCIDRv6",(e,n)=>{n.pattern??(n.pattern=fD),Rt.init(e,n),e._zod.check=r=>{uM(r.value)||r.issues.push({code:"invalid_format",format:"cidrv6",input:r.value,inst:e,continue:!n.abort})}});function ZN(e){if(e==="")return!0;if(/\s/.test(e)||e.length%4!==0)return!1;try{return atob(e),!0}catch{return!1}}const GN=/^[0-9a-zA-Z+/]*={0,2}$/,fM=ie("$ZodBase64",(e,n)=>{n.pattern??(n.pattern=GN),Rt.init(e,n),e._zod.check=r=>{ZN(r.value)||r.issues.push({code:"invalid_format",format:"base64",input:r.value,inst:e,continue:!n.abort})}}),dg=/^[A-Za-z0-9_-]*$/;function hM(e){if(!dg.test(e))return!1;const n=e.replace(/[-_]/g,a=>a==="-"?"+":"/"),r=n.padEnd(Math.ceil(n.length/4)*4,"=");return ZN(r)}const mM=ie("$ZodBase64URL",(e,n)=>{n.pattern??(n.pattern=dg),Rt.init(e,n),e._zod.check=r=>{hM(r.value)||r.issues.push({code:"invalid_format",format:"base64url",input:r.value,inst:e,continue:!n.abort})}}),pM=ie("$ZodE164",(e,n)=>{n.pattern??(n.pattern=gD),Rt.init(e,n)});function gM(e,n=null){try{const r=e.split(".");if(r.length!==3)return!1;const[a]=r;if(!a)return!1;const o=JSON.parse(atob(a));return!("typ"in o&&(o==null?void 0:o.typ)!=="JWT"||!o.alg||n&&(!("alg"in o)||o.alg!==n))}catch{return!1}}const xM=ie("$ZodJWT",(e,n)=>{Rt.init(e,n),e._zod.check=r=>{gM(r.value,n.alg)||r.issues.push({code:"invalid_format",format:"jwt",input:r.value,inst:e,continue:!n.abort})}}),QN=ie("$ZodNumber",(e,n)=>{Wt.init(e,n),e._zod.pattern=_D,e._zod.parse=(r,a)=>{if(n.coerce)try{r.value=Number(r.value)}catch{}const o=r.value;if(typeof o=="number"&&!Number.isNaN(o)&&Number.isFinite(o))return r;const u=typeof o=="number"?Number.isNaN(o)?"NaN":Number.isFinite(o)?void 0:String(o):void 0;return r.issues.push({expected:"number",code:"invalid_type",input:o,inst:e,...u?{received:u}:{}}),r}}),bM=ie("$ZodNumberFormat",(e,n)=>{ED.init(e,n),QN.init(e,n)}),yM=ie("$ZodUnknown",(e,n)=>{Wt.init(e,n),e._zod.parse=r=>r}),vM=ie("$ZodNever",(e,n)=>{Wt.init(e,n),e._zod.parse=(r,a)=>(r.issues.push({expected:"never",code:"invalid_type",input:r.value,inst:e}),r)});function S1(e,n,r){e.issues.length&&n.issues.push(...DN(r,e.issues)),n.value[r]=e.value}const wM=ie("$ZodArray",(e,n)=>{Wt.init(e,n);const r=_r.memoizer;r==null||r.attach(e),e._zod.parse=(a,o)=>{const u=a.value;if(!Array.isArray(u))return a.issues.push({expected:"array",code:"invalid_type",input:u,inst:e}),a;a.value=r?r.alloc(e,a,Array(u.length),o):Array(u.length);const f=[],m=o==null?void 0:o.abortEarly;for(let p=0;pS1(x,a,p)));else if(S1(g,a,p),m&&g.issues.length!==0&&Vr(g))break}return f.length?Promise.all(f).then(()=>a):a}});function Wd(e,n,r,a,o,u){const f=r in a,m=u==="optional";if(!(!f&&m&&o==="optional")){if(e.issues.length){if(o!==void 0&&m&&!f)return;n.issues.push(...DN(r,e.issues))}if(!f&&o===void 0){e.issues.length||n.issues.push({code:"invalid_type",expected:"nonoptional",input:void 0,path:[r]});return}e.value===void 0?(f||o==="defaulted"&&!m)&&(n.value[r]=void 0):n.value[r]=e.value}}const _M=[];function KN(e){var f,m,p,y;const n=Object.keys(e.shape),r=Object.getOwnPropertySymbols(e.shape),a=r.length?r:_M,o=a.length?[...n,...a]:n;for(const g of o)if(!((y=(p=(m=(f=e.shape)==null?void 0:f[g])==null?void 0:m._zod)==null?void 0:p.traits)!=null&&y.has("$ZodType")))throw new Error(`Invalid element at key "${String(g)}": expected a Zod schema`);const u=f8(e.shape);return{...e,allKeys:o,symbolKeys:a,keySet:new Set(n),numKeys:n.length,optionalKeys:new Set(u)}}function YN(e,n,r,a,o,u,f){const m=[],p=o.keySet,y=o.catchall._zod,g=y.def.type,x=y.optin,v=y.optout;let S=0;for(const A in n){if(f&&r.issues.length!==S){if(Vr(r,S))break;S=r.issues.length}if(p.has(A))continue;if(A==="__proto__"){g==="never"&&m.push(A);continue}if(g==="never"){m.push(A);continue}const C=y.run({value:n[A],issues:[]},a);C instanceof Promise?e.push(C.then(O=>Wd(O,r,A,n,x,v))):Wd(C,r,A,n,x,v)}return m.length&&r.issues.push({code:"unrecognized_keys",keys:m,input:n,inst:u,continue:!0}),e.length?Promise.all(e).then(()=>r):r}const SM=ie("$ZodObject",(e,n)=>{Wt.init(e,n);const r=Object.getOwnPropertyDescriptor(n,"shape"),a=r!=null&&r.get?r.get.raw:n.shape??{};if(a){const y=()=>{const g={...a};return Object.defineProperty(n,"shape",{value:g}),y.raw=g,g};y.raw=a,Object.defineProperty(n,"shape",{get:y})}const o=tg(()=>KN(n));yt(e,"propValues",y=>{const g=y.def.shape,x={};for(const v in g){const S=g[v]._zod;if(S.values){Object.prototype.hasOwnProperty.call(x,v)||Ha(x,v,new Set);for(const A of S.values)x[v].add(A);S.optin!==void 0&&x[v].add(void 0)}}return x});const u=Jd,f=n.catchall;let m;const p=_r.memoizer;p==null||p.attach(e),e._zod.parse=(y,g)=>{m??(m=o.value);const x=y.value;if(!u(x))return y.issues.push({expected:"object",code:"invalid_type",input:x,inst:e}),y;y.value=p?p.alloc(e,y,{},g):{};const v=[],S=m.shape,A=g==null?void 0:g.abortEarly;let C=y.issues.length;for(const O of m.allKeys){if(A&&y.issues.length!==C){if(Vr(y,C))break;C=y.issues.length}if(O==="__proto__")continue;const N=S[O],j=N._zod.optin,F=N._zod.optout,q=N._zod.run({value:x[O],issues:[]},g);q instanceof Promise?v.push(q.then(B=>Wd(B,y,O,x,j,F))):Wd(q,y,O,x,j,F)}return f?YN(v,x,y,g,o.value,e,A===!0):v.length?Promise.all(v).then(()=>y):y}}),NM=ie("$ZodObjectJIT",(e,n)=>{SM.init(e,n);const r=e._zod.parse,a=tg(()=>KN(n)),o=_r.memoizer,u=S=>{var B,re;const A=a.value,C=A.symbolKeys,O=new ID(["payload","ctx"],{shape:S,inst:e,memo:o,syms:C}),N=Q=>`shape[${Q}]._zod.run({ value: input[${Q}], issues: [] }, ctx)`,j=(Q,X)=>`
+ let ${Q}_ab = false;
+ for (let i = 0; i < ${Q}.issues.length; i++) {
+ const iss = ${Q}.issues[i];
+ iss.path = iss.path ? [${X}, ...iss.path] : [${X}];
+ payload.issues.push(iss);
+ if (iss.continue !== true) ${Q}_ab = true;
+ }
+ if (${Q}_ab && ctx && ctx.abortEarly) {
+ payload.value = newResult;
+ return payload;
+ }`;O.write("const input = payload.value;");const F=Object.create(null);let q=0;for(const Q of A.allKeys)F[Q]=`key_${q++}`;O.write(o?"const newResult = memo.alloc(inst, payload, {}, ctx);":"const newResult = {};");for(const Q of A.allKeys){if(Q==="__proto__")continue;const X=F[Q],se=typeof Q=="symbol"?`syms[${C.indexOf(Q)}]`:o8(Q),fe=`${se} in input`,we=S[Q],Ce=(B=we==null?void 0:we._zod)==null?void 0:B.optin,Z=Ce!==void 0,W=((re=we==null?void 0:we._zod)==null?void 0:re.optout)==="optional";if(O.write(`const ${X} = ${N(se)};`),Z&&W){const oe=Ce==="optional"?`${X}_present`:`${X}.value !== undefined || ${X}_present`;O.write(`
+ const ${X}_present = ${fe};
+ if (!${X}.issues.length || ${X}_present) {
+ if (${X}.issues.length) {${j(X,se)}
+ }
+
+ if (${oe}) {
+ newResult[${se}] = ${X}.value;
+ }
+ }
+
+ `)}else Z?(O.write(`
+ if (${X}.issues.length) {${j(X,se)}
+ }
+ `),Ce==="defaulted"?O.write(`newResult[${se}] = ${X}.value;`):O.write(`
+ if (${X}.value !== undefined || ${fe}) {
+ newResult[${se}] = ${X}.value;
+ }
+ `)):O.write(`
+ const ${X}_present = ${fe};
+ if (${X}.issues.length) {${j(X,se)}
+ }
+ if (!${X}_present && !${X}.issues.length) {
+ payload.issues.push({
+ code: "invalid_type",
+ expected: "nonoptional",
+ input: undefined,
+ path: [${se}]
+ });
+ if (ctx && ctx.abortEarly) {
+ payload.value = newResult;
+ return payload;
+ }
+ }
+
+ if (${X}_present) {
+ newResult[${se}] = ${X}.value;
+ }
+
+ `)}return O.write("payload.value = newResult;"),O.write("return payload;"),O.compile()};let f;const m=Jd,p=!_r.jitless,g=p&&u8.value,x=n.catchall;let v;e._zod.parse=(S,A)=>{v??(v=a.value);const C=S.value;return m(C)?p&&g&&(A==null?void 0:A.async)===!1&&A.jitless!==!0?(f||(f=u(n.shape)),S=f(S,A),x?YN([],C,S,A,v,e,(A==null?void 0:A.abortEarly)===!0):S):r(S,A):(S.issues.push({expected:"object",code:"invalid_type",input:C,inst:e}),S)}});function N1(e,n,r,a){for(const u of e)if(u.issues.length===0)return n.value=u.value,n;const o=e.filter(u=>!Vr(u));return o.length===1?(n.value=o[0].value,o[0]):(n.issues.push({code:"invalid_union",input:n.value,inst:r,errors:e.map(u=>u.issues.map(f=>oo(f,a,Pi())))}),n)}const jM=ie("$ZodUnion",(e,n)=>{Wt.init(e,n),yt(e,"optin",a=>a.def.options.some(o=>o._zod.optin==="defaulted")?"defaulted":a.def.options.some(o=>o._zod.optin!==void 0)?"optional":void 0),yt(e,"optout",a=>a.def.options.some(o=>o._zod.optout==="optional")?"optional":void 0),yt(e,"values",a=>{if(a.def.options.every(o=>o._zod.values))return new Set(a.def.options.flatMap(o=>Array.from(o._zod.values)))}),yt(e,"pattern",a=>{if(a.def.options.every(o=>o._zod.pattern)){const o=a.def.options.map(u=>u._zod.pattern);return new RegExp(`^(${o.map(u=>ng(u.source)).join("|")})$`)}});const r=n.options.length===1?n.options[0]._zod.run:null;e._zod.parse=(a,o)=>{if(r)return r(a,o);let u=!1;const f=[];for(const m of n.options){const p=m._zod.run({value:a.value,issues:[]},o);if(p instanceof Promise)f.push(p),u=!0;else{if(p.issues.length===0)return p;f.push(p)}}return u?Promise.all(f).then(m=>N1(m,a,e,o)):N1(f,a,e,o)}}),EM=ie("$ZodIntersection",(e,n)=>{Wt.init(e,n),e._zod.parse=(r,a)=>{const o=r.value,u=n.left._zod.run({value:o,issues:[]},a),f=n.right._zod.run({value:o,issues:[]},a);return u instanceof Promise||f instanceof Promise?Promise.all([u,f]).then(([p,y])=>j1(r,p,y)):j1(r,u,f)}});function hp(e,n){if(e===n)return{valid:!0,data:e};if(e instanceof Date&&n instanceof Date&&+e==+n)return{valid:!0,data:e};if(Ac(e)&&Ac(n)){const r=Object.keys(n),a=Object.keys(e).filter(u=>r.indexOf(u)!==-1),o={...e,...n};Object.prototype.hasOwnProperty.call(o,"__proto__")&&delete o.__proto__;for(const u of a){if(u==="__proto__")continue;const f=hp(e[u],n[u]);if(!f.valid)return{valid:!1,mergeErrorPath:[u,...f.mergeErrorPath]};o[u]=f.data}return{valid:!0,data:o}}if(Array.isArray(e)&&Array.isArray(n)){if(e.length!==n.length)return{valid:!1,mergeErrorPath:[]};const r=[];for(let a=0;a{var v,S;let x;if(y.code==="unrecognized_keys"&&!((v=y.path)!=null&&v.length))o??(o=y),x=y.keys;else if(y.code==="invalid_key"&&y.origin==="record"&&((S=y.path)==null?void 0:S.length)===1){const A=String(y.path[0]);u.has(A)||u.set(A,y),x=[A]}else return!1;for(const A of x)a.has(A)||a.set(A,{}),a.get(A)[g]=!0;return!0};for(const y of n.issues)f(y,"l")||e.issues.push(y);for(const y of r.issues)f(y,"r")||e.issues.push(y);const m=[...a].filter(([,y])=>y.l&&y.r).map(([y])=>y);if(m.length){const y=o?m.filter(g=>o.keys.includes(g)):[];y.length&&e.issues.push({...o,keys:y});for(const g of m)!y.includes(g)&&u.has(g)&&e.issues.push(u.get(g))}const p=hp(n.value,r.value);if(!p.valid){if(Vr(e))return e;throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(p.mergeErrorPath)}`)}return e.value=p.data,e}const AM=ie("$ZodEnum",(e,n)=>{Wt.init(e,n);const r=up(n.entries),a=new Set(r);e._zod.values=a,yt(e,"pattern",o=>{const u=up(o.def.entries).filter(f=>d8.has(typeof f));return new RegExp(u.length?`^(${u.map(f=>gf(f.toString())).join("|")})$`:"^[^\\s\\S]$")}),e._zod.parse=(o,u)=>{const f=o.value;return a.has(f)||o.issues.push({code:"invalid_value",values:r,input:f,inst:e}),o}}),CM=ie("$ZodTransform",(e,n)=>{var r;Wt.init(e,n),e._zod.optin="optional",(r=_r.memoizer)==null||r.guard(e),e._zod.parse=(a,o)=>{if(o.direction==="backward")throw new UN(e.constructor.name);const u=n.transform(a.value,a);if(o.async)return(u instanceof Promise?u:Promise.resolve(u)).then(m=>(a.value=m,a));if(u instanceof Promise)throw new Ra;return a.value=u,a}});function E1(e,n){return e.value=n.issues.length?void 0:n.value,e}const JN=ie("$ZodOptional",(e,n)=>{Wt.init(e,n),yt(e,"optin",r=>r.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),e._zod.optout="optional",yt(e,"values",r=>{const a=r.def.innerType._zod.values;return a?new Set([...a,void 0]):void 0}),yt(e,"pattern",r=>{const a=r.def.innerType._zod.pattern;return a?new RegExp(`^(${ng(a.source)})?$`):void 0}),e._zod.parse=(r,a)=>{if(r.value===void 0){if(n.innerType._zod.optin!=="defaulted")return r;const o=n.innerType._zod.run({value:r.value,issues:[]},a);return o instanceof Promise?o.then(u=>E1(r,u)):E1(r,o)}return n.innerType._zod.run(r,a)}}),OM=ie("$ZodExactOptional",(e,n)=>{JN.init(e,n),yt(e,"values",r=>r.def.innerType._zod.values),yt(e,"pattern",r=>r.def.innerType._zod.pattern),e._zod.parse=(r,a)=>n.innerType._zod.run(r,a)}),RM=ie("$ZodNullable",(e,n)=>{Wt.init(e,n),yt(e,"optin",r=>r.def.innerType._zod.optin),yt(e,"optout",r=>r.def.innerType._zod.optout),yt(e,"pattern",r=>{const a=r.def.innerType._zod.pattern;return a?new RegExp(`^(${ng(a.source)}|null)$`):void 0}),yt(e,"values",r=>r.def.innerType._zod.values?new Set([...r.def.innerType._zod.values,null]):void 0),e._zod.parse=(r,a)=>r.value===null?r:n.innerType._zod.run(r,a)}),TM=ie("$ZodDefault",(e,n)=>{Wt.init(e,n),e._zod.optin="defaulted",yt(e,"values",r=>r.def.innerType._zod.values),e._zod.parse=(r,a)=>{if(a.direction==="backward")return n.innerType._zod.run(r,a);if(r.value===void 0)return r.value=n.defaultValue,r;const o=n.innerType._zod.run(r,a);return o instanceof Promise?o.then(u=>A1(u,n)):A1(o,n)}});function A1(e,n){return e.value===void 0&&(e.value=n.defaultValue),e}const kM=ie("$ZodPrefault",(e,n)=>{Wt.init(e,n),e._zod.optin="defaulted",yt(e,"values",r=>r.def.innerType._zod.values),e._zod.parse=(r,a)=>(a.direction==="backward"||r.value===void 0&&(r.value=n.defaultValue),n.innerType._zod.run(r,a))}),DM=ie("$ZodNonOptional",(e,n)=>{Wt.init(e,n),yt(e,"values",r=>{const a=r.def.innerType._zod.values;return a?new Set([...a].filter(o=>o!==void 0)):void 0}),e._zod.parse=(r,a)=>{const o=n.innerType._zod.run(r,a);return o instanceof Promise?o.then(u=>C1(u,e)):C1(o,e)}});function C1(e,n){return!e.issues.length&&e.value===void 0&&e.issues.push({code:"invalid_type",expected:"nonoptional",input:e.value,inst:n}),e}function O1(e,n,r,a){return n.issues.length?(e.value=r.catchValue({...n,value:e.value,error:{issues:n.issues.map(o=>oo(o,a,Pi()))},input:e.value}),e):(e.value=n.value,n.memo&&(e.memo=!0),e)}const MM=ie("$ZodCatch",(e,n)=>{Wt.init(e,n),yt(e,"optin",r=>r.def.innerType._zod.optin==="defaulted"?"defaulted":"optional"),yt(e,"optout",r=>r.def.innerType._zod.optout),yt(e,"values",r=>r.def.innerType._zod.values),e._zod.parse=(r,a)=>{if(a.direction==="backward")return n.innerType._zod.run(r,a);const o=n.innerType._zod.run({value:r.value,issues:[]},a);return o instanceof Promise?o.then(u=>O1(r,u,n,a)):O1(r,o,n,a)}}),zM=ie("$ZodPipe",(e,n)=>{Wt.init(e,n),yt(e,"values",r=>r.def.in._zod.values),yt(e,"optin",r=>r.def.in._zod.optin),yt(e,"optout",r=>r.def.out._zod.optout),yt(e,"propValues",r=>r.def.in._zod.propValues),e._zod.parse=(r,a)=>{if(a.direction==="backward"){const u=n.out._zod.run(r,a);return u instanceof Promise?u.then(f=>Nd(f,n.in,a)):Nd(u,n.in,a)}const o=n.in._zod.run(r,a);return o instanceof Promise?o.then(u=>Nd(u,n.out,a)):Nd(o,n.out,a)}});function Nd(e,n,r){return e.issues.some(a=>a.code!=="unrecognized_keys")?(e.aborted=!0,e):n._zod.run({value:e.value,issues:e.issues},r)}const UM=ie("$ZodReadonly",(e,n)=>{Wt.init(e,n),yt(e,"propValues",r=>r.def.innerType._zod.propValues),yt(e,"values",r=>r.def.innerType._zod.values),yt(e,"optin",r=>{var a,o;return(o=(a=r.def.innerType)==null?void 0:a._zod)==null?void 0:o.optin}),yt(e,"optout",r=>{var a,o;return(o=(a=r.def.innerType)==null?void 0:a._zod)==null?void 0:o.optout}),e._zod.parse=(r,a)=>{if(a.direction==="backward")return n.innerType._zod.run(r,a);const o=n.innerType._zod.run(r,a);return o instanceof Promise?o.then(R1):R1(o)}});function R1(e){return e.memo||(e.value=Object.freeze(e.value)),e}const IM=ie("$ZodCustom",(e,n)=>{Xn.init(e,n),Wt.init(e,n),e._zod.parse=(r,a)=>r,e._zod.check=r=>{const a=r.value,o=n.fn(a);if(o instanceof Promise)return o.then(u=>T1(u,r,a,e));T1(o,r,a,e)}});function T1(e,n,r,a){if(!e){const o={code:"custom",input:r,inst:a,path:[...a._zod.def.path??[]],continue:!a._zod.def.abort};a._zod.def.params&&(o.params=a._zod.def.params),n.issues.push(Cc(o))}}class LM extends Error{constructor(){super("Cannot parse a reference cycle that closes through a transform"),this.name="ZodCyclicError"}}const mp="~memo",k1=[];function XN(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function E0(e){return e.map(n=>n.path?{...n,path:n.path.slice()}:{...n})}const WN=new WeakMap,bc=0,qd=1,wc=2;function Bd(e,n,r){const a=WN.get(e);if(a!==void 0)return a?wc:bc;if(n.has(e))return wc;n.add(e);let o=bc;const u=g=>{if(o!==wc&&(g!=null&&g._zod)){const x=Bd(g,n);x>o&&(o=x)}},f=(g,x)=>{var S;let v=bc;for(const A of Reflect.ownKeys(g)){const C=Object.getOwnPropertyDescriptor(g,A);if(x&&!C.enumerable)continue;const O=C.get?qd:(S=C.value)!=null&&S._zod?Bd(C.value,n):bc;O>v&&(v=O)}return v},m=g=>{g>o&&(o=g)},p=e._zod.def;switch(p.type){case"object":{const g=jN(p);m(g?f(g,!0):qd),u(p.catchall);break}case"properties":m(f(p.shape,!1));break;case"array":u(p.element);break;case"tuple":for(const g of p.items)u(g);u(p.rest);break;case"record":case"map":u(p.keyType),u(p.valueType);break;case"set":u(p.valueType);break;case"union":for(const g of p.options)u(g);break;case"intersection":u(p.left),u(p.right);break;case"optional":case"nullable":case"default":case"prefault":case"catch":case"readonly":case"nonoptional":case"promise":case"success":u(p.innerType);break;case"pipe":u(p.in),u(p.out);break;case"function":u(p.input),u(p.output);break;case"lazy":{const g=p._cachedInner??void 0;m(g?Bd(g,n):qd);break}case"template_literal":case"string":case"number":case"int":case"boolean":case"bigint":case"symbol":case"undefined":case"null":case"void":case"never":case"any":case"unknown":case"date":case"nan":case"enum":case"literal":case"file":case"transform":case"custom":break;default:for(const g in p){const x=Object.getOwnPropertyDescriptor(p,g);if(!x||x.get)continue;const v=x.value;if(!(!v||typeof v!="object")){if(v._zod)u(v);else if(Array.isArray(v))for(const S of v)u(S)}}}return n.delete(e),PM(e,o)}function PM(e,n){return n!==qd&&WN.set(e,n===wc),n}function HM(e,n){let r=e.buckets.get(n);return r||(r=new WeakMap,e.buckets.set(n,r)),r}let jd;const Ed=[],qM={alloc(e,n,r){const a=jd;if(!a)return r;jd=void 0;const o={value:r,issues:null};return a.set(n.value,o),Ed.push(o),r},guard(e){var n;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{const r=e._zod.parse,a=(o,u)=>{if(u.direction!=="backward"&&$M(u,o.value))throw new LM;return r(o,u)};e._zod.parse=a,e._zod.run===r&&(e._zod.run=a)})},attach(e){var n;let r,a=!1,o,u;(n=e._zod).deferred??(n.deferred=[]),e._zod.deferred.push(()=>{const f=e._zod.parse,m=(p,y)=>{if(r===void 0){const N=Bd(e,new Set);if(N===bc)return e._zod.parse=f,e._zod.run===m&&(e._zod.run=f),f(p,y);N===wc||a?r=!0:a=!0}const g=p.value;if(!XN(g))return f(p,y);let x=y[mp];x||(x={buckets:new WeakMap,backEdges:void 0},y[mp]=x);let v;o===y?v=u:(v=HM(x,e),o=y,u=v);const S=v.get(g);if(S)return p.value=S.value,S.issues?S.issues.length&&p.issues.push(...E0(S.issues)):(p.memo=!0,x.backEdges??(x.backEdges=new WeakSet),x.backEdges.add(S.value)),p;jd=v;const A=Ed.length,C=f(p,y);jd=void 0;const O=Ed.length>A?Ed.pop():void 0;return C instanceof Promise?C.then(N=>(O&&(O.issues=N.issues.length?E0(N.issues):k1),N)):(O&&(O.issues=C.issues.length?E0(C.issues):k1),C)};e._zod.parse=m,e._zod.run===f&&(e._zod.run=m)})}};function BM(){return qM}function $M(e,n){var a;const r=(a=e[mp])==null?void 0:a.backEdges;return r!==void 0&&XN(n)&&r.has(n)}const FM=()=>{const e={string:{unit:"characters",verb:"to have"},file:{unit:"bytes",verb:"to have"},array:{unit:"items",verb:"to have"},set:{unit:"items",verb:"to have"},map:{unit:"entries",verb:"to have"}};function n(u){return e[u]??null}const r={regex:"input",email:"email address",url:"URL",emoji:"emoji",uuid:"UUID",uuidv4:"UUIDv4",uuidv6:"UUIDv6",nanoid:"nanoid",guid:"GUID",cuid:"cuid",cuid2:"cuid2",ulid:"ULID",xid:"XID",ksuid:"KSUID",datetime:"ISO datetime",date:"ISO date",time:"ISO time",duration:"ISO duration",ipv4:"IPv4 address",ipv6:"IPv6 address",mac:"MAC address",cidrv4:"IPv4 range",cidrv6:"IPv6 range",base64:"base64-encoded string",base64url:"base64url-encoded string",json_string:"JSON string",e164:"E.164 number",credit_card:"credit card number",iban:"IBAN",jwt:"JWT",template_literal:"input"},a={nan:"NaN"};function o(u,f){return u==="number"&&typeof f=="number"&&!Number.isFinite(f)?String(f):a[u]??u}return u=>{switch(u.code){case"invalid_type":{const f=o(u.expected),m=_8(u.input),p=o(m,u.input);return`Invalid input: expected ${f}, received ${p}`}case"invalid_value":return u.values.length===1?`Invalid input: expected ${RN(u.values[0])}`:`Invalid option: expected one of ${p1(u.values,"|")}`;case"too_big":{const f=u.exact?"exactly ":u.inclusive?"<=":"<",m=n(u.origin);return m?`Too big: expected ${u.origin??"value"} to have ${f}${u.maximum.toString()} ${m.unit??"elements"}`:`Too big: expected ${u.origin??"value"} to be ${f}${u.maximum.toString()}`}case"too_small":{const f=u.exact?"exactly ":u.inclusive?">=":">",m=n(u.origin);return m?`Too small: expected ${u.origin} to have ${f}${u.minimum.toString()} ${m.unit}`:`Too small: expected ${u.origin} to be ${f}${u.minimum.toString()}`}case"invalid_format":{const f=u;return f.format==="starts_with"?`Invalid string: must start with "${f.prefix}"`:f.format==="ends_with"?`Invalid string: must end with "${f.suffix}"`:f.format==="includes"?`Invalid string: must include "${f.includes}"`:f.format==="regex"?`Invalid string: must match pattern ${f.pattern}`:`Invalid ${r[f.format]??u.format}`}case"not_multiple_of":return`Invalid number: must be a multiple of ${u.divisor}`;case"unrecognized_keys":return`Unrecognized key${u.keys.length>1?"s":""}: ${p1(u.keys,", ")}`;case"invalid_key":return`Invalid key in ${u.origin}`;case"invalid_union":return u.options&&Array.isArray(u.options)&&u.options.length>0?`Invalid discriminator value. Expected ${u.options.map(m=>`'${m}'`).join(" | ")}`:u.inclusive===!1?"Invalid input: more than one option matched":"Invalid input";case"invalid_element":return`Invalid value in ${u.origin}`;default:return"Invalid input"}}};function VM(){return{localeError:FM()}}var D1;class ZM{constructor(){this._map=new WeakMap,this._idmap=new Map}add(n,...r){const a=r[0];return this._map.set(n,a),a&&typeof a=="object"&&"id"in a&&this._idmap.set(a.id,n),this}clear(){return this._map=new WeakMap,this._idmap=new Map,this}remove(n){const r=this._map.get(n);return r&&typeof r=="object"&&"id"in r&&this._idmap.delete(r.id),this._map.delete(n),this}get(n){const r=n._zod.parent;if(r){const a={...this.get(r)??{}};delete a.id;const o={...a,...this._map.get(n)};return Object.keys(o).length?o:void 0}return this._map.get(n)}has(n){return this._map.has(n)}}function GM(){return new ZM}(D1=globalThis).__zod_globalRegistry??(D1.__zod_globalRegistry=GM());const yc=globalThis.__zod_globalRegistry;function QM(e,n){return new e({type:"string",...Ie(n)})}function KM(e,n){return new e({type:"string",format:"email",check:"string_format",abort:!1,...Ie(n)})}function YM(e,n){return new e({type:"string",format:"guid",check:"string_format",abort:!1,...Ie(n)})}function JM(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,...Ie(n)})}function XM(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v4",...Ie(n)})}function WM(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v6",...Ie(n)})}function e6(e,n){return new e({type:"string",format:"uuid",check:"string_format",abort:!1,version:"v7",...Ie(n)})}function t6(e,n){return new e({type:"string",format:"url",check:"string_format",abort:!1,...Ie(n)})}function n6(e,n){return new e({type:"string",format:"emoji",check:"string_format",abort:!1,...Ie(n)})}function s6(e,n){return new e({type:"string",format:"nanoid",check:"string_format",abort:!1,...Ie(n)})}function r6(e,n){return new e({type:"string",format:"cuid",check:"string_format",abort:!1,...Ie(n)})}function i6(e,n){return new e({type:"string",format:"cuid2",check:"string_format",abort:!1,...Ie(n)})}function a6(e,n){return new e({type:"string",format:"ulid",check:"string_format",abort:!1,...Ie(n)})}function l6(e,n){return new e({type:"string",format:"xid",check:"string_format",abort:!1,...Ie(n)})}function o6(e,n){return new e({type:"string",format:"ksuid",check:"string_format",abort:!1,...Ie(n)})}function c6(e,n){return new e({type:"string",format:"ipv4",check:"string_format",abort:!1,...Ie(n)})}function u6(e,n){return new e({type:"string",format:"ipv6",check:"string_format",abort:!1,...Ie(n)})}function d6(e,n){return new e({type:"string",format:"cidrv4",check:"string_format",abort:!1,...Ie(n)})}function f6(e,n){return new e({type:"string",format:"cidrv6",check:"string_format",abort:!1,...Ie(n)})}function h6(e,n){return new e({type:"string",format:"base64",check:"string_format",abort:!1,...Ie(n)})}function m6(e,n){return new e({type:"string",format:"base64url",check:"string_format",abort:!1,...Ie(n)})}function p6(e,n){return new e({type:"string",format:"e164",check:"string_format",abort:!1,...Ie(n)})}function g6(e,n){return new e({type:"string",format:"jwt",check:"string_format",abort:!1,...Ie(n)})}function x6(e,n){return new e({type:"string",format:"datetime",check:"string_format",offset:!1,local:!1,precision:null,...Ie(n)})}function b6(e,n){return new e({type:"string",format:"date",check:"string_format",...Ie(n)})}function y6(e,n){return new e({type:"string",format:"time",check:"string_format",precision:null,...Ie(n)})}function v6(e,n){return new e({type:"string",format:"duration",check:"string_format",...Ie(n)})}function w6(e,n){return new e({type:"number",coerce:!0,checks:[],...Ie(n)})}function _6(e,n){return new e({type:"number",check:"number_format",abort:!1,format:"safeint",...Ie(n)})}function S6(e){return new e({type:"unknown"})}function N6(e,n){return new e({type:"never",...Ie(n)})}function M1(e,n){return new PN({check:"less_than",...Ie(n),value:e,inclusive:!1})}function A0(e,n){return new PN({check:"less_than",...Ie(n),value:e,inclusive:!0})}function z1(e,n){return new HN({check:"greater_than",...Ie(n),value:e,inclusive:!1})}function C0(e,n){return new HN({check:"greater_than",...Ie(n),value:e,inclusive:!0})}function U1(e,n){return new jD({check:"multiple_of",...Ie(n),value:e})}function ej(e,n){return new AD({check:"max_length",...Ie(n),maximum:e})}function ef(e,n){return new CD({check:"min_length",...Ie(n),minimum:e})}function tj(e,n){return new OD({check:"length_equals",...Ie(n),length:e})}function j6(e,n){return new RD({check:"string_format",format:"regex",...Ie(n),pattern:e})}function E6(e){return new TD({check:"string_format",format:"lowercase",...Ie(e)})}function A6(e){return new kD({check:"string_format",format:"uppercase",...Ie(e)})}function C6(e,n){return new DD({check:"string_format",format:"includes",...Ie(n),includes:e})}function O6(e,n){return new MD({check:"string_format",format:"starts_with",...Ie(n),prefix:e})}function R6(e,n){return new zD({check:"string_format",format:"ends_with",...Ie(n),suffix:e})}function co(e){return new UD({check:"overwrite",tx:e})}function T6(e){return co(n=>n.normalize(e))}function k6(){return co(e=>e.trim())}function D6(){return co(e=>e.toLowerCase())}function M6(){return co(e=>e.toUpperCase())}function z6(){return co(e=>c8(e))}function U6(e,n,r){return new e({type:"array",element:n,...Ie(r)})}function I6(e,n,r){return new e({type:"custom",check:"custom",fn:n,...Ie(r)})}function L6(e,n){const r=P6(a=>(a.addIssue=o=>{if(typeof o=="string")a.issues.push(Cc(o,a.value,r._zod.def));else{const u=o;u.fatal&&(u.continue=!1),u.code??(u.code="custom"),"input"in u||(u.input=a.value),u.inst??(u.inst=r),u.continue??(u.continue=!r._zod.def.abort),a.issues.push(Cc(u))}},e(a.value,a)),n);return r}function P6(e,n){const r=new Xn({check:"custom",...Ie(n)});return r._zod.check=e,r}function _c(e,...n){for(const r of n)for(const a of Reflect.ownKeys(r))Object.prototype.propertyIsEnumerable.call(r,a)&&Ha(e,a,r[a]);return e}function nj(e){let n=(e==null?void 0:e.target)??"draft-2020-12";return n==="draft-4"&&(n="draft-04"),n==="draft-7"&&(n="draft-07"),{processors:e.processors??{},metadataRegistry:(e==null?void 0:e.metadata)??yc,target:n,unrepresentable:(e==null?void 0:e.unrepresentable)??"throw",override:(e==null?void 0:e.override)??(()=>{}),io:(e==null?void 0:e.io)??"output",counter:0,seen:new Map,sharedDefsExtractedFor:void 0,sharedEmitDoneFor:void 0,cycles:(e==null?void 0:e.cycles)??"ref",reused:(e==null?void 0:e.reused)??"inline",intersections:[],deferred:[],external:(e==null?void 0:e.external)??void 0}}function uo(e,n,r,a,o){const u=typeof n.unrepresentable=="function"?n.unrepresentable({zodSchema:e,path:a.path,message:o}):n.unrepresentable;if(u==="any")return!1;if(u===void 0||u==="throw")throw new Error(o);return Object.assign(r,u),!0}function _n(e,n,r={path:[],schemaPath:[]}){var g,x;var a;const o=e._zod.def,u=n.seen.get(e);if(u)return u.count++,r.schemaPath.includes(e)&&(u.cycle=r.path),u.schema;const f={schema:{},count:1,cycle:void 0,path:r.path};n.seen.set(e,f),n.sharedDefsExtractedFor=void 0,n.sharedEmitDoneFor=void 0;const m=(x=(g=e._zod).toJSONSchema)==null?void 0:x.call(g);if(m)f.schema=m;else{const v={...r,schemaPath:[...r.schemaPath,e],path:r.path};if(e._zod.processJSONSchema)e._zod.processJSONSchema(n,f.schema,v);else{const A=f.schema,C=n.processors[o.type];if(!C)throw new Error(`[toJSONSchema]: Non-representable type encountered: ${o.type}`);C(e,n,A,v)}const S=e._zod.parent;S&&(f.ref||(f.ref=S),_n(S,n,v),n.seen.get(S).isParent=!0)}const p=n.metadataRegistry.get(e);return p&&_c(f.schema,p),n.io==="input"&&Un(e)&&(delete f.schema.examples,delete f.schema.default),n.io==="input"&&"_prefault"in f.schema&&((a=f.schema).default??(a.default=f.schema._prefault)),delete f.schema._prefault,n.seen.get(e).schema}function I1(e){return e.replace(/~/g,"~0").replace(/\//g,"~1")}function sj(e,n){var f,m,p,y;const r=e.seen.get(n);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");if(e.external&&e.sharedDefsExtractedFor===e.external)return;const a=new Map;for(const g of e.seen.entries()){const x=(f=e.metadataRegistry.get(g[0]))==null?void 0:f.id;if(x){const v=a.get(x);if(v&&v!==g[0])throw new Error(`Duplicate schema id "${x}" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);a.set(x,g[0])}}const o=g=>{var C;const x=e.target==="draft-2020-12"?"$defs":"definitions";if(e.external){const O=(C=e.external.registry.get(g[0]))==null?void 0:C.id,N=e.external.uri??(F=>F);if(O)return{ref:N(O)};const j=g[1].defId??g[1].schema.id??`schema${e.counter++}`;return g[1].defId=j,{defId:j,ref:`${N("__shared")}#/${x}/${I1(j)}`}}const v="#",S=`${v}/${x}/`;if(g[1]===r&&!g[1].schema.id)return{ref:v};const A=g[1].schema.id??`__schema${e.counter++}`;return{defId:A,ref:S+I1(A)}},u=g=>{if(g[1].schema.$ref)return;const x=g[1],{ref:v,defId:S}=o(g);x.def={...x.schema},S&&(x.defId=S);const A=x.schema;for(const C in A)delete A[C];A.$ref=v};if(e.cycles==="throw")for(const g of e.seen.entries()){const x=g[1];if(x.cycle)throw new Error(`Cycle detected: #/${(m=x.cycle)==null?void 0:m.join("/")}/
+
+Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.`)}for(const g of e.seen.entries()){const x=g[1];if(n===g[0]){u(g);continue}if(e.external){const S=(p=e.external.registry.get(g[0]))==null?void 0:p.id;if(n!==g[0]&&S){u(g);continue}}if((y=e.metadataRegistry.get(g[0]))==null?void 0:y.id){u(g);continue}if(x.cycle){u(g);continue}x.count>1&&e.reused==="ref"&&u(g)}e.external&&(e.sharedDefsExtractedFor=e.external)}function rj(e){const n=e.anyOf;if(!Array.isArray(n)||n.length===0||e.type!==void 0)return;const r=[];for(const a of n){if(!a||typeof a!="object")return;rj(a);const o=Object.keys(a);if(o.length!==1||o[0]!=="type")return;const u=a.type;for(const f of Array.isArray(u)?u:[u]){if(typeof f!="string")return;r.includes(f)||r.push(f)}}delete e.anyOf,e.type=r.length===1?r[0]:r}const ij=new Set(["type","properties","required","additionalProperties"]),L1=["oneOf","anyOf"];function P1(e){const n=e.additionalProperties;return n===void 0||n===!1||typeof n!="object"||n===null?null:Object.keys(n).length?n:null}function pp(e){var u;const n=[];for(const f of e){if(typeof f!="object"||f.type!=="object")return null;for(const m in f)if(!ij.has(m))return null;n.push(f)}const r={},a=new Set;for(const f of n){for(const m in f.properties){if(Object.prototype.hasOwnProperty.call(r,m))continue;const p=[];for(const g of n){const x=((u=g.properties)==null?void 0:u[m])??P1(g);x!=null&&(p.some(v=>JSON.stringify(v)===JSON.stringify(x))||p.push(x))}const y=p.length===1?p[0]:pp(p)??{allOf:p};Ha(r,m,y)}for(const m of f.required??[])a.add(m)}const o={type:"object",properties:r};if(a.size&&(o.required=[...a]),n.every(f=>f.additionalProperties===!1))o.additionalProperties=!1;else{const f=[];for(const m of n){const p=P1(m);p&&!f.some(y=>JSON.stringify(y)===JSON.stringify(p))&&f.push(p)}f.length===1?o.additionalProperties=f[0]:f.length>1&&(o.additionalProperties={allOf:f})}return o}function H6(e){const n=e.allOf;if(!Array.isArray(n)||n.length<2)return;for(const o of ij)if(o in e)return;const r=n.filter(o=>L1.some(u=>Array.isArray(o[u])));let a=null;if(!r.length)a=pp(n);else{const o=r[0],u=L1.find(p=>Array.isArray(o[p]));if(Object.keys(o).length!==1)return;const f=n.filter(p=>p!==o),m=o[u].map(p=>pp([...f,p]));if(m.some(p=>!p))return;a={[u]:m}}a&&(delete e.allOf,_c(e,a))}function aj(e,n){var m,p,y,g;const r=e.seen.get(n);if(!r)throw new Error("Unprocessed schema. This is a bug in Zod.");const a=x=>{const v=e.seen.get(x);if(v.ref===null)return;const S=v.def??v.schema,A={...S},C=v.ref;if(v.ref=null,C){a(C);const N=e.seen.get(C),j=N.schema;if(j.$ref&&(e.target==="draft-07"||e.target==="draft-04"||e.target==="openapi-3.0")?(S.allOf=S.allOf??[],S.allOf.push(j)):_c(S,j),_c(S,A),x._zod.parent===C)for(const q in S)q==="$ref"||q==="allOf"||q in A||delete S[q];if(j.$ref&&N.def)for(const q in S)q==="$ref"||q==="allOf"||q in N.def&&JSON.stringify(S[q])===JSON.stringify(N.def[q])&&delete S[q]}const O=x._zod.parent;if(O&&O!==C){a(O);const N=e.seen.get(O);if(N!=null&&N.schema.$ref&&(S.$ref=N.schema.$ref,N.def))for(const j in S)j==="$ref"||j==="allOf"||j in N.def&&JSON.stringify(S[j])===JSON.stringify(N.def[j])&&delete S[j]}e.override({zodSchema:x,jsonSchema:S,path:v.path??[]})};if(!e.external||e.sharedEmitDoneFor!==e.external){for(const x of[...e.seen.entries()].reverse())a(x[0]);if(e.target!=="openapi-3.0")for(const x of e.seen.entries())rj(x[1].def??x[1].schema);for(const x of e.deferred)x();if(e.intersections.length){const x=new Map;for(const v of e.seen.values())for(const S of[v.schema,v.def]){const A=S==null?void 0:S.allOf;if(!Array.isArray(A))continue;const C=x.get(A);C?C.push(S):x.set(A,[S])}for(const v of e.intersections)for(const S of x.get(v)??[])H6(S)}}const o={};if(e.target==="draft-2020-12"?o.$schema="https://json-schema.org/draft/2020-12/schema":e.target==="draft-07"?o.$schema="http://json-schema.org/draft-07/schema#":e.target==="draft-04"?o.$schema="http://json-schema.org/draft-04/schema#":e.target,(m=e.external)!=null&&m.uri){const x=(p=e.external.registry.get(n))==null?void 0:p.id;if(!x)throw new Error("Schema is missing an `id` property");o.$id=e.external.uri(x)}_c(o,r.defId?r.schema:r.def??r.schema);const u=(y=e.metadataRegistry.get(n))==null?void 0:y.id;u!==void 0&&o.id===u&&delete o.id;const f=((g=e.external)==null?void 0:g.defs)??{};if(!e.external||e.sharedEmitDoneFor!==e.external)for(const x of e.seen.entries()){const v=x[1];v.def&&v.defId&&(v.def.id===v.defId&&delete v.def.id,Ha(f,v.defId,v.def))}e.external&&(e.sharedEmitDoneFor=e.external),e.external||Object.keys(f).length>0&&(e.target==="draft-2020-12"?o.$defs=f:o.definitions=f);try{const x=JSON.parse(JSON.stringify(o));return Object.defineProperty(x,"~standard",{value:{...n["~standard"],jsonSchema:{input:tf(n,"input",e.processors),output:tf(n,"output",e.processors)}},enumerable:!1,writable:!1}),x}catch{throw new Error("Error converting schema to JSON.")}}function Un(e,n){const r=n??{seen:new Set};if(r.seen.has(e))return!1;r.seen.add(e);const a=e._zod.def;if(a.type==="transform")return!0;if(a.type==="array")return Un(a.element,r);if(a.type==="set")return Un(a.valueType,r);if(a.type==="lazy")return Un(a.getter(),r);if(a.type==="promise"||a.type==="optional"||a.type==="nonoptional"||a.type==="nullable"||a.type==="readonly"||a.type==="default"||a.type==="prefault"||a.type==="catch")return Un(a.innerType,r);if(a.type==="intersection")return Un(a.left,r)||Un(a.right,r);if(a.type==="record"||a.type==="map")return Un(a.keyType,r)||Un(a.valueType,r);if(a.type==="pipe")return e._zod.traits.has("$ZodCodec")?!0:Un(a.in,r)||Un(a.out,r);if(a.type==="object"){for(const o in a.shape)if(Un(a.shape[o],r))return!0;return!1}if(a.type==="union"){for(const o of a.options)if(Un(o,r))return!0;return!1}if(a.type==="tuple"){for(const o of a.items)if(Un(o,r))return!0;return!!(a.rest&&Un(a.rest,r))}return!1}const q6=(e,n={})=>r=>{const a=nj({...r,processors:n});return _n(e,a),sj(a,e),aj(a,e)},tf=(e,n,r={})=>a=>{const{libraryOptions:o,target:u}=a??{},f=nj({...o??{},target:u,io:n,processors:r});return _n(e,f),sj(f,e),aj(f,e)},eo=(e,n,r)=>{(e[n]===void 0||r>e[n])&&(e[n]=r)},to=(e,n,r)=>{(e[n]===void 0||r{eo(e,"minimum",n),to(e,"maximum",n)},lj=(e,n)=>{e.multipleOf??(e.multipleOf=[]),e.multipleOf.includes(n)||e.multipleOf.push(n)},oj=(e,n)=>{e.patterns??(e.patterns=new Set),e.patterns.add(n)},cj=(e,n)=>{e.mime=e.mime?e.mime.filter(r=>n.includes(r)):[...n]},uj=(e,n)=>{e.format=n,n.includes("int")&&(e.isInt=!0)},q1=(e,n)=>eo(e,"minimum",n.minimum),B1=(e,n)=>to(e,"maximum",n.maximum),$1=e=>(n,r)=>{uj(n,r.format);const[a,o]=e[r.format];eo(n,"minimum",a),to(n,"maximum",o)},O0={greater_than:(e,n)=>eo(e,n.inclusive?"minimum":"exclusiveMinimum",n.value),less_than:(e,n)=>to(e,n.inclusive?"maximum":"exclusiveMaximum",n.value),multiple_of:(e,n)=>lj(e,n.value),number_format:$1(TN),bigint_format:$1(h8),min_length:q1,max_length:B1,length_equals:(e,n)=>H1(e,n.length),min_size:q1,max_size:B1,size_equals:(e,n)=>H1(e,n.size),string_format:(e,n)=>{uj(e,n.format),n.pattern&&oj(e,n.pattern),(n.format==="base64"||n.format==="base64url")&&(e.contentEncoding=n.format),(n.local||n.precision===-1)&&(e.laxFormat=!0)},mime_type:(e,n)=>cj(e,n.mime)};function vr(e){var u;const n={},r=e._zod.def,a=e._zod.traits.has("$ZodCheck")?[e,...r.checks??[]]:r.checks??[];for(const f of a)(u=O0[f._zod.def.check])==null||u.call(O0,n,f._zod.def);const o=e._zod.bag;o.minimum!==void 0&&eo(n,"minimum",o.minimum),o.exclusiveMinimum!==void 0&&eo(n,"exclusiveMinimum",o.exclusiveMinimum),o.maximum!==void 0&&to(n,"maximum",o.maximum),o.exclusiveMaximum!==void 0&&to(n,"exclusiveMaximum",o.exclusiveMaximum),o.multipleOf!==void 0&&lj(n,o.multipleOf),o.format!==void 0&&(n.format??(n.format=o.format),o.format.includes("int")&&(n.isInt=!0)),o.mime&&cj(n,o.mime);for(const f of o.patterns??[])oj(n,f);return n}const B6={guid:"uuid",url:"uri",datetime:"date-time",json_string:"json-string",regex:""},$6=new Map([[GN,hD],[dg,mD]]),F6=e=>$6.get(e)??e,V6=(e,n,r,a)=>{const o=r;o.type="string";const{minimum:u,maximum:f,format:m,patterns:p,contentEncoding:y,laxFormat:g}=vr(e);if(typeof u=="number"&&(o.minLength=u),typeof f=="number"&&(o.maxLength=f),m&&(o.format=B6[m]??m,o.format===""&&delete o.format,(m==="time"||g)&&delete o.format),y&&(o.contentEncoding=y),p&&p.size>0){const x=[...p].map(F6);x.length===1?o.pattern=x[0].source:x.length>1&&(o.allOf=[...x.map(v=>({...n.target==="draft-07"||n.target==="draft-04"||n.target==="openapi-3.0"?{type:"string"}:{},pattern:v.source}))])}},Z6=(e,n,r,a)=>{const o=r,{minimum:u,maximum:f,multipleOf:m,exclusiveMaximum:p,exclusiveMinimum:y,isInt:g}=vr(e);o.type=g?"integer":"number";const x=typeof y=="number"&&y>=(u??Number.NEGATIVE_INFINITY),v=typeof p=="number"&&p<=(f??Number.POSITIVE_INFINITY),S=n.target==="draft-04"||n.target==="openapi-3.0";if(x?S?(o.minimum=y,o.exclusiveMinimum=!0):o.exclusiveMinimum=y:typeof u=="number"&&(o.minimum=u),v?S?(o.maximum=p,o.exclusiveMaximum=!0):o.exclusiveMaximum=p:typeof f=="number"&&(o.maximum=f),m){const A=new Set;for(const N of m)Number.isFinite(N)&&N!==0?A.add(Math.abs(N)):uo(e,n,o,a,`A multipleOf divisor of ${N} cannot be represented in JSON Schema`);const[C,...O]=A;C!==void 0&&(o.multipleOf=C),O.length&&(o.allOf=[...o.allOf??[],...O.map(N=>({multipleOf:N}))])}},G6=(e,n,r,a)=>{r.not={}},Q6=(e,n,r,a)=>{},K6=(e,n,r,a)=>{const o=e._zod.def,u=up(o.entries);if(u.length===0){r.not={};return}u.every(f=>typeof f=="number")&&(r.type="number"),u.every(f=>typeof f=="string")&&(r.type="string"),r.enum=u},Y6=(e,n,r,a)=>{uo(e,n,r,a,"Custom types cannot be represented in JSON Schema")},J6=(e,n,r,a)=>{uo(e,n,r,a,"Transforms cannot be represented in JSON Schema")},X6=(e,n,r,a)=>{const o=r,u=e._zod.def,{minimum:f,maximum:m}=vr(e);typeof f=="number"&&(o.minItems=f),typeof m=="number"&&(o.maxItems=m),o.type="array",o.items=_n(u.element,n,{...a,path:[...a.path,"items"]})};function gp(e){const n=e._zod.def;return n.type==="pipe"&&n.in._zod.traits.has("$ZodTransform")?gp(n.out):n.type==="catch"?gp(n.innerType):e._zod.optin}const W6=(e,n,r,a)=>{var g;const o=r,u=e._zod.def,f=u.shape;if(Object.getOwnPropertySymbols(f).length&&uo(e,n,o,a,"Symbol keys cannot be represented in JSON Schema"))return;o.type="object",o.properties={};for(const x in f)Ha(o.properties,x,_n(f[x],n,{...a,path:[...a.path,"properties",x]}));const p=new Set(Object.keys(f)),y=new Set([...p].filter(x=>{const v=u.shape[x];return n.io==="input"?gp(v)===void 0:v._zod.optout===void 0}));y.size>0&&(o.required=Array.from(y)),((g=u.catchall)==null?void 0:g._zod.def.type)==="never"?o.additionalProperties=!1:u.catchall?u.catchall&&(o.additionalProperties=_n(u.catchall,n,{...a,path:[...a.path,"additionalProperties"]})):n.io==="output"&&(o.additionalProperties=!1)},ez=(e,n,r,a)=>{const o=e._zod.def,u=o.inclusive===!1,f=o.options.map((m,p)=>_n(m,n,{...a,path:[...a.path,u?"oneOf":"anyOf",p]}));u?r.oneOf=f:r.anyOf=f},tz=(e,n,r,a)=>{const o=e._zod.def,u=_n(o.left,n,{...a,path:[...a.path,"allOf",0]}),f=_n(o.right,n,{...a,path:[...a.path,"allOf",1]}),m=y=>"allOf"in y&&Object.keys(y).length===1,p=[...m(u)?u.allOf:[u],...m(f)?f.allOf:[f]];r.allOf=p,n.intersections.push(p)},nz=(e,n,r,a)=>{const o=e._zod.def,u=_n(o.innerType,n,a),f=n.seen.get(e);n.target==="openapi-3.0"?(f.ref=o.innerType,r.nullable=!0):r.anyOf=[u,{type:"null"}]},sz=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);u.ref=o.innerType},fg=Symbol();function dj(e,n,r,a,o){let u=!1;const f=JSON.stringify(e,(m,p)=>typeof p!="bigint"?p:(u=!0,null));return u?(uo(n,r,a,o,"BigInt defaults cannot be represented in JSON Schema"),fg):JSON.parse(f)}const rz=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);u.ref=o.innerType;const f=dj(o.defaultValue,e,n,r,a);f!==fg&&(r.default=f)},iz=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);if(u.ref=o.innerType,n.io!=="input")return;const f=dj(o.defaultValue,e,n,r,a);f!==fg&&(r._prefault=f)},az=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);u.ref=o.innerType;let f;try{f=o.catchValue(void 0)}catch{uo(e,n,r,a,"Dynamic catch values are not supported in JSON Schema");return}r.default=f},lz=(e,n,r,a)=>{const o=e._zod.def,u=o.in._zod.traits.has("$ZodTransform"),f=n.io==="input"?u?o.out:o.in:o.out;_n(f,n,a);const m=n.seen.get(e);m.ref=f},oz=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);u.ref=o.innerType,r.readOnly=!0},fj=(e,n,r,a)=>{const o=e._zod.def;_n(o.innerType,n,a);const u=n.seen.get(e);u.ref=o.innerType},F1=new WeakSet([Object.prototype,Error.prototype]);function Ad(e,n,r){Object.defineProperty(e,n,{configurable:!0,enumerable:!1,get(){const a=r(this);return Object.defineProperty(this,n,{value:a,configurable:!0,writable:!0}),a},set(a){Object.defineProperty(this,n,{value:a,configurable:!0,writable:!0})}})}const cz=(e,n)=>{M8.init(e,n),e.name="ZodError";const r=Object.getPrototypeOf(e);F1.has(r)||(F1.add(r),Ad(r,"format",a=>o=>I8(a,o)),Ad(r,"flatten",a=>o=>U8(a,o)),Ad(r,"addIssue",a=>o=>{a.issues.push(o),a.message=JSON.stringify(a.issues,dp,2)}),Ad(r,"addIssues",a=>o=>{a.issues.push(...o),a.message=JSON.stringify(a.issues,dp,2)}),Object.defineProperty(r,"isEmpty",{configurable:!0,enumerable:!1,get(){return this.issues.length===0}}))},ks=ie("ZodError",cz,void 0,{Parent:Error}),uz=ig(ks),dz=ag(ks),fz=lg(ks),hz=og(ks),mz=$8(ks),pz=F8(ks),gz=V8(ks),xz=Z8(ks),bz=G8(ks),yz=Q8(ks),vz=K8(ks),wz=Y8(ks);function _z(){_r.localeError||Pi(VM())}function hg(){_r.memoizer||Pi({memoizer:BM()})}const en=ie("ZodType",(e,n)=>(_z(),Wt.init(e,n),e.def=n,e.type=n.type,e),{check(...e){const n=this.def;return this.clone(ps(n,{checks:[...n.checks??[],...e.map(r=>typeof r=="function"?{_zod:{check:r,def:{check:"custom"},onattach:[]}}:r)]}),{parent:!0})},with(...e){return this.check(...e)},clone(e,n){return Li(this,e,n)},brand(){return this},register(e,n){return e.add(this,n),this},refine(e,n){return this.check(yU(e,n))},superRefine(e,n){return this.check(vU(e,n))},overwrite(e){return this.check(co(e))},optional(){return G1(this)},exactOptional(){return aU(this)},nullable(){return Q1(this)},nullish(){return G1(Q1(this))},nonoptional(e){return fU(this,e)},array(){return Jz(this)},or(e){return eU([this,e])},and(e){return nU(this,e)},transform(e){return K1(this,iU(e))},default(e){return cU(this,e)},prefault(e){return dU(this,e)},catch(e){return mU(this,e)},pipe(e){return K1(this,e)},readonly(){return xU(this)},describe(e){const n=this.clone();return yc.add(n,{description:e}),n},meta(...e){if(e.length===0)return yc.get(this);const n=this.clone();return yc.add(n,e[0]),n},isOptional(){return this.safeParse(void 0).success},isNullable(){return this.safeParse(null).success},apply(e,...n){return n.length===0?e(this):e(this,...n)},get"~standard"(){return MN(this,"~standard",{...BN(this),jsonSchema:{input:tf(this,"input"),output:tf(this,"output")}})},set"~standard"(e){Ii(this,"~standard",e)},parse:function e(n,r){return uz(this,n,r,{callee:e})},parseAsync:async function e(n,r){return await dz(this,n,r,{callee:e})},safeParse(e,n){return fz(this,e,n)},async safeParseAsync(e,n){return hz(this,e,n)},get spa(){return this==null?void 0:this.safeParseAsync},set spa(e){Ii(this,"spa",e)},validate(e,n){return H8(this,e,n)},validateAsync(e,n){return B8(this,e,n)},encode:function e(n,r){return mz(this,n,r,{callee:e})},decode:function e(n,r){return pz(this,n,r,{callee:e})},encodeAsync:async function e(n,r){return await gz(this,n,r,{callee:e})},decodeAsync:async function e(n,r){return await xz(this,n,r,{callee:e})},safeEncode(e,n){return bz(this,e,n)},safeDecode(e,n){return yz(this,e,n)},async safeEncodeAsync(e,n){return vz(this,e,n)},async safeDecodeAsync(e,n){return wz(this,e,n)},toJSONSchema(e){return q6(this,{})(e)},get description(){var e;return(e=yc.get(this))==null?void 0:e.description},get _def(){return this._zod.def}}),hj=ie("_ZodString",(e,n)=>{ug.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>V6(e,r,a)},zN({format:e=>vr(e).format??null,minLength:e=>vr(e).minimum??null,maxLength:e=>vr(e).maximum??null},{regex(...e){return this.check(j6(...e))},includes(...e){return this.check(C6(...e))},startsWith(...e){return this.check(O6(...e))},endsWith(...e){return this.check(R6(...e))},min(...e){return this.check(ef(...e))},max(...e){return this.check(ej(...e))},length(...e){return this.check(tj(...e))},nonempty(...e){return this.check(ef(1,...e))},lowercase(e){return this.check(E6(e))},uppercase(e){return this.check(A6(e))},trim(){return this.check(k6())},normalize(...e){return this.check(T6(...e))},toLowerCase(){return this.check(D6())},toUpperCase(){return this.check(M6())},slugify(){return this.check(z6())}})),Sz=ie("ZodString",(e,n)=>{ug.init(e,n),hj.init(e,n)},{email(e){return this.check(KM(Cz,e))},url(e){return this.check(t6(Rz,e))},jwt(e){return this.check(g6(Vz,e))},emoji(e){return this.check(n6(Tz,e))},guid(e){return this.check(YM(Oz,e))},uuid(e){return this.check(JM(Cd,e))},uuidv4(e){return this.check(XM(Cd,e))},uuidv6(e){return this.check(WM(Cd,e))},uuidv7(e){return this.check(e6(Cd,e))},nanoid(e){return this.check(s6(kz,e))},cuid(e){return this.check(r6(Dz,e))},cuid2(e){return this.check(i6(Mz,e))},ulid(e){return this.check(a6(zz,e))},base64(e){return this.check(h6(Bz,e))},base64url(e){return this.check(m6($z,e))},xid(e){return this.check(l6(Uz,e))},ksuid(e){return this.check(o6(Iz,e))},ipv4(e){return this.check(c6(Lz,e))},ipv6(e){return this.check(u6(Pz,e))},cidrv4(e){return this.check(d6(Hz,e))},cidrv6(e){return this.check(f6(qz,e))},e164(e){return this.check(p6(Fz,e))},datetime(e){return this.check(x6(Nz,e))},date(e){return this.check(b6(jz,e))},time(e){return this.check(y6(Ez,e))},duration(e){return this.check(v6(Az,e))}});function Et(e){return QM(Sz,e)}const Mt=ie("ZodStringFormat",(e,n)=>{Rt.init(e,n),hj.init(e,n)}),Nz=ie("ZodISODateTime",(e,n)=>{nM.init(e,n),Mt.init(e,n)}),jz=ie("ZodISODate",(e,n)=>{sM.init(e,n),Mt.init(e,n)}),Ez=ie("ZodISOTime",(e,n)=>{rM.init(e,n),Mt.init(e,n)}),Az=ie("ZodISODuration",(e,n)=>{iM.init(e,n),Mt.init(e,n)}),Cz=ie("ZodEmail",(e,n)=>{BD.init(e,n),Mt.init(e,n)}),Oz=ie("ZodGUID",(e,n)=>{HD.init(e,n),Mt.init(e,n)}),Cd=ie("ZodUUID",(e,n)=>{qD.init(e,n),Mt.init(e,n)}),Rz=ie("ZodURL",(e,n)=>{QD.init(e,n),Mt.init(e,n)}),Tz=ie("ZodEmoji",(e,n)=>{KD.init(e,n),Mt.init(e,n)}),kz=ie("ZodNanoID",(e,n)=>{YD.init(e,n),Mt.init(e,n)}),Dz=ie("ZodCUID",(e,n)=>{JD.init(e,n),Mt.init(e,n)}),Mz=ie("ZodCUID2",(e,n)=>{XD.init(e,n),Mt.init(e,n)}),zz=ie("ZodULID",(e,n)=>{WD.init(e,n),Mt.init(e,n)}),Uz=ie("ZodXID",(e,n)=>{eM.init(e,n),Mt.init(e,n)}),Iz=ie("ZodKSUID",(e,n)=>{tM.init(e,n),Mt.init(e,n)}),Lz=ie("ZodIPv4",(e,n)=>{aM.init(e,n),Mt.init(e,n)}),Pz=ie("ZodIPv6",(e,n)=>{oM.init(e,n),Mt.init(e,n)}),Hz=ie("ZodCIDRv4",(e,n)=>{cM.init(e,n),Mt.init(e,n)}),qz=ie("ZodCIDRv6",(e,n)=>{dM.init(e,n),Mt.init(e,n)}),Bz=ie("ZodBase64",(e,n)=>{fM.init(e,n),Mt.init(e,n)}),$z=ie("ZodBase64URL",(e,n)=>{mM.init(e,n),Mt.init(e,n)}),Fz=ie("ZodE164",(e,n)=>{pM.init(e,n),Mt.init(e,n)}),Vz=ie("ZodJWT",(e,n)=>{xM.init(e,n),Mt.init(e,n)}),mj=ie("ZodNumber",(e,n)=>{QN.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>Z6(e,r,a,o),e.isFinite=!0},zN({minValue:e=>{const{minimum:n,exclusiveMinimum:r}=vr(e);return Math.max(n??Number.NEGATIVE_INFINITY,r??Number.NEGATIVE_INFINITY)},maxValue:e=>{const{maximum:n,exclusiveMaximum:r}=vr(e);return Math.min(n??Number.POSITIVE_INFINITY,r??Number.POSITIVE_INFINITY)},isInt:e=>{const{isInt:n,multipleOf:r}=vr(e);return!!n||!!(r!=null&&r.some(Number.isSafeInteger))},format:e=>vr(e).format??null},{gt(e,n){return this.check(z1(e,n))},gte(e,n){return this.check(C0(e,n))},min(e,n){return this.check(C0(e,n))},lt(e,n){return this.check(M1(e,n))},lte(e,n){return this.check(A0(e,n))},max(e,n){return this.check(A0(e,n))},int(e){return this.check(V1(e))},safe(e){return this.check(V1(e))},positive(e){return this.check(z1(0,e))},nonnegative(e){return this.check(C0(0,e))},negative(e){return this.check(M1(0,e))},nonpositive(e){return this.check(A0(0,e))},multipleOf(e,n){return this.check(U1(e,n))},step(e,n){return this.check(U1(e,n))},finite(){return this}})),Zz=ie("ZodNumberFormat",(e,n)=>{bM.init(e,n),mj.init(e,n)});function V1(e){return _6(Zz,e)}const Gz=ie("ZodUnknown",(e,n)=>{yM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>Q6()});function Z1(){return S6(Gz)}const Qz=ie("ZodNever",(e,n)=>{vM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>G6(e,r,a)});function Kz(e){return N6(Qz,e)}const Yz=ie("ZodArray",(e,n)=>{hg(),wM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>X6(e,r,a,o),e.element=n.element},{min(e,n){return this.check(ef(e,n))},nonempty(e){return this.check(ef(1,e))},max(e,n){return this.check(ej(e,n))},length(e,n){return this.check(tj(e,n))},unwrap(){return this.element}});function Jz(e,n){return U6(Yz,e,n)}const Xz=ie("ZodObject",(e,n)=>{hg(),NM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>W6(e,r,a,o),E8(e,"shape",r=>r._zod.def.shape,!1)},{keyof(){return sU(Object.keys(this._zod.def.shape))},catchall(e){return this.clone(ps(this._zod.def,{catchall:e}))},passthrough(){return this.clone(ps(this._zod.def,{catchall:Z1()}))},loose(){return this.clone(ps(this._zod.def,{catchall:Z1()}))},strict(){return this.clone(ps(this._zod.def,{catchall:Kz()}))},strip(){return this.clone(ps(this._zod.def,{catchall:void 0}))},extend(e){return g8(this,e)},safeExtend(e){return x8(this,e)},merge(e){return b8(this,e)},pick(e){return m8(this,e)},omit(e){return p8(this,e)},partial(...e){return g1(pj,this,e[0])},exactPartial(...e){return g1(gj,this,e[0],"exactPartial")},required(...e){return y8(xj,this,e[0])}});function xs(e,n){const r={type:"object",shape:e??{},...Ie(n)};return new Xz(r)}const Wz=ie("ZodUnion",(e,n)=>{jM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>ez(e,r,a,o),e.options=n.options});function eU(e,n){return new Wz({type:"union",options:e,...Ie(n)})}const tU=ie("ZodIntersection",(e,n)=>{EM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>tz(e,r,a,o)});function nU(e,n){return new tU({type:"intersection",left:e,right:n})}const xp=ie("ZodEnum",(e,n)=>{AM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(a,o,u)=>K6(e,a,o),e.enum=n.entries,e.options=[...e._zod.values];const r=new Set(Object.keys(n.entries));e.extract=(a,o)=>{const u={};for(const f of a)if(r.has(f))u[f]=n.entries[f];else throw new Error(`Key ${f} not found in enum`);return new xp({...n,checks:[],...Ie(o),entries:u})},e.exclude=(a,o)=>{const u={...n.entries};for(const f of a)if(r.has(f))delete u[f];else throw new Error(`Key ${f} not found in enum`);return new xp({...n,checks:[],...Ie(o),entries:u})}});function sU(e,n){const r=Array.isArray(e)?Object.fromEntries(e.map(a=>[a,a])):e;return new xp({type:"enum",entries:r,...Ie(n)})}const rU=ie("ZodTransform",(e,n)=>{hg(),CM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>J6(e,r,a,o),e._zod.parse=(r,a)=>{if(a.direction==="backward")throw new UN(e.constructor.name);r.addIssue=u=>{if(typeof u=="string")r.issues.push(Cc(u,r.value,n));else{const f=u;f.fatal&&(f.continue=!1),f.code??(f.code="custom"),"input"in f||(f.input=r.value),f.inst??(f.inst=e),r.issues.push(Cc(f))}};const o=n.transform(r.value,r);return o instanceof Promise?o.then(u=>(r.value=u,r)):(r.value=o,r)}});function iU(e){return new rU({type:"transform",transform:e})}const pj=ie("ZodOptional",(e,n)=>{JN.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>fj(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function G1(e){return new pj({type:"optional",innerType:e})}const gj=ie("ZodExactOptional",(e,n)=>{OM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>fj(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function aU(e){return new gj({type:"optional",innerType:e})}const lU=ie("ZodNullable",(e,n)=>{RM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>nz(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function Q1(e){return new lU({type:"nullable",innerType:e})}const oU=ie("ZodDefault",(e,n)=>{TM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>rz(e,r,a,o),e.unwrap=()=>e._zod.def.innerType,e.removeDefault=e.unwrap});function cU(e,n){return new oU({type:"default",innerType:e,get defaultValue(){return typeof n=="function"?n():ON(n)}})}const uU=ie("ZodPrefault",(e,n)=>{kM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>iz(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function dU(e,n){return new uU({type:"prefault",innerType:e,get defaultValue(){return typeof n=="function"?n():ON(n)}})}const xj=ie("ZodNonOptional",(e,n)=>{DM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>sz(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function fU(e,n){return new xj({type:"nonoptional",innerType:e,...Ie(n)})}const hU=ie("ZodCatch",(e,n)=>{MM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>az(e,r,a,o),e.unwrap=()=>e._zod.def.innerType,e.removeCatch=e.unwrap});function mU(e,n){return new hU({type:"catch",innerType:e,catchValue:typeof n=="function"?n:C8(n)})}const pU=ie("ZodPipe",(e,n)=>{zM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>lz(e,r,a,o),e.in=n.in,e.out=n.out});function K1(e,n){return new pU({type:"pipe",in:e,out:n})}const gU=ie("ZodReadonly",(e,n)=>{UM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>oz(e,r,a,o),e.unwrap=()=>e._zod.def.innerType});function xU(e){return new gU({type:"readonly",innerType:e})}const bU=ie("ZodCustom",(e,n)=>{IM.init(e,n),en.init(e,n),e._zod.processJSONSchema=(r,a,o)=>Y6(e,r,a,o)});function yU(e,n={}){return I6(bU,e,n)}function vU(e,n){return L6(e,n)}function _t(e){return w6(mj,e)}const hn=({isOpen:e,onClose:n,title:r,subtitle:a,children:o,maxWidth:u="lg"})=>{if(T.useEffect(()=>{const m=p=>{p.key==="Escape"&&n()};return e&&(document.body.style.overflow="hidden",window.addEventListener("keydown",m)),()=>{document.body.style.overflow="unset",window.removeEventListener("keydown",m)}},[e,n]),!e)return null;const f={sm:"max-w-sm",md:"max-w-md",lg:"max-w-lg",xl:"max-w-xl","2xl":"max-w-2xl"}[u];return c.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-in fade-in duration-150",children:c.jsxs("div",{className:`relative w-full ${f} bg-[#0f1523] rounded-2xl shadow-2xl border border-slate-800/90 overflow-hidden flex flex-col max-h-[90vh] text-slate-200`,onClick:m=>m.stopPropagation(),children:[c.jsxs("div",{className:"flex items-start justify-between p-5 border-b border-slate-800/80 bg-[#0d121f]/90",children:[c.jsxs("div",{children:[c.jsx("h3",{className:"text-lg font-semibold text-white tracking-tight",children:r}),a&&c.jsx("p",{className:"text-xs text-slate-400 mt-0.5",children:a})]}),c.jsx("button",{onClick:n,className:"rounded-xl p-1.5 text-slate-400 hover:text-white hover:bg-slate-800/60 transition cursor-pointer",children:c.jsx(Bp,{className:"w-5 h-5"})})]}),c.jsx("div",{className:"p-6 overflow-y-auto flex-1 bg-[#0f1523]",children:o})]})})},wU=xs({name:Et().min(3,"Name must be at least 3 characters"),registrationNo:Et().min(4,"Registration number is mandatory"),email:Et().email("Enter a valid email address"),type:_t().min(1).max(27),status:_t().optional()}),_U=xs({name:Et().min(3,"Portfolio name is required"),description:Et().min(5,"Description is required"),organisationId:_t().min(1,"Select a parent organisation"),ownerId:Et().min(1,"Owner ID is required")}),SU=xs({name:Et().min(3,"Programme name is required"),portfolioId:_t().min(1,"Select a parent portfolio"),status:_t().optional(),ownerId:Et().min(1,"Owner ID is required")}),NU=()=>{var Re,pe;const e=Pa(),{sub:n,user:r}=Uc(),a=n||(r==null?void 0:r.id)||"",{success:o,error:u}=Lc(),[f,m]=T.useState("organisations"),[p,y]=T.useState(""),[g,x]=T.useState(!1),[v,S]=T.useState(null),[A,C]=T.useState(!1),[O,N]=T.useState(null),[j,F]=T.useState(!1),[q,B]=T.useState(null),[re,Q]=T.useState(null),[X,se]=T.useState(!1),[fe,we]=T.useState(null),[Ce,Z]=T.useState(!1),{data:W=[],isLoading:oe}=Ke({queryKey:["organisations"],queryFn:()=>df()}),{data:ee=[],isLoading:z}=Ke({queryKey:["portfolios"],queryFn:()=>XS()}),{data:ue=[],isLoading:Me}=Ke({queryKey:["programmes"],queryFn:()=>ff()}),{data:Ae=[],isLoading:Ye}=Ke({queryKey:["instruments",re==null?void 0:re.id],queryFn:()=>re?kT(re.id):Promise.resolve([]),enabled:!!re&&X}),{data:$e=[],isLoading:ut}=Ke({queryKey:["portfolioProgrammes",fe==null?void 0:fe.id],queryFn:()=>fe?OT(fe.id):Promise.resolve([]),enabled:!!fe&&Ce}),_=hs({resolver:ms(wU),defaultValues:{name:"",registrationNo:"",email:"",type:ot.Enterprise,status:Cn.Active}}),Y=hs({resolver:ms(_U),defaultValues:{name:"",description:"",organisationId:(Re=W[0])!=null&&Re.id?Number(W[0].id):101,ownerId:a}}),he=hs({resolver:ms(SU),defaultValues:{name:"",portfolioId:(pe=ee[0])!=null&&pe.id?Number(ee[0].id):201,status:bt.Approved,ownerId:a}}),ce=Ft({mutationFn:$=>v?AT({organisationId:v.id,name:$.name,registrationNo:$.registrationNo,email:$.email,type:Number($.type),status:$.status!==void 0?Number($.status):void 0}):ET($),onSuccess:()=>{e.invalidateQueries({queryKey:["organisations"]}),o(v?"Organisation updated":"Organisation registered successfully"),x(!1),S(null),_.reset()},onError:$=>{u("Failed to save organisation",String($))}}),Oe=Ft({mutationFn:$=>O?TT({portfolioId:O.id,name:$.name,description:$.description,organisationId:Number($.organisationId),ownerId:$.ownerId}):RT($),onSuccess:()=>{e.invalidateQueries({queryKey:["portfolios"]}),o(O?"Portfolio updated successfully":"Portfolio created successfully"),C(!1),N(null),Y.reset()},onError:$=>{u("Failed to save portfolio",String($))}}),Pe=Ft({mutationFn:$=>q?zT({programmeId:q.id,name:$.name,portfolioId:Number($.portfolioId),status:$.status!==void 0?Number($.status):void 0,ownerId:$.ownerId}):MT($),onSuccess:()=>{e.invalidateQueries({queryKey:["programmes"]}),o(q?"Programme updated successfully":"Programme created successfully"),F(!1),B(null),he.reset()},onError:$=>{u("Failed to save programme",String($))}}),qe=()=>{S(null),_.reset({name:"",registrationNo:"",email:"",type:ot.Enterprise,status:Cn.Active}),x(!0)},_e=$=>{S($),_.reset({name:$.name||"",registrationNo:$.registrationNo||"",email:$.email||"",type:$.type||ot.Enterprise,status:$.status??Cn.Active}),x(!0)},je=()=>{var $;N(null),Y.reset({name:"",description:"",organisationId:($=W[0])!=null&&$.id?Number(W[0].id):101,ownerId:a}),C(!0)},at=$=>{N($),Y.reset({name:$.name||"",description:$.description||"",organisationId:Number($.organisationId)||101,ownerId:$.ownedBy||a}),C(!0)},Tt=()=>{var $;B(null),he.reset({name:"",portfolioId:($=ee[0])!=null&&$.id?Number(ee[0].id):201,status:bt.Approved,ownerId:a}),F(!0)},Zt=$=>{B($),he.reset({name:$.name||"",portfolioId:Number($.portfolioId)||201,status:$.status??bt.Approved,ownerId:$.ownedBy||a}),F(!0)},J=W.filter($=>{var Ge,rt,qt;return((Ge=$.name)==null?void 0:Ge.toLowerCase().includes(p.toLowerCase()))||((rt=$.registrationNo)==null?void 0:rt.toLowerCase().includes(p.toLowerCase()))||((qt=$.email)==null?void 0:qt.toLowerCase().includes(p.toLowerCase()))});return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-[#0d121f]/90 p-5 rounded-2xl border border-slate-800/80 backdrop-blur-md shadow-xl",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("h2",{className:"text-xl font-bold text-white tracking-tight",children:"Governance & Structure"}),c.jsx("span",{className:"text-[11px] font-semibold text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-full border border-blue-500/30",children:"Directory"})]}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"Maintain Master Organisation Registry, Funder Portfolios, and Strategic Programmes hierarchy."})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[f==="organisations"&&c.jsxs("button",{onClick:qe,className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Register Organisation"]}),f==="portfolios"&&c.jsxs("button",{onClick:je,className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Create Portfolio"]}),f==="programmes"&&c.jsxs("button",{onClick:Tt,className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Create Programme"]})]})]}),c.jsxs("div",{className:"flex border-b border-slate-800/80 bg-[#0d121f]/80 px-4 rounded-2xl backdrop-blur-sm",children:[c.jsxs("button",{onClick:()=>m("organisations"),className:`py-3.5 px-4 text-xs font-semibold border-b-2 transition-colors flex items-center gap-2 cursor-pointer ${f==="organisations"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(cS,{className:"w-4 h-4"}),"Organisations Directory (",W.length,")"]}),c.jsxs("button",{onClick:()=>m("portfolios"),className:`py-3.5 px-4 text-xs font-semibold border-b-2 transition-colors flex items-center gap-2 cursor-pointer ${f==="portfolios"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(Pp,{className:"w-4 h-4"}),"Funder Portfolios (",ee.length,")"]}),c.jsxs("button",{onClick:()=>m("programmes"),className:`py-3.5 px-4 text-xs font-semibold border-b-2 transition-colors flex items-center gap-2 cursor-pointer ${f==="programmes"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(W0,{className:"w-4 h-4"}),"Programmes Hierarchy (",ue.length,")"]})]}),f==="organisations"&&c.jsxs("div",{className:"relative max-w-md",children:[c.jsx(pS,{className:"w-4 h-4 text-slate-500 absolute left-3 top-3"}),c.jsx("input",{type:"text",placeholder:"Search by organisation name, reg no, or email...",value:p,onChange:$=>y($.target.value),className:"w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"})]}),f==="organisations"&&c.jsx("div",{className:"bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 shadow-lg backdrop-blur-sm overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-[#0d121f]/95 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold text-[11px]",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Organisation Name"}),c.jsx("th",{className:"py-3.5 px-4",children:"Registration No"}),c.jsx("th",{className:"py-3.5 px-4",children:"Entity Type"}),c.jsx("th",{className:"py-3.5 px-4",children:"Contact Email"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Actions"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/60",children:oe?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-slate-500",children:"Loading registered organisations..."})}):J.length===0?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-slate-500",children:"No organisations found matching query."})}):J.map($=>{const Ge=Ml($.status);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5",children:[c.jsx("span",{className:"font-semibold text-white block text-xs",children:$.name}),c.jsxs("span",{className:"text-[10px] text-slate-500 font-mono",children:["ID: ",$.id]})]}),c.jsx("td",{className:"py-3.5 px-4 font-mono text-slate-300",children:$.registrationNo||"—"}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:"inline-flex items-center px-2.5 py-0.5 rounded-md text-[11px] font-medium bg-slate-900/80 text-slate-300 border border-slate-700/80",children:_k($.type)})}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300",children:c.jsxs("span",{className:"flex items-center gap-1.5 font-mono text-[11px]",children:[c.jsx(mS,{className:"w-3.5 h-3.5 text-slate-500"}),$.email]})}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${Ge.className}`,children:Ge.label})}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:c.jsxs("button",{onClick:()=>_e($),className:"inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-3 py-1.5 rounded-lg transition shadow-xs cursor-pointer",children:[c.jsx(f0,{className:"w-3 h-3"}),"Edit"]})})]},$.id)})})]})})}),f==="portfolios"&&c.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5",children:z?c.jsx("p",{className:"text-xs text-slate-500 col-span-3",children:"Loading portfolios..."}):ee.map($=>{const Ge=W.find(qt=>String(qt.id)===String($.organisationId)),rt=ue.filter(qt=>String(qt.portfolioId)===String($.id)).length;return c.jsxs("div",{className:"bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 shadow-lg backdrop-blur-sm flex flex-col justify-between hover:border-slate-700/80 transition-all",children:[c.jsxs("div",{className:"space-y-2",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsxs("span",{className:"text-[10px] font-mono uppercase tracking-wider text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-md border border-blue-500/30",children:["Portfolio #",$.id]}),c.jsxs("span",{className:"text-[11px] text-slate-400 font-medium",children:[rt," Programmes"]})]}),c.jsx("h3",{className:"text-base font-bold text-white leading-snug",children:$.name}),c.jsx("p",{className:"text-xs text-slate-400 leading-relaxed",children:$.description})]}),c.jsxs("div",{className:"mt-4 pt-3 border-t border-slate-800/80 space-y-2",children:[c.jsxs("div",{className:"flex items-center justify-between text-xs text-slate-400",children:[c.jsx("span",{className:"truncate max-w-[170px] font-medium text-slate-300",children:Ge?Ge.name:`Org #${$.organisationId}`}),c.jsx("span",{className:"font-mono text-[11px] text-slate-500",children:Kn($.createdAt)})]}),c.jsxs("div",{className:"flex items-center justify-between pt-1 text-xs",children:[c.jsxs("span",{className:"text-slate-400 font-medium truncate max-w-[190px]",children:["Owner: ",c.jsx("span",{className:"text-slate-200 font-mono text-[11px]",children:ya($.ownedBy)})]}),c.jsxs("div",{className:"flex items-center gap-1.5",children:[c.jsxs("button",{type:"button",onClick:()=>{we($),Z(!0)},className:"inline-flex items-center gap-1 text-xs font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer",children:[c.jsx(W0,{className:"w-3 h-3"}),"Programmes"]}),c.jsxs("button",{type:"button",onClick:()=>at($),className:"inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer",children:[c.jsx(f0,{className:"w-3 h-3"}),"Edit"]})]})]})]})]},$.id)})}),f==="programmes"&&c.jsx("div",{className:"bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 shadow-lg backdrop-blur-sm overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-[#0d121f]/95 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold text-[11px]",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Programme Name"}),c.jsx("th",{className:"py-3.5 px-4",children:"Parent Portfolio"}),c.jsx("th",{className:"py-3.5 px-4",children:"Governance Status"}),c.jsx("th",{className:"py-3.5 px-4",children:"Creation Date"}),c.jsx("th",{className:"py-3.5 px-4",children:"Owner Email"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Actions"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/60",children:Me?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-slate-500",children:"Loading programmes..."})}):ue.map($=>{const Ge=ee.find(qt=>String(qt.id)===String($.portfolioId)),rt=Xl($.status);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5",children:[c.jsx("span",{className:"font-semibold text-white block text-xs",children:$.name}),c.jsxs("span",{className:"text-[10px] text-slate-500 font-mono",children:["ID: ",$.id]})]}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300 font-medium",children:Ge?Ge.name:`Portfolio #${$.portfolioId}`}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${rt.className}`,children:rt.label})}),c.jsx("td",{className:"py-3.5 px-4 text-slate-400 font-mono text-[11px]",children:Kn($.createdAt)}),c.jsx("td",{className:"py-3.5 px-4 font-mono text-slate-300 font-medium text-[11px]",children:ya($.ownedBy)}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:c.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[c.jsxs("button",{type:"button",onClick:()=>{Q($),se(!0)},className:"inline-flex items-center gap-1 text-xs font-semibold text-blue-400 hover:text-white bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer",title:"View Funding Instruments",children:[c.jsx(Ic,{className:"w-3 h-3"}),"Instruments"]}),c.jsxs("button",{type:"button",onClick:()=>Zt($),className:"inline-flex items-center gap-1 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/80 hover:bg-slate-800 border border-slate-700/80 px-2.5 py-1 rounded-lg transition shadow-xs cursor-pointer",children:[c.jsx(f0,{className:"w-3 h-3"}),"Edit"]})]})})]},$.id)})})]})})}),c.jsx(hn,{isOpen:g,onClose:()=>x(!1),title:v?"Edit Organisation Profile":"Register New Organisation",subtitle:"Mandatory master data profile for Recipient or Funder entities (POPIA compliant)",children:c.jsxs("form",{onSubmit:_.handleSubmit($=>ce.mutate($)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Legal Organisation Name *"}),c.jsx("input",{type:"text",..._.register("name"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. AgriBio Southern Solutions"}),_.formState.errors.name&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:_.formState.errors.name.message})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Registration / CIPC Number *"}),c.jsx("input",{type:"text",..._.register("registrationNo"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono text-white bg-[#090d16]",placeholder:"2020/554129/08"}),_.formState.errors.registrationNo&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:_.formState.errors.registrationNo.message})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Entity Classification *"}),c.jsx("select",{..._.register("type"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:cN.map($=>c.jsx("option",{value:$.value,children:$.label},$.value))})]})]}),c.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Official Contact Email *"}),c.jsx("input",{type:"email",..._.register("email"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"governance@organisation.org.za"}),_.formState.errors.email&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:_.formState.errors.email.message})]}),v&&c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Operating Status *"}),c.jsx("select",{..._.register("status"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:bk.map($=>c.jsx("option",{value:$.value,children:$.label},$.value))})]})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>x(!1),className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:ce.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:ce.isPending?"Saving...":v?"Update Organisation":"Register Organisation"})]})]})}),c.jsx(hn,{isOpen:A,onClose:()=>{C(!1),N(null)},title:O?"Edit Funder Portfolio":"Create Funder Portfolio",subtitle:"Strategic allocation bucket grouping multiple programmatic initiatives",children:c.jsxs("form",{onSubmit:Y.handleSubmit($=>Oe.mutate($)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Portfolio Title *"}),c.jsx("input",{type:"text",...Y.register("name"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. National Green Hydrogen & Climate Fund"}),Y.formState.errors.name&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:Y.formState.errors.name.message})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Parent Organisation *"}),c.jsx("select",{...Y.register("organisationId"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:W.map($=>c.jsx("option",{value:$.id,children:$.name},$.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Owner Email *"}),c.jsx("input",{type:"text",...Y.register("ownerId"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono text-white bg-[#090d16]",placeholder:"khwezim@gmail.com"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Strategic Objective *"}),c.jsx("textarea",{rows:3,...Y.register("description"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"Define the overarching funding mandate and target impacts..."}),Y.formState.errors.description&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:Y.formState.errors.description.message})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>{C(!1),N(null)},className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Oe.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:Oe.isPending?"Saving...":O?"Update Portfolio":"Create Portfolio"})]})]})}),c.jsx(hn,{isOpen:j,onClose:()=>{F(!1),B(null)},title:q?"Edit Strategic Programme":"Create Strategic Programme",subtitle:"Programmatic operational entity funding distinct projects and awards",children:c.jsxs("form",{onSubmit:he.handleSubmit($=>Pe.mutate($)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Programme Title *"}),c.jsx("input",{type:"text",...he.register("name"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. DeepTech Venture Commercialisation"}),he.formState.errors.name&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:he.formState.errors.name.message})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Assign to Portfolio *"}),c.jsx("select",{...he.register("portfolioId"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:ee.map($=>c.jsx("option",{value:$.id,children:$.name},$.id))})]}),c.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Governance Status *"}),c.jsx("select",{...he.register("status"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:dN.map($=>c.jsx("option",{value:$.value,children:$.label},$.value))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Owner Email *"}),c.jsx("input",{type:"text",...he.register("ownerId"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono text-white bg-[#090d16]",placeholder:"khwezim@gmail.com"})]})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>{F(!1),B(null)},className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Pe.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:Pe.isPending?"Saving...":q?"Update Programme":"Create Programme"})]})]})}),c.jsx(hn,{isOpen:X,onClose:()=>{se(!1),Q(null)},title:`Funding Instruments: ${(re==null?void 0:re.name)||""}`,subtitle:`Authoritative funding instruments linked to Programme #${(re==null?void 0:re.id)||""}`,children:c.jsxs("div",{className:"space-y-4",children:[Ye?c.jsx("div",{className:"py-8 text-center text-xs text-slate-400",children:"Loading funding instruments..."}):Ae.length===0?c.jsx("div",{className:"py-8 text-center text-xs text-slate-500",children:"No funding instruments registered under this programme."}):c.jsx("div",{className:"space-y-2.5 max-h-[400px] overflow-y-auto",children:Ae.map($=>{const Ge=Ml($.status);return c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800 bg-[#0d121f] flex flex-col gap-1.5",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("span",{className:"font-mono text-xs font-bold text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded border border-blue-500/20",children:$.code||`INST-${$.id}`}),c.jsx("span",{className:"font-semibold text-xs text-white",children:$.name})]}),c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Ge.className}`,children:Ge.label})]}),$.description&&c.jsx("p",{className:"text-xs text-slate-400 leading-relaxed",children:$.description}),c.jsxs("div",{className:"flex items-center justify-between pt-2 border-t border-slate-800/80 text-[10px] text-slate-500 font-mono",children:[c.jsxs("span",{children:["Created by: ",ya($.createdBy)]}),c.jsx("span",{children:Kn($.createdAt)})]})]},$.id)})}),c.jsx("div",{className:"pt-2 border-t border-slate-800 flex justify-end",children:c.jsx("button",{type:"button",onClick:()=>{se(!1),Q(null)},className:"px-4 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Close"})})]})}),c.jsx(hn,{isOpen:Ce,onClose:()=>{Z(!1),we(null)},title:`Portfolio Programmes: ${(fe==null?void 0:fe.name)||""}`,subtitle:`Programmes allocated under Portfolio #${(fe==null?void 0:fe.id)||""}`,children:c.jsxs("div",{className:"space-y-4",children:[ut?c.jsx("div",{className:"py-8 text-center text-xs text-slate-400",children:"Loading portfolio programmes..."}):$e.length===0?c.jsx("div",{className:"py-8 text-center text-xs text-slate-500",children:"No programmes registered under this portfolio."}):c.jsx("div",{className:"space-y-2.5 max-h-[400px] overflow-y-auto",children:$e.map($=>{const Ge=Xl($.status);return c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800 bg-[#0d121f] flex items-center justify-between",children:[c.jsxs("div",{children:[c.jsx("span",{className:"font-semibold text-xs text-white block",children:$.name}),c.jsxs("span",{className:"text-[10px] text-slate-500 font-mono",children:["ID: ",$.id," • Owner: ",ya($.ownedBy)," • ",Kn($.createdAt)]})]}),c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Ge.className}`,children:Ge.label})]},$.id)})}),c.jsx("div",{className:"pt-2 border-t border-slate-800 flex justify-end",children:c.jsx("button",{type:"button",onClick:()=>{Z(!1),we(null)},className:"px-4 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Close"})})]})})]})},jU=xs({name:Et().min(4,"Project name must be at least 4 characters"),description:Et().min(10,"Provide a meaningful implementation description"),programmeId:_t().min(1,"Select a programme"),sector:_t().min(1),projectType:_t().min(1)}),EU=xs({title:Et().min(3,"Activity title required"),description:Et().min(5,"Description required"),category:Et().min(2,"Category required"),amount:_t().min(1,"Amount must be positive")}),AU=xs({name:Et().min(4,"Milestone deliverable required"),dueAt:Et().min(4,"Due date is mandatory"),priority:_t().min(1).max(4)}),CU=()=>{var ee;const e=Pa(),{success:n,error:r}=Lc(),[a,o]=T.useState(null),[u,f]=T.useState("activities"),[m,p]=T.useState("all"),[y,g]=T.useState(!1),[x,v]=T.useState(!1),[S,A]=T.useState(!1),{data:C=[]}=Ke({queryKey:["programmes"],queryFn:()=>ff()}),{data:O=[],isLoading:N}=Ke({queryKey:["projects",m],queryFn:()=>m!=="all"?DT(m):hf()}),j=a?a.id:null,{data:F=[]}=Ke({queryKey:["activities",j],queryFn:()=>j?LT(j):Promise.resolve([]),enabled:!!j}),{data:q=[]}=Ke({queryKey:["milestones",j],queryFn:()=>j?HT(j):Promise.resolve([]),enabled:!!j}),{data:B=[]}=Ke({queryKey:["indicators",j],queryFn:()=>j?BT(j):Promise.resolve([]),enabled:!!j}),{data:re=[]}=Ke({queryKey:["risks",j],queryFn:()=>j?$T(j):Promise.resolve([]),enabled:!!j}),Q=hs({resolver:ms(jU),defaultValues:{name:"",description:"",programmeId:(ee=C[0])!=null&&ee.id?Number(C[0].id):0,sector:Jt.AgriTech,projectType:cs.Innovation}}),X=hs({resolver:ms(EU),defaultValues:{title:"",description:"",category:"",amount:0}}),se=hs({resolver:ms(AU),defaultValues:{name:"",dueAt:new Date(Date.now()+2160*60*60*1e3).toISOString().slice(0,10),priority:yr.High}}),fe=Ft({mutationFn:z=>UT(z),onSuccess:z=>{e.invalidateQueries({queryKey:["projects"]}),n("Project registered",`Project "${z.name}" created successfully`),g(!1),Q.reset(),o(z)},onError:z=>{r("Failed to create project",String(z))}}),we=Ft({mutationFn:({projectId:z,status:ue})=>IT({projectId:z,status:ue}),onSuccess:(z,ue)=>{e.invalidateQueries({queryKey:["projects"]}),a&&o({...a,status:ue.status}),n("Status updated","Project governance status changed successfully")},onError:z=>{r("Failed to update status",String(z))}}),Ce=Ft({mutationFn:z=>PT(z),onSuccess:()=>{e.invalidateQueries({queryKey:["activities",j]}),n("Activity added","Activity registered to project work breakdown"),v(!1),X.reset()},onError:z=>{r("Failed to add activity",String(z))}}),Z=Ft({mutationFn:z=>qT(z),onSuccess:()=>{e.invalidateQueries({queryKey:["milestones",j]}),n("Milestone created","Contractual deliverable added to implementation schedule"),A(!1),se.reset()},onError:z=>{r("Failed to add milestone",String(z))}}),W=q.filter(z=>z.status===bt.Approved).length,oe=q.length>0?Math.round(W/q.length*100):0;return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-[#0d121f]/90 p-5 rounded-2xl border border-slate-800/80 backdrop-blur-md shadow-xl",children:[c.jsxs("div",{children:[c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("h2",{className:"text-xl font-bold text-white tracking-tight flex items-center gap-2",children:"Project & Milestone Management"}),c.jsx("span",{className:"text-[11px] font-semibold text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-full border border-blue-500/30",children:"Work Breakdown"})]}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"Work breakdown structure, contractual deliverables, milestone evidence tracking, and progress calculation."})]}),c.jsxs("button",{onClick:()=>g(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 transition cursor-pointer self-start sm:self-auto",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Create Project"]})]}),c.jsxs("div",{className:"grid grid-cols-1 lg:grid-cols-12 gap-6",children:[c.jsxs("div",{className:"lg:col-span-5 space-y-3",children:[c.jsxs("div",{className:"flex items-center justify-between gap-2 px-1",children:[c.jsxs("h3",{className:"text-xs font-bold text-slate-400 uppercase tracking-wider",children:["Projects Directory (",O.length,")"]}),c.jsxs("select",{value:m,onChange:z=>p(z.target.value==="all"?"all":z.target.value),className:"text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-2.5 py-1 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 max-w-[190px] [&>option]:bg-slate-900 [&>option]:text-white",children:[c.jsx("option",{value:"all",children:"All Programmes"}),C.map(z=>c.jsx("option",{value:z.id,children:z.name},z.id))]})]}),N?c.jsx("div",{className:"bg-[#0f1523]/80 p-6 rounded-2xl border border-slate-800/80 text-center text-xs text-slate-400",children:"Loading projects..."}):c.jsx("div",{className:"space-y-2.5",children:O.map(z=>{const ue=(a==null?void 0:a.id)===z.id,Me=Xl(z.status);return c.jsxs("div",{onClick:()=>o(z),className:`p-4 rounded-2xl border transition-all cursor-pointer bg-[#0f1523]/80 backdrop-blur-sm text-left ${ue?"border-blue-500 ring-2 ring-blue-500/20 shadow-lg shadow-blue-500/10":"border-slate-800/80 hover:border-slate-700 hover:shadow-md"}`,children:[c.jsxs("div",{className:"flex items-start justify-between gap-2",children:[c.jsx("span",{className:"font-bold text-sm text-white leading-snug",children:z.name}),c.jsx("span",{className:`shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Me.className}`,children:Me.label.split("/")[0]})]}),c.jsx("p",{className:"text-xs text-slate-400 mt-1 line-clamp-2 leading-relaxed",children:z.description}),c.jsxs("div",{className:"flex items-center gap-2 mt-3 pt-2.5 border-t border-slate-800/80 text-[11px] text-slate-400",children:[c.jsx("span",{className:"bg-slate-900/80 text-slate-300 border border-slate-700/80 px-2 py-0.5 rounded-md font-medium",children:fN(z.sector)}),c.jsx("span",{children:"•"}),c.jsx("span",{className:"text-slate-300",children:Sk(z.projectType)})]})]},z.id)})})]}),c.jsx("div",{className:"lg:col-span-7",children:a?c.jsxs("div",{className:"bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 shadow-xl backdrop-blur-md overflow-hidden sticky top-6",children:[c.jsxs("div",{className:"p-6 border-b border-slate-800/80 bg-[#0d121f]/90",children:[c.jsxs("div",{className:"flex items-start justify-between gap-3",children:[c.jsxs("div",{children:[c.jsxs("span",{className:"text-[10px] font-mono uppercase tracking-wider text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-md border border-blue-500/30",children:["Project #",a.id]}),c.jsx("h3",{className:"text-lg font-bold text-white mt-1.5 leading-snug",children:a.name}),c.jsx("p",{className:"text-xs text-slate-400 mt-1 leading-relaxed",children:a.description})]}),c.jsxs("div",{className:"shrink-0 text-right",children:[c.jsx("label",{className:"text-[10px] uppercase font-bold text-slate-400 block mb-1",children:"Approval Status"}),c.jsx("select",{value:a.status,onChange:z=>we.mutate({projectId:a.id,status:Number(z.target.value)}),className:"text-xs font-semibold bg-[#090d16] border border-slate-700/80 rounded-xl px-2.5 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 cursor-pointer",children:dN.map(z=>c.jsx("option",{value:z.value,children:z.label},z.value))})]})]}),c.jsxs("div",{className:"mt-5 pt-4 border-t border-slate-800/80",children:[c.jsxs("div",{className:"flex items-center justify-between text-xs mb-1.5",children:[c.jsxs("span",{className:"font-semibold text-slate-300 flex items-center gap-1.5",children:[c.jsx(qp,{className:"w-3.5 h-3.5 text-blue-400"}),"Calculated Progress (Milestone-based)"]}),c.jsxs("span",{className:"font-bold text-blue-400",children:[oe,"%"]})]}),c.jsx("div",{className:"w-full h-2 bg-slate-900 rounded-full overflow-hidden border border-slate-800",children:c.jsx("div",{className:"h-full bg-gradient-to-r from-blue-600 to-indigo-500 transition-all duration-300 rounded-full",style:{width:`${oe}%`}})}),c.jsxs("div",{className:"flex justify-between text-[10px] text-slate-500 mt-1",children:[c.jsxs("span",{children:[W," of ",q.length," milestones verified"]}),c.jsx("span",{children:"Target: 100% completion"})]})]})]}),c.jsxs("div",{className:"flex border-b border-slate-800/80 px-4 bg-[#0d121f]/60",children:[c.jsxs("button",{onClick:()=>f("activities"),className:`py-3 px-3 text-xs font-semibold border-b-2 transition flex items-center gap-1.5 cursor-pointer ${u==="activities"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(XA,{className:"w-3.5 h-3.5"}),"Work Activities (",F.length,")"]}),c.jsxs("button",{onClick:()=>f("milestones"),className:`py-3 px-3 text-xs font-semibold border-b-2 transition flex items-center gap-1.5 cursor-pointer ${u==="milestones"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(jC,{className:"w-3.5 h-3.5"}),"Milestones (",q.length,")"]}),c.jsxs("button",{onClick:()=>f("indicators"),className:`py-3 px-3 text-xs font-semibold border-b-2 transition flex items-center gap-1.5 cursor-pointer ${u==="indicators"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(vC,{className:"w-3.5 h-3.5"}),"Indicators (",B.length,")"]}),c.jsxs("button",{onClick:()=>f("risks"),className:`py-3 px-3 text-xs font-semibold border-b-2 transition flex items-center gap-1.5 cursor-pointer ${u==="risks"?"border-blue-500 text-blue-400":"border-transparent text-slate-400 hover:text-slate-200"}`,children:[c.jsx(uS,{className:"w-3.5 h-3.5"}),"Risks (",re.length,")"]})]}),c.jsxs("div",{className:"p-5 max-h-[500px] overflow-y-auto",children:[u==="activities"&&c.jsxs("div",{className:"space-y-4",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsx("span",{className:"text-xs font-bold text-slate-300",children:"Project Work Breakdown"}),c.jsxs("button",{onClick:()=>v(!0),className:"inline-flex items-center gap-1 text-xs font-semibold text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-3 py-1 rounded-xl transition cursor-pointer",children:[c.jsx(Rs,{className:"w-3.5 h-3.5"}),"Add Activity"]})]}),F.length===0?c.jsx("p",{className:"text-xs text-slate-500 py-4 text-center",children:"No activities recorded yet."}):c.jsx("div",{className:"space-y-2.5",children:F.map(z=>c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800/80 bg-[#0d121f]/90",children:[c.jsxs("div",{className:"flex items-start justify-between",children:[c.jsx("span",{className:"font-semibold text-xs text-white",children:z.title}),c.jsx("span",{className:"font-mono text-xs font-bold text-blue-400",children:ji(z.amount)})]}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:z.description}),c.jsxs("div",{className:"flex items-center justify-between mt-2 pt-2 border-t border-slate-800/80 text-[10px] text-slate-400",children:[c.jsx("span",{className:"bg-slate-900/80 px-2 py-0.5 rounded-md border border-slate-700/80 text-slate-300 font-medium",children:z.category}),c.jsxs("span",{children:["Date: ",Kn(z.performedAt)]})]})]},z.id))})]}),u==="milestones"&&c.jsxs("div",{className:"space-y-4",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsx("span",{className:"text-xs font-bold text-slate-300",children:"Contractual Milestones"}),c.jsxs("button",{onClick:()=>A(!0),className:"inline-flex items-center gap-1 text-xs font-semibold text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-3 py-1 rounded-xl transition cursor-pointer",children:[c.jsx(Rs,{className:"w-3.5 h-3.5"}),"Add Milestone"]})]}),q.length===0?c.jsx("p",{className:"text-xs text-slate-500 py-4 text-center",children:"No milestones scheduled."}):c.jsx("div",{className:"space-y-2.5",children:q.map(z=>{const ue=Xl(z.status),Me=wk(z.priority);return c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800/80 bg-[#0d121f]/90 space-y-2",children:[c.jsxs("div",{className:"flex items-start justify-between gap-2",children:[c.jsx("span",{className:"font-semibold text-xs text-white",children:z.name}),c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${ue.className}`,children:ue.label})]}),c.jsxs("div",{className:"flex items-center justify-between text-[11px] text-slate-400",children:[c.jsxs("span",{className:"flex items-center gap-1",children:[c.jsx(cC,{className:"w-3 h-3 text-slate-500"}),"Due: ",Kn(z.dueAt)]}),c.jsx("span",{className:`text-[10px] px-1.5 py-0.5 rounded-md font-medium border ${Me.className}`,children:Me.label})]})]},z.id)})})]}),u==="indicators"&&c.jsxs("div",{className:"space-y-3",children:[c.jsx("span",{className:"text-xs font-bold text-slate-300 block",children:"Assigned Project Indicators"}),B.length===0?c.jsx("p",{className:"text-xs text-slate-500 py-4 text-center",children:"No indicators attached."}):B.map(z=>{const ue=z.targetAmount>0?Math.round(z.actualAmount/z.targetAmount*100):0;return c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800/80 bg-[#0d121f]/90 space-y-1.5",children:[c.jsxs("div",{className:"flex justify-between text-xs font-semibold",children:[c.jsx("span",{className:"text-white",children:z.name}),c.jsxs("span",{className:ue>=90?"text-emerald-400":"text-amber-400",children:[ue,"%"]})]}),c.jsxs("div",{className:"flex justify-between text-[11px] text-slate-400",children:[c.jsxs("span",{children:["Baseline: ",z.baselineAmount]}),c.jsxs("span",{children:["Target: ",z.targetAmount]}),c.jsxs("span",{className:"font-bold text-slate-200",children:["Actual: ",z.actualAmount]})]})]},z.id)})]}),u==="risks"&&c.jsxs("div",{className:"space-y-3",children:[c.jsx("span",{className:"text-xs font-bold text-slate-300 block",children:"Project Risk Register (M09)"}),re.length===0?c.jsx("p",{className:"text-xs text-slate-500 py-4 text-center",children:"No project risks logged."}):re.map(z=>c.jsxs("div",{className:"p-3.5 rounded-xl border border-slate-800/80 bg-[#0d121f]/90 space-y-1",children:[c.jsxs("div",{className:"flex justify-between text-xs font-semibold text-white",children:[c.jsx("span",{children:z.name}),c.jsxs("span",{className:"text-[10px] font-bold text-rose-400 bg-rose-500/10 px-2 py-0.5 rounded-md border border-rose-500/30 font-mono",children:["Score: ",z.likelihood*z.impact," / 25"]})]}),c.jsx("p",{className:"text-xs text-slate-400",children:z.description}),z.mitigationAction&&c.jsxs("p",{className:"text-[11px] text-emerald-400 bg-emerald-500/10 p-2 rounded-lg mt-1.5 border border-emerald-500/20",children:[c.jsx("strong",{className:"text-emerald-300",children:"Mitigation:"})," ",z.mitigationAction]})]},z.id))]})]})]}):c.jsxs("div",{className:"bg-[#0f1523]/80 rounded-2xl border border-dashed border-slate-800 p-12 text-center text-slate-400 flex flex-col items-center justify-center",children:[c.jsx(Pp,{className:"w-10 h-10 text-slate-600 mb-3"}),c.jsx("p",{className:"text-sm font-semibold text-slate-200",children:"Select a project to inspect implementation"}),c.jsx("p",{className:"text-xs text-slate-500 mt-1",children:"View activities, milestones, indicators, and risk controls."})]})})]}),c.jsx(hn,{isOpen:y,onClose:()=>g(!1),title:"Create Funded Project",subtitle:"Operational entity under an approved Programme",children:c.jsxs("form",{onSubmit:Q.handleSubmit(z=>fe.mutate(z)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Project Name *"}),c.jsx("input",{type:"text",...Q.register("name"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. Drone Precision Seed & Thermal Scouting"}),Q.formState.errors.name&&c.jsx("p",{className:"text-[10px] text-rose-500 mt-1",children:Q.formState.errors.name.message})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Parent Programme *"}),c.jsx("select",{...Q.register("programmeId"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:C.map(z=>c.jsx("option",{value:z.id,children:z.name},z.id))})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Industry Sector *"}),c.jsx("select",{...Q.register("sector"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:oN.map(z=>c.jsx("option",{value:z.value,children:z.label},z.value))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Project Type *"}),c.jsx("select",{...Q.register("projectType"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:lN.map(z=>c.jsx("option",{value:z.value,children:z.label},z.value))})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Project Scope & Objectives *"}),c.jsx("textarea",{rows:3,...Q.register("description"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"Outline the operational work, methodology, and key milestones..."})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>g(!1),className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:fe.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:fe.isPending?"Creating...":"Register Project"})]})]})}),c.jsx(hn,{isOpen:x,onClose:()=>v(!1),title:"Add Activity to Implementation Plan",subtitle:`Registering activity under Project #${j}`,children:c.jsxs("form",{onSubmit:X.handleSubmit(z=>Ce.mutate({projectId:j,...z})),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Activity Title *"}),c.jsx("input",{type:"text",...X.register("title"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. Cleanroom Sensor Calibration"})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Budget Allocation (ZAR) *"}),c.jsx("input",{type:"number",...X.register("amount"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono text-white bg-[#090d16]"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Cost Category *"}),c.jsx("input",{type:"text",...X.register("category"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"Hardware / Field Testing"})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Description *"}),c.jsx("textarea",{rows:2,...X.register("description"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>v(!1),className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Ce.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:Ce.isPending?"Saving...":"Add Activity"})]})]})}),c.jsx(hn,{isOpen:S,onClose:()=>A(!1),title:"Define Contractual Milestone",subtitle:`Deliverable schedule for Project #${j}`,children:c.jsxs("form",{onSubmit:se.handleSubmit(z=>Z.mutate({projectId:j,...z})),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Milestone Deliverable Name *"}),c.jsx("input",{type:"text",...se.register("name"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]",placeholder:"e.g. Milestone 4: Prototype Pilot Validation Report"})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Contractual Due Date *"}),c.jsx("input",{type:"date",...se.register("dueAt"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-white bg-[#090d16]"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1",children:"Priority Level *"}),c.jsx("select",{...se.register("priority"),className:"w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium",children:xk.map(z=>c.jsx("option",{value:z.value,children:z.label},z.value))})]})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>A(!1),className:"px-3.5 py-2 text-xs font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 rounded-xl cursor-pointer",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Z.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-md shadow-blue-500/20 cursor-pointer",children:Z.isPending?"Saving...":"Define Milestone"})]})]})})]})},OU=xs({referenceNo:Et().min(4,"Approval / Reference number is mandatory (FR-M01-002)"),amount:_t().min(1,"Approved funding amount must be > 0 (FR-M01-003)"),fundingPeriod:Et().min(3,"Funding period is mandatory (e.g. 24 Months)"),organisationId:_t().min(1,"Select recipient organisation (FR-M01-006)"),programmeId:_t().min(1,"Select funding programme (FR-M01-005)")}),RU=xs({awardId:_t().min(1,"Select approved award"),totalValue:_t().min(1,"Total value is required"),effectiveAt:Et().min(4,"Effective date is mandatory"),expiresAt:Et().min(4,"Expiry date is mandatory")}),TU=xs({contractId:_t().min(1),externalSignatureId:Et().min(4,"Digital signature ID is required"),signedDocumentUrl:Et().url("Must be a valid document URL")}),kU=xs({projectId:_t().min(1,"Select project"),name:Et().min(3,"Budget category name required"),amount:_t().min(1,"Amount must be greater than zero")}),DU=xs({projectId:_t().min(1,"Select project"),milestoneId:_t().optional(),amount:_t().min(1,"Disbursement tranche amount required")}),MU=()=>{var _e,je,at,Tt,Zt;const e=Pa(),{success:n,error:r}=Lc(),[a,o]=T.useState("awards"),[u,f]=T.useState(!1),[m,p]=T.useState(!1),[y,g]=T.useState(!1),[x,v]=T.useState(null),[S,A]=T.useState(!1),[C,O]=T.useState(!1),{data:N=[],isLoading:j}=Ke({queryKey:["awards"],queryFn:()=>Yp(1,100)}),{data:F=[],isLoading:q}=Ke({queryKey:["contracts"],queryFn:()=>eN(1,100)}),{data:B=[],isLoading:re}=Ke({queryKey:["budgets"],queryFn:()=>tN(1,100)}),{data:Q=[],isLoading:X}=Ke({queryKey:["disbursements"],queryFn:()=>nN(1,100)}),{data:se=[]}=Ke({queryKey:["organisations"],queryFn:()=>df()}),{data:fe=[]}=Ke({queryKey:["programmes"],queryFn:()=>ff()}),{data:we=[]}=Ke({queryKey:["projects"],queryFn:()=>hf()}),{data:Ce=[]}=Ke({queryKey:["milestones-all"],queryFn:()=>WS()}),Z=hs({resolver:ms(OU),defaultValues:{referenceNo:"",amount:1e7,fundingPeriod:"24 Months",organisationId:(_e=se[0])!=null&&_e.id?Number(se[0].id):101,programmeId:(je=fe[0])!=null&&je.id?Number(fe[0].id):301}}),W=hs({resolver:ms(RU),defaultValues:{awardId:(at=N[0])!=null&&at.id?Number(N[0].id):501,totalValue:1e7,effectiveAt:new Date().toISOString().slice(0,10),expiresAt:new Date(Date.now()+365*24*60*60*1e3).toISOString().slice(0,10)}}),oe=hs({resolver:ms(TU),defaultValues:{contractId:0,externalSignatureId:"",signedDocumentUrl:""}}),ee=hs({resolver:ms(kU),defaultValues:{projectId:(Tt=we[0])!=null&&Tt.id?Number(we[0].id):401,name:"",amount:1e6}}),z=hs({resolver:ms(DU),defaultValues:{projectId:(Zt=we[0])!=null&&Zt.id?Number(we[0].id):401,amount:25e5}}),ue=Ft({mutationFn:J=>FT(J),onSuccess:J=>{e.invalidateQueries({queryKey:["awards"]}),e.invalidateQueries({queryKey:["contracts"]}),e.invalidateQueries({queryKey:["dashboard"]}),n("Award Registered & Contract Initiated",`Award ${J.referenceNo} recorded. Automated Contract workflow initiated (BR-002).`),f(!1),Z.reset()},onError:J=>{r("Award registration failed",String(J))}}),Me=Ft({mutationFn:J=>VT(J),onSuccess:()=>{e.invalidateQueries({queryKey:["contracts"]}),n("Contract generated","New legal agreement created in Draft state"),p(!1),W.reset()},onError:J=>{r("Contract creation failed",String(J))}}),Ae=Ft({mutationFn:J=>KT(J),onSuccess:()=>{e.invalidateQueries({queryKey:["contracts"]}),n("Contract Executed & Locked","Agreement locked as authoritative version (AT-02). Downstream budgets and disbursements enabled."),g(!1),oe.reset()},onError:J=>{r("Execution trigger failed",String(J))}}),Ye=Ft({mutationFn:J=>YT(J),onSuccess:()=>{e.invalidateQueries({queryKey:["budgets"]}),n("Budget line established","Budget line created against project"),A(!1),ee.reset()},onError:J=>{r("Budget creation failed",String(J))}}),$e=Ft({mutationFn:J=>JT(J),onSuccess:()=>{e.invalidateQueries({queryKey:["disbursements"]}),n("Disbursement Scheduled","Payment tranche added in Pending Review state"),O(!1),z.reset()},onError:J=>{r("Disbursement creation failed",String(J))}}),ut=Ft({mutationFn:J=>XT(J),onSuccess:()=>{e.invalidateQueries({queryKey:["disbursements"]}),e.invalidateQueries({queryKey:["dashboard"]}),n("Payment Approved & Released","Conditions precedent verified. EFT reference generated and disbursement ledger updated (AT-05).")},onError:J=>{r("Disbursement approval failed",String(J))}}),_=J=>{v(J),oe.reset({contractId:Number(J.id),externalSignatureId:`SIG-DIGITAL-${Math.floor(1e5+Math.random()*9e5)}`,signedDocumentUrl:`https://documents.ifri.khongisa.co.za/contracts/CTR-${J.id}-Signed.pdf`}),g(!0)},Y=async J=>{try{const Re=await QT(J);Re?(window.open(Re,"_blank","noopener,noreferrer"),n("Secure URL Retrieved","Temporary signed download link opened.")):r("Download Error","No active secure download link generated.")}catch(Re){r("Link Error",(Re==null?void 0:Re.message)||"Failed to fetch contract secure link.")}},he=async(J,Re)=>{try{await GT(J,Re),n("Document Attached",`Contract #${J} document uploaded successfully.`),e.invalidateQueries({queryKey:["contracts"]})}catch(pe){r("Upload Failed",(pe==null?void 0:pe.message)||"Contract file upload failed.")}},ce=async(J,Re)=>{try{await ZT({contractId:J,status:Re}),n("Contract Updated",`Contract status updated to ${Qw(Re).label}.`),e.invalidateQueries({queryKey:["contracts"]})}catch(pe){r("Update Failed",(pe==null?void 0:pe.message)||"Failed to update contract status.")}},Oe=N.reduce((J,Re)=>J+(Number(Re.amount)||0),0),Pe=Q.filter(J=>J.status===bt.Approved).reduce((J,Re)=>J+(Number(Re.amount)||0),0),qe=Q.filter(J=>J.status===bt.PendingReview).reduce((J,Re)=>J+(Number(Re.amount)||0),0);return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl space-y-5",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4",children:[c.jsxs("div",{children:[c.jsx("h2",{className:"text-2xl font-bold text-white tracking-tight flex items-center gap-2",children:"Grant & Financial Management"}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"End-to-end controlled execution thread: Funding Award Intake → Contracting → Budgeting → Condition-Checked Disbursement."})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[a==="awards"&&c.jsxs("button",{onClick:()=>f(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Register Approved Award"]}),a==="contracts"&&c.jsxs("button",{onClick:()=>p(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Draft Contract"]}),a==="budgets"&&c.jsxs("button",{onClick:()=>A(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Add Budget Line"]}),a==="disbursements"&&c.jsxs("button",{onClick:()=>O(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Schedule Disbursement"]})]})]}),c.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-3 pt-3 border-t border-slate-800/80 text-xs",children:[c.jsxs("div",{className:"p-3.5 rounded-xl bg-slate-900/60 border border-slate-800/80 flex items-center justify-between",children:[c.jsx("span",{className:"text-slate-400 font-medium",children:"Total Awards Registered"}),c.jsx("span",{className:"font-bold text-white font-mono",children:Ei(Oe)})]}),c.jsxs("div",{className:"p-3.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-between",children:[c.jsx("span",{className:"text-emerald-400 font-medium",children:"Disbursed (Verified & Paid)"}),c.jsx("span",{className:"font-bold text-emerald-300 font-mono",children:Ei(Pe)})]}),c.jsxs("div",{className:"p-3.5 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-between",children:[c.jsx("span",{className:"text-amber-400 font-medium",children:"Pending Release"}),c.jsx("span",{className:"font-bold text-amber-300 font-mono",children:Ei(qe)})]})]})]}),c.jsxs("div",{className:"flex gap-2 border-b border-slate-800/80 pb-3 overflow-x-auto",children:[c.jsxs("button",{onClick:()=>o("awards"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="awards"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(Ic,{className:"w-4 h-4"}),"Awards Intake (",N.length,")"]}),c.jsxs("button",{onClick:()=>o("contracts"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="contracts"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(fS,{className:"w-4 h-4"}),"Contracts & Agreements (",F.length,")"]}),c.jsxs("button",{onClick:()=>o("budgets"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="budgets"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(sC,{className:"w-4 h-4"}),"Budget Lines (",B.length,")"]}),c.jsxs("button",{onClick:()=>o("disbursements"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="disbursements"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(QA,{className:"w-4 h-4"}),"Disbursements & Approvals (",Q.length,")"]})]}),a==="awards"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Approval Reference #"}),c.jsx("th",{className:"py-3.5 px-4",children:"Recipient Organisation"}),c.jsx("th",{className:"py-3.5 px-4",children:"Funding Programme"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Approved Amount (ZAR)"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Date Registered"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:j?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-slate-500",children:"Loading registered awards..."})}):N.map(J=>{const Re=Xl(J.status);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5",children:[c.jsx("span",{className:"font-mono font-bold text-white block",children:J.referenceNo}),c.jsxs("span",{className:"text-[10px] text-slate-500 font-mono",children:["System ID: #",J.id]})]}),c.jsx("td",{className:"py-3.5 px-4 text-slate-200 font-medium",children:J.organisationName||`Org #${J.organisationId}`}),c.jsx("td",{className:"py-3.5 px-4 text-slate-400",children:J.programmeName||`Programme #${J.programmeId}`}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-white text-xs",children:ji(J.amount)}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Re.className}`,children:Re.label})}),c.jsx("td",{className:"py-3.5 px-5 text-right font-mono text-slate-400",children:Kn(J.createdAt)})]},J.id)})})]})})}),a==="contracts"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Contract ID"}),c.jsx("th",{className:"py-3.5 px-4",children:"Associated Award"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Total Contract Value"}),c.jsx("th",{className:"py-3.5 px-4",children:"Effective / Expiry"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Action"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:q?c.jsx("tr",{children:c.jsx("td",{colSpan:6,className:"py-8 text-center text-slate-500",children:"Loading contracts..."})}):F.map(J=>{const Re=Qw(J.status),pe=J.status===dn.Executed;return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5 font-mono font-bold text-white",children:["CTR-",J.id]}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-300 font-medium",children:["Award #",J.awardId]}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-white",children:ji(J.totalValue)}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-400 font-mono text-[11px]",children:[Kn(J.effectiveAt)," → ",Kn(J.expiresAt)]}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${Re.className}`,children:Re.label})}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:c.jsxs("div",{className:"flex items-center justify-end gap-1.5",children:[c.jsx("button",{type:"button",onClick:()=>Y(J.id),title:"Fetch Secure Download URL",className:"p-1.5 rounded-lg text-slate-400 hover:text-white bg-slate-900/60 hover:bg-slate-800 border border-slate-700/60 transition cursor-pointer",children:c.jsx(hS,{className:"w-3.5 h-3.5"})}),c.jsxs("label",{title:"Upload Executed Contract Document",className:"p-1.5 rounded-lg text-slate-400 hover:text-white bg-slate-900/60 hover:bg-slate-800 border border-slate-700/60 transition cursor-pointer inline-flex items-center",children:[c.jsx(xS,{className:"w-3.5 h-3.5"}),c.jsx("input",{type:"file",className:"hidden",onChange:$=>{var rt;const Ge=(rt=$.target.files)==null?void 0:rt[0];Ge&&he(J.id,Ge)}})]}),c.jsxs("select",{value:J.status,onChange:$=>ce(J.id,Number($.target.value)),className:"text-[10px] bg-slate-900/80 border border-slate-700/80 rounded-lg px-2 py-1 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 [&>option]:bg-slate-900 [&>option]:text-white cursor-pointer",title:"Update Status",children:[c.jsx("option",{value:dn.Draft,children:"Draft"}),c.jsx("option",{value:dn.PendingApproval,children:"Pending Approval"}),c.jsx("option",{value:dn.Active,children:"Active / Executed"}),c.jsx("option",{value:dn.Terminated,children:"Terminated"})]}),pe?c.jsxs("span",{className:"inline-flex items-center gap-1 text-[11px] text-emerald-400 font-semibold bg-emerald-500/10 px-2 py-1 rounded-lg border border-emerald-500/20",children:[c.jsx(HC,{className:"w-3 h-3"}),"Locked"]}):c.jsxs("button",{type:"button",onClick:()=>_(J),className:"inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-lg shadow-xs shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(bC,{className:"w-3 h-3"}),"Execute"]})]})})]},J.id)})})]})})}),a==="budgets"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Budget Line Category"}),c.jsx("th",{className:"py-3.5 px-4",children:"Project ID"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Approved Amount (ZAR)"}),c.jsx("th",{className:"py-3.5 px-4",children:"Date Established"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Created By"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:re?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"Loading budget lines..."})}):B.map(J=>c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsx("td",{className:"py-3.5 px-5 font-semibold text-white",children:J.name}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-400 font-mono",children:["Project #",J.projectId]}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-white",children:ji(J.amount)}),c.jsx("td",{className:"py-3.5 px-4 font-mono text-slate-400",children:Kn(J.createdAt)}),c.jsx("td",{className:"py-3.5 px-5 text-right font-mono text-slate-400",children:ya(J.createdBy)})]},J.id))})]})})}),a==="disbursements"&&c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:[c.jsx("div",{className:"p-4 border-b border-slate-800/80 bg-amber-500/10 flex items-center justify-between",children:c.jsxs("div",{className:"flex items-center gap-2 text-xs text-amber-300",children:[c.jsx(Hp,{className:"w-4 h-4 text-amber-400"}),c.jsx("span",{className:"font-semibold",children:"Disbursement Control:"}),c.jsx("span",{className:"text-amber-200/80",children:"Disbursement cannot be released unless contractual conditions are verified. Duplicate approvals blocked."})]})}),c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Disbursement ID"}),c.jsx("th",{className:"py-3.5 px-4",children:"Project ID"}),c.jsx("th",{className:"py-3.5 px-4",children:"Milestone Association"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Tranche Amount"}),c.jsx("th",{className:"py-3.5 px-4",children:"Payment Reference"}),c.jsx("th",{className:"py-3.5 px-4",children:"Approval Status"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Approval Action"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:X?c.jsx("tr",{children:c.jsx("td",{colSpan:7,className:"py-8 text-center text-slate-500",children:"Loading disbursements..."})}):Q.map(J=>{const Re=Xl(J.status),pe=J.status===bt.Approved;return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5 font-mono font-bold text-white",children:["DSB-",J.id]}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-300 font-mono",children:["Project #",J.projectId]}),c.jsx("td",{className:"py-3.5 px-4 text-slate-400 font-medium",children:J.milestoneId?`Milestone #${J.milestoneId}`:"Initial Advance Tranche"}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-white text-xs",children:ji(J.amount)}),c.jsx("td",{className:"py-3.5 px-4 font-mono text-slate-400 text-[11px]",children:J.paymentReference||c.jsx("span",{className:"text-slate-500 italic",children:"Pending Release"})}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Re.className}`,children:Re.label})}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:pe?c.jsxs("span",{className:"inline-flex items-center gap-1 text-xs text-emerald-400 font-semibold bg-emerald-500/10 px-2.5 py-1 rounded-lg border border-emerald-500/20",children:[c.jsx(X0,{className:"w-3.5 h-3.5"}),"Released"]}):c.jsxs("button",{onClick:()=>ut.mutate(J.id),disabled:ut.isPending,className:"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-lg shadow-md shadow-blue-500/20 transition",children:[c.jsx(X0,{className:"w-3.5 h-3.5"}),"Approve Payment"]})})]},J.id)})})]})})]}),c.jsx(hn,{isOpen:u,onClose:()=>f(!1),title:"Register Approved Funding Award",subtitle:"Step 1 of 3 — Award Details",children:c.jsxs("form",{onSubmit:Z.handleSubmit(J=>ue.mutate(J)),className:"space-y-4",children:[c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Approval / Reference Number *"}),c.jsx("input",{type:"text",...Z.register("referenceNo"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition",placeholder:"AWD-2026-NSI-088"}),Z.formState.errors.referenceNo&&c.jsx("p",{className:"text-[10px] text-rose-400 mt-1",children:Z.formState.errors.referenceNo.message})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Approved Funding Amount (ZAR) *"}),c.jsx("input",{type:"number",...Z.register("amount"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"}),Z.formState.errors.amount&&c.jsx("p",{className:"text-[10px] text-rose-400 mt-1",children:Z.formState.errors.amount.message})]})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Recipient Organisation [search] *"}),c.jsx("select",{...Z.register("organisationId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:se.map(J=>c.jsx("option",{value:J.id,children:J.name},J.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Funding Programme / Instrument *"}),c.jsx("select",{...Z.register("programmeId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:fe.map(J=>c.jsx("option",{value:J.id,children:J.name},J.id))})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Funding Period *"}),c.jsx("input",{type:"text",...Z.register("fundingPeriod"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition",placeholder:"e.g. 24 Months (1 Oct 2026 – 30 Sep 2028)"})]}),c.jsxs("div",{className:"p-3 rounded-xl bg-blue-500/10 border border-blue-500/20 text-[11px] text-blue-300 space-y-1",children:[c.jsx("span",{className:"font-bold block",children:"Workflow Automation:"}),c.jsx("span",{children:"Submitting this record will automatically initiate the Contract/Agreement workflow."})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>f(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:ue.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:ue.isPending?"Submitting...":"Submit Award & Initiate Contract"})]})]})}),c.jsx(hn,{isOpen:m,onClose:()=>p(!1),title:"Draft Contract Agreement",subtitle:"Convert approved funding decision into legally enforceable terms",children:c.jsxs("form",{onSubmit:W.handleSubmit(J=>Me.mutate(J)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Select Award *"}),c.jsx("select",{...W.register("awardId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:N.map(J=>c.jsxs("option",{value:J.id,children:[J.referenceNo," — ",ji(J.amount)]},J.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Contract Total Value (ZAR) *"}),c.jsx("input",{type:"number",...W.register("totalValue"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Effective Date *"}),c.jsx("input",{type:"date",...W.register("effectiveAt"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Expiry Date *"}),c.jsx("input",{type:"date",...W.register("expiresAt"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"})]})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>p(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Me.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:Me.isPending?"Generating...":"Create Contract"})]})]})}),c.jsx(hn,{isOpen:y,onClose:()=>g(!1),title:"Execute Contract & Lock Agreement",subtitle:`Locking Contract #${x==null?void 0:x.id} as Authoritative Version`,children:c.jsxs("form",{onSubmit:oe.handleSubmit(J=>Ae.mutate(J)),className:"space-y-4",children:[c.jsx("div",{className:"p-3.5 bg-slate-900/60 border border-slate-800/80 rounded-xl text-xs space-y-1 text-slate-300",children:c.jsxs("p",{children:[c.jsx("strong",{className:"text-white",children:"Obligation:"})," Once executed, the agreement is locked and downstream budget control and disbursement requests are enabled."]})}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"External Electronic Signature ID *"}),c.jsx("input",{type:"text",...oe.register("externalSignatureId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition",placeholder:"e.g. SIG-DOCUSIGN-993821"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Signed Document URL / Vault *"}),c.jsx("input",{type:"url",...oe.register("signedDocumentUrl"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>g(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Ae.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:Ae.isPending?"Executing...":"Confirm Execution & Lock"})]})]})}),c.jsx(hn,{isOpen:S,onClose:()=>A(!1),title:"Add Project Budget Line",subtitle:"Maintain approved expenditure breakdown and commitments",children:c.jsxs("form",{onSubmit:ee.handleSubmit(J=>Ye.mutate(J)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Associated Project *"}),c.jsx("select",{...ee.register("projectId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:we.map(J=>c.jsx("option",{value:J.id,children:J.name},J.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Budget Category Name *"}),c.jsx("input",{type:"text",...ee.register("name"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition",placeholder:"e.g. Specialized Equipment & Prototype Probes"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Approved Line Amount (ZAR) *"}),c.jsx("input",{type:"number",...ee.register("amount"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>A(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:Ye.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:Ye.isPending?"Saving...":"Add Budget Line"})]})]})}),c.jsx(hn,{isOpen:C,onClose:()=>O(!1),title:"Schedule Disbursement Tranche",subtitle:"Link disbursement to milestone verification and conditions precedent",children:c.jsxs("form",{onSubmit:z.handleSubmit(J=>$e.mutate(J)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Select Project *"}),c.jsx("select",{...z.register("projectId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:we.map(J=>c.jsx("option",{value:J.id,children:J.name},J.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Linked Milestone Deliverable"}),c.jsxs("select",{...z.register("milestoneId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:[c.jsx("option",{value:"",children:"-- Advance Tranche (No linked milestone) --"}),Ce.map(J=>c.jsx("option",{value:J.id,children:J.name},J.id))]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Tranche Amount (ZAR) *"}),c.jsx("input",{type:"number",...z.register("amount"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>O(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:$e.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:$e.isPending?"Scheduling...":"Schedule Disbursement"})]})]})})]})},zU=xs({projectId:_t().min(1,"Select project"),name:Et().min(5,"Indicator definition required"),unitOfMeasure:_t().min(1),baselineAmount:_t().min(0),targetAmount:_t().min(1,"Target must be > 0")}),UU=xs({projectId:_t().min(1,"Select project"),name:Et().min(5,"Risk title required"),description:Et().min(10,"Risk description required"),likelihood:_t().min(1).max(5),impact:_t().min(1).max(5)}),IU=()=>{var $e,ut;const e=Pa(),{success:n,error:r}=Lc(),[a,o]=T.useState("evidence"),u=T.useRef(null),[f,m]=T.useState(!1),[p,y]=T.useState(!1),[g,x]=T.useState(null),[v,S]=T.useState(0),[A,C]=T.useState(In.CompletionCertificate),[O,N]=T.useState(""),[j,F]=T.useState("all"),{data:q=[],isLoading:B}=Ke({queryKey:["indicators"],queryFn:()=>ek()}),{data:re=[],isLoading:Q}=Ke({queryKey:["evidence",j],queryFn:()=>j!=="all"?ak(j):rk()}),{data:X=[],isLoading:se}=Ke({queryKey:["risks"],queryFn:()=>sN()}),{data:fe=[]}=Ke({queryKey:["projects"],queryFn:()=>hf()}),{data:we=[]}=Ke({queryKey:["milestones-all"],queryFn:()=>WS()}),Ce=hs({resolver:ms(zU),defaultValues:{projectId:($e=fe[0])!=null&&$e.id?Number(fe[0].id):401,name:"",unitOfMeasure:$s.Count,baselineAmount:0,targetAmount:100}}),Z=hs({resolver:ms(UU),defaultValues:{projectId:(ut=fe[0])!=null&&ut.id?Number(fe[0].id):401,name:"",description:"",likelihood:ma.Medium,impact:pa.Moderate}}),W=Ft({mutationFn:_=>tk(_),onSuccess:()=>{e.invalidateQueries({queryKey:["indicators"]}),n("Indicator defined","Indicator added to project monitoring plan (M08)"),m(!1),Ce.reset()},onError:_=>{r("Failed to create indicator",String(_))}}),oe=Ft({mutationFn:({id:_,amount:Y})=>nk(_,Y),onSuccess:()=>{e.invalidateQueries({queryKey:["indicators"]}),n("Actual value recorded","M&E variance and indicator status recalculated (BR-009)"),x(null)},onError:_=>{r("Failed to record actual",String(_))}}),ee=Ft({mutationFn:_=>lk(_),onSuccess:()=>{e.invalidateQueries({queryKey:["evidence"]}),n("Evidence uploaded","Supporting file registered for reviewer inspection (Figure 10 / M14)")},onError:_=>{r("Upload failed",String(_))}}),z=Ft({mutationFn:({id:_,status:Y})=>ok({evidenceId:_,version:2,evidenceType:In.DeliverableProof,status:Y}),onSuccess:(_,Y)=>{e.invalidateQueries({queryKey:["evidence"]}),Y.status===bt.Approved?n("Evidence Approved & Verified","Approval recorded. Auto-triggers payment eligibility check in Disbursements."):n("Rework Requested","Evidence returned to recipient for correction."),N("")},onError:_=>{r("Review update failed",String(_))}}),ue=Ft({mutationFn:_=>WT(_),onSuccess:()=>{e.invalidateQueries({queryKey:["risks"]}),e.invalidateQueries({queryKey:["dashboard"]}),n("Risk registered","Risk item added to project register"),y(!1),Z.reset()},onError:_=>{r("Failed to log risk",String(_))}}),Me=_=>{var he;const Y=(he=_.target.files)==null?void 0:he[0];if(Y){const ce=new FormData;ce.append("file",Y),ce.append("projectId","401"),ce.append("milestoneId","803"),ce.append("evidenceType",String(A)),ee.mutate(ce)}},Ae=async _=>{try{const Y=await ik(_);Y?(window.open(Y,"_blank","noopener,noreferrer"),n("Secure URL Retrieved","Signed evidence download link opened.")):r("Download Error","No active secure download link generated.")}catch(Y){r("Link Error",(Y==null?void 0:Y.message)||"Failed to retrieve evidence secure link.")}},Ye=(_,Y)=>{if(!Y)return{label:"Pending",badge:"bg-slate-800/60 text-slate-400 border-slate-700/60"};const he=_/Y*100;return he>=90?{label:"Green (≥90%)",badge:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"}:he>=70?{label:"Amber (70–89%)",badge:"bg-amber-500/10 text-amber-400 border-amber-500/20"}:{label:"Red (<70%)",badge:"bg-rose-500/10 text-rose-400 border-rose-500/20"}};return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl",children:[c.jsxs("div",{children:[c.jsx("h2",{className:"text-2xl font-bold text-white tracking-tight flex items-center gap-2",children:"M&E, Evidence & Risk Management"}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register."})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[a==="indicators"&&c.jsxs("button",{onClick:()=>m(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Define Indicator"]}),a==="risks"&&c.jsxs("button",{onClick:()=>y(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:[c.jsx(Rs,{className:"w-4 h-4"}),"Add Risk to Register"]})]})]}),c.jsxs("div",{className:"flex gap-2 border-b border-slate-800/80 pb-3 overflow-x-auto",children:[c.jsxs("button",{onClick:()=>o("evidence"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="evidence"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(ZC,{className:"w-4 h-4"}),"Milestone Evidence & Review Queue"]}),c.jsxs("button",{onClick:()=>o("indicators"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="indicators"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(qp,{className:"w-4 h-4"}),"Indicators & Actuals (",q.length,")"]}),c.jsxs("button",{onClick:()=>o("risks"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${a==="risks"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(gS,{className:"w-4 h-4"}),"Risk Register & Heatmap (",X.length,")"]})]}),a==="evidence"&&c.jsx("div",{className:"space-y-6",children:c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:[c.jsxs("div",{className:"bg-slate-900/90 text-white px-6 py-4 flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-slate-800/80",children:[c.jsx("span",{className:"text-xs font-mono font-semibold tracking-wider text-blue-400 uppercase",children:"RECIPIENT PORTAL • Project XYZ • Milestone 3: Prototype Delivery"}),c.jsx("span",{className:"text-[11px] text-slate-400 font-mono",children:"Milestone Evidence Submission"})]}),c.jsxs("div",{className:"p-6 border-b border-slate-800/80 bg-slate-900/40",children:[c.jsxs("div",{className:"grid grid-cols-2 sm:grid-cols-5 gap-2 text-center text-xs font-semibold",children:[c.jsx("div",{className:"py-2.5 px-3 rounded-xl bg-slate-800/60 text-slate-400 border border-slate-700/50",children:"Planned"}),c.jsx("div",{className:"py-2.5 px-3 rounded-xl bg-slate-800/60 text-slate-400 border border-slate-700/50",children:"In Progress"}),c.jsx("div",{className:"py-2.5 px-3 rounded-xl bg-blue-600 text-white shadow-lg shadow-blue-600/30 ring-2 ring-blue-500/40",children:"Submitted"}),c.jsx("div",{className:"py-2.5 px-3 rounded-xl bg-amber-500/15 text-amber-300 border border-amber-500/30",children:"Under Review"}),c.jsx("div",{className:"py-2.5 px-3 rounded-xl bg-slate-900/40 text-slate-500 border border-dashed border-slate-800",children:"Verified & Paid"})]}),c.jsx("div",{className:"mt-4 flex items-center justify-between text-xs",children:c.jsxs("div",{className:"flex items-center gap-2 text-slate-300",children:[c.jsx("span",{className:"font-semibold",children:"Current status:"}),c.jsx("span",{className:"font-mono text-emerald-400 bg-emerald-500/10 px-2.5 py-1 rounded-lg border border-emerald-500/20 font-bold",children:"SUBMITTED — awaiting Programme Manager review"})]})})]}),c.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-slate-800/80",children:[c.jsxs("div",{className:"p-6 space-y-4",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsx("h4",{className:"text-sm font-bold text-white",children:"Upload Evidence"}),c.jsxs("div",{className:"flex items-center gap-1.5",children:[c.jsx("label",{className:"text-[11px] font-medium text-slate-400",children:"Type:"}),c.jsx("select",{value:A,onChange:_=>C(Number(_.target.value)),className:"text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-2.5 py-1.5 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 max-w-[180px] [&>option]:bg-slate-900 [&>option]:text-white",children:aN.map(_=>c.jsx("option",{value:_.value,children:_.label},_.value))})]})]}),c.jsx("p",{className:"text-xs text-slate-400",children:"Drag & drop or browse files. Accepted formats: PDF, JPG, DOCX, XLSX (Max 25MB)."}),c.jsxs("div",{onClick:()=>{var _;return(_=u.current)==null?void 0:_.click()},className:"border-2 border-dashed border-slate-700/80 hover:border-blue-500/80 bg-slate-900/40 hover:bg-blue-500/5 rounded-2xl p-8 text-center cursor-pointer transition flex flex-col items-center justify-center space-y-3",children:[c.jsx(xS,{className:"w-8 h-8 text-slate-400"}),c.jsxs("div",{children:[c.jsx("span",{className:"text-xs font-bold text-white block",children:"Click to Browse or Drag Files Here"}),c.jsx("span",{className:"text-[10px] text-slate-500",children:"Automated SHA-256 hash stamp applied on intake"})]}),c.jsx("button",{type:"button",className:"px-4 py-1.5 text-xs font-semibold text-white bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-xl shadow-xs transition",children:"Browse Files"})]}),c.jsx("input",{type:"file",ref:u,onChange:Me,className:"hidden",accept:".pdf,.jpg,.jpeg,.png,.docx,.xlsx,.zip"})]}),c.jsxs("div",{className:"p-6 space-y-4",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-2",children:[c.jsxs("div",{children:[c.jsx("h4",{className:"text-sm font-bold text-white",children:"Attached Evidence (v2)"}),c.jsxs("span",{className:"text-[11px] text-slate-400 font-mono",children:[re.length," files loaded"]})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[c.jsx("span",{className:"text-xs text-slate-400",children:"Milestone:"}),c.jsxs("select",{value:j,onChange:_=>F(_.target.value==="all"?"all":_.target.value),className:"text-xs bg-slate-900 border border-slate-700/80 rounded-xl px-2.5 py-1 text-white focus:outline-none focus:ring-1 focus:ring-blue-500 max-w-[200px] [&>option]:bg-slate-900 [&>option]:text-white",children:[c.jsx("option",{value:"all",children:"All Milestones"}),we.map(_=>c.jsx("option",{value:_.id,children:_.name||`Milestone #${_.id}`},_.id))]})]})]}),c.jsx("div",{className:"space-y-2.5",children:re.map(_=>c.jsxs("div",{className:"p-3 rounded-xl border border-slate-800/80 bg-slate-900/60 flex items-center justify-between gap-3 text-xs",children:[c.jsxs("div",{className:"flex items-center gap-2.5 min-w-0",children:[c.jsx(fS,{className:"w-4 h-4 text-blue-400 shrink-0"}),c.jsxs("div",{className:"min-w-0",children:[c.jsx("span",{className:"font-semibold text-slate-200 block truncate font-mono",children:_.fileName||`evidence_file_${_.id}.pdf`}),c.jsxs("span",{className:"text-[10px] text-slate-400 font-mono",children:[_.fileSize||"3.2 MB"," • v",_.version," • ",Nk(_.type)]})]})]}),c.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[c.jsx("span",{className:"inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold bg-amber-500/10 text-amber-400 border border-amber-500/20",children:"Under Review"}),c.jsx("button",{type:"button",onClick:()=>Ae(_.id),className:"p-1 rounded text-slate-400 hover:text-white hover:bg-slate-800 transition cursor-pointer",title:"Fetch Secure Signed Link",children:c.jsx(hS,{className:"w-3.5 h-3.5"})}),_.documentUrl&&c.jsx("a",{href:_.documentUrl,target:"_blank",rel:"noopener noreferrer",className:"text-slate-400 hover:text-white p-1 transition",title:"Download evidence file",children:c.jsx(mC,{className:"w-3.5 h-3.5"})})]})]},_.id))})]})]}),c.jsxs("div",{className:"p-6 border-t border-slate-800/80 bg-slate-900/40 space-y-4",children:[c.jsx("h4",{className:"text-sm font-bold text-white",children:"Programme Manager Review"}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Reviewer comment / rejection reason"}),c.jsx("input",{type:"text",value:O,onChange:_=>N(_.target.value),placeholder:"Mandatory if requesting rework; optional for approvals...",className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"})]}),c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-3 pt-2",children:[c.jsxs("div",{className:"flex items-center gap-3",children:[c.jsx("button",{onClick:()=>{var _;return z.mutate({id:((_=re[0])==null?void 0:_.id)||1201,status:bt.Rejected})},disabled:z.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-rose-600/90 hover:bg-rose-600 rounded-xl shadow-lg shadow-rose-600/20 transition",children:"Reject / Rework"}),c.jsx("button",{onClick:()=>{var _;return z.mutate({id:((_=re[0])==null?void 0:_.id)||1201,status:bt.Approved})},disabled:z.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:"Approve & Verify"})]}),c.jsx("p",{className:"text-[11px] text-slate-400 italic",children:"Approval auto-triggers payment eligibility check."})]})]})]})}),a==="indicators"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Indicator Name"}),c.jsx("th",{className:"py-3.5 px-4",children:"Project ID"}),c.jsx("th",{className:"py-3.5 px-4",children:"Unit of Measure"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Baseline"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Target"}),c.jsx("th",{className:"py-3.5 px-4 text-right",children:"Actual Recorded"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Action"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:B?c.jsx("tr",{children:c.jsx("td",{colSpan:8,className:"py-8 text-center text-slate-500",children:"Loading indicators..."})}):q.map(_=>{const Y=Ye(_.actualAmount,_.targetAmount);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsx("td",{className:"py-3.5 px-5 font-semibold text-white",children:_.name}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-400 font-mono",children:["Project #",_.projectId]}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300",children:jk(_.unitOfMeasure)}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono text-slate-400",children:_.baselineAmount}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-white",children:_.targetAmount}),c.jsx("td",{className:"py-3.5 px-4 text-right font-mono font-bold text-emerald-400 text-xs",children:_.actualAmount}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-semibold border ${Y.badge}`,children:Y.label})}),c.jsx("td",{className:"py-3.5 px-5 text-right",children:c.jsx("button",{onClick:()=>{x(_),S(_.actualAmount)},className:"inline-flex items-center gap-1 text-xs font-semibold text-slate-300 hover:text-white bg-slate-800/80 hover:bg-slate-700 px-3 py-1.5 rounded-lg border border-slate-700/60 transition",children:"Record Actual"})})]},_.id)})})]})})}),a==="risks"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Risk Title & Description"}),c.jsx("th",{className:"py-3.5 px-4",children:"Project ID"}),c.jsx("th",{className:"py-3.5 px-4 text-center",children:"Likelihood (1–5)"}),c.jsx("th",{className:"py-3.5 px-4 text-center",children:"Impact (1–5)"}),c.jsx("th",{className:"py-3.5 px-4 text-center",children:"Residual Score"}),c.jsx("th",{className:"py-3.5 px-4",children:"Risk Owner"}),c.jsx("th",{className:"py-3.5 px-5",children:"Mitigation Control"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:se?c.jsx("tr",{children:c.jsx("td",{colSpan:7,className:"py-8 text-center text-slate-500",children:"Loading risk register..."})}):X.map(_=>{const Y=_.likelihood*_.impact,he=Ek(_.likelihood,_.impact);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5",children:[c.jsx("span",{className:"font-semibold text-white block text-xs",children:_.name}),c.jsx("span",{className:"text-[11px] text-slate-400 line-clamp-1",children:_.description})]}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-400 font-mono",children:["Project #",_.projectId]}),c.jsx("td",{className:"py-3.5 px-4 text-center font-mono font-bold text-slate-200",children:_.likelihood}),c.jsx("td",{className:"py-3.5 px-4 text-center font-mono font-bold text-slate-200",children:_.impact}),c.jsx("td",{className:"py-3.5 px-4 text-center",children:c.jsxs("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-bold ${he.badge}`,children:[Y," / 25 (",he.level,")"]})}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300 font-medium",children:ya(_.owner)}),c.jsx("td",{className:"py-3.5 px-5 text-slate-400 text-[11px]",children:_.mitigationAction||"Standard internal audit controls applied."})]},_.id)})})]})})}),c.jsx(hn,{isOpen:f,onClose:()=>m(!1),title:"Define M&E Indicator",subtitle:"Logical Framework & Theory of Change metric",children:c.jsxs("form",{onSubmit:Ce.handleSubmit(_=>W.mutate(_)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Project *"}),c.jsx("select",{...Ce.register("projectId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:fe.map(_=>c.jsx("option",{value:_.id,children:_.name},_.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Indicator Statement *"}),c.jsx("input",{type:"text",...Ce.register("name"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition",placeholder:"e.g. Smallholder farmers adopting precision drone analytics"})]}),c.jsxs("div",{className:"grid grid-cols-3 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Unit of Measure *"}),c.jsx("select",{...Ce.register("unitOfMeasure"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:uN.map(_=>c.jsx("option",{value:_.value,children:_.label},_.value))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Baseline Value *"}),c.jsx("input",{type:"number",...Ce.register("baselineAmount"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Target Value *"}),c.jsx("input",{type:"number",...Ce.register("targetAmount"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>m(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:W.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:W.isPending?"Saving...":"Define Indicator"})]})]})}),c.jsx(hn,{isOpen:!!g,onClose:()=>x(null),title:"Record Indicator Actual Value",subtitle:(g==null?void 0:g.name)||"",children:c.jsxs("div",{className:"space-y-4",children:[c.jsxs("div",{className:"p-3.5 bg-slate-900/60 border border-slate-800/80 rounded-xl text-xs flex justify-between text-slate-300",children:[c.jsxs("span",{children:["Baseline: ",c.jsx("strong",{className:"text-white",children:g==null?void 0:g.baselineAmount})]}),c.jsxs("span",{children:["Target: ",c.jsx("strong",{className:"text-white",children:g==null?void 0:g.targetAmount})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Actual Value to Record *"}),c.jsx("input",{type:"number",value:v,onChange:_=>S(Number(_.target.value)),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>x(null),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"button",onClick:()=>g&&oe.mutate({id:g.id,amount:v}),disabled:oe.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:oe.isPending?"Recording...":"Submit Actual Value"})]})]})}),c.jsx(hn,{isOpen:p,onClose:()=>y(!1),title:"Add Risk to Risk Register",subtitle:"Identify and score project delivery risks",children:c.jsxs("form",{onSubmit:Z.handleSubmit(_=>ue.mutate(_)),className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Associated Project *"}),c.jsx("select",{...Z.register("projectId"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:fe.map(_=>c.jsx("option",{value:_.id,children:_.name},_.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Risk Title *"}),c.jsx("input",{type:"text",...Z.register("name"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition",placeholder:"e.g. Foreign Exchange Currency Volatility"})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Likelihood (1–5) *"}),c.jsx("select",{...Z.register("likelihood"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:yk.map(_=>c.jsx("option",{value:_.value,children:_.label},_.value))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Impact (1–5) *"}),c.jsx("select",{...Z.register("impact"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white",children:vk.map(_=>c.jsx("option",{value:_.value,children:_.label},_.value))})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Risk Description *"}),c.jsx("textarea",{rows:2,...Z.register("description"),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>y(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:ue.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition",children:ue.isPending?"Logging...":"Log Risk"})]})]})})]})},LU=()=>{const e=Pa(),{user:n}=Uc(),{success:r,error:a}=Lc(),[o,u]=T.useState("users"),[f,m]=T.useState(""),[p,y]=T.useState("all"),[g,x]=T.useState(null),[v,S]=T.useState(!1),A={email:f.trim()||void 0,status:p!=="all"?p:void 0},{data:C=[],isLoading:O,refetch:N}=Ke({queryKey:["users",p,f],queryFn:()=>ck(1,100,A)}),j=Ft({mutationFn:()=>dk(),onSuccess:()=>{r("Integration Sync Complete","Background user and tenant directory synchronized successfully."),e.invalidateQueries({queryKey:["users"]})},onError:ae=>{a("Sync Failed",(ae==null?void 0:ae.message)||"Failed to trigger background integration sync.")}}),F=Ft({mutationFn:({userId:ae,status:dt})=>uk(ae,dt),onSuccess:(ae,dt)=>{r("User Status Updated",`User status changed to ${Ml(dt.status).label}.`),e.invalidateQueries({queryKey:["users"]}),g&&g.id===dt.userId&&x({...g,status:dt.status})},onError:ae=>{a("Update Failed",(ae==null?void 0:ae.message)||"Could not update user status.")}}),[q,B]=T.useState(!1),[re,Q]=T.useState(""),[X,se]=T.useState(null),[fe,we]=T.useState(!1),[Ce,Z]=T.useState(""),[W,oe]=T.useState(""),[ee,z]=T.useState(""),[ue,Me]=T.useState(""),[Ae,Ye]=T.useState(""),[$e,ut]=T.useState(""),[_,Y]=T.useState(As.Email),{data:he=[],isLoading:ce}=Ke({queryKey:["invites"],queryFn:()=>hk(1,100)}),{data:Oe=[]}=Ke({queryKey:["organisations"],queryFn:()=>df(1,50)}),{data:Pe=[]}=Ke({queryKey:["awards"],queryFn:()=>Yp(1,50)}),{data:qe=[]}=Ke({queryKey:["contracts"],queryFn:()=>eN(1,50)}),_e=Ft({mutationFn:ae=>fk(ae),onSuccess:()=>{r("Invitation Dispatched","Statutory communications invitation sent successfully."),e.invalidateQueries({queryKey:["invites"]}),B(!1),Ye(""),ut("")},onError:ae=>{a("Dispatch Error",(ae==null?void 0:ae.message)||"Failed to dispatch invitation.")}}),je=async ae=>{if(ae.preventDefault(),!!re.trim())try{we(!0);const dt=await mk(re.trim());se(dt),r("Lookup Complete",`Retrieved ${dt.length} invitation records for applicant.`)}catch(dt){a("Lookup Error",(dt==null?void 0:dt.message)||"Applicant invitation lookup failed."),se([])}finally{we(!1)}},[at,Tt]=T.useState(fs.Connected),[Zt,J]=T.useState(!1),[Re,pe]=T.useState(As.Email),[$,Ge]=T.useState(us.Sent),[rt,qt]=T.useState(""),[Sr,Sn]=T.useState(""),[Hi,Kr]=T.useState([]),{data:er=[],isLoading:qa}=Ke({queryKey:["auditLogs"],queryFn:()=>pk()});return c.jsxs("div",{className:"space-y-6 max-w-7xl mx-auto",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl",children:[c.jsxs("div",{children:[c.jsx("h2",{className:"text-2xl font-bold text-white tracking-tight",children:"System Administration & Communications"}),c.jsx("p",{className:"text-xs text-slate-400 mt-1",children:"User access directory, statutory invitations, background integration sync, and audit ledger."})]}),c.jsxs("div",{className:"flex items-center gap-2",children:[o==="users"&&c.jsxs("button",{onClick:()=>j.mutate(),disabled:j.isPending,className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-slate-800 hover:bg-slate-700 border border-slate-700/80 rounded-xl shadow-md transition cursor-pointer disabled:opacity-50",children:[c.jsx(tp,{className:`w-3.5 h-3.5 ${j.isPending?"animate-spin":""}`}),j.isPending?"Syncing...":"Sync Integration"]}),o==="invites"&&c.jsxs("button",{onClick:()=>B(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(Ew,{className:"w-4 h-4"}),"Dispatch Invitation"]}),o==="notifications"&&c.jsxs("button",{onClick:()=>J(!0),className:"inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer",children:[c.jsx(Ew,{className:"w-4 h-4"}),"Dispatch Notification"]})]})]}),c.jsxs("div",{className:"flex gap-2 border-b border-slate-800/80 pb-3 overflow-x-auto",children:[c.jsxs("button",{onClick:()=>u("users"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 whitespace-nowrap cursor-pointer ${o==="users"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(yS,{className:"w-4 h-4"}),"User Directory (",C.length,")"]}),c.jsxs("button",{onClick:()=>u("invites"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 whitespace-nowrap cursor-pointer ${o==="invites"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(mS,{className:"w-4 h-4"}),"Communications & Invites (",he.length,")"]}),c.jsxs("button",{onClick:()=>u("notifications"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 whitespace-nowrap cursor-pointer ${o==="notifications"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(YA,{className:"w-4 h-4"}),"Gateways & Notifications"]}),c.jsxs("button",{onClick:()=>u("audit"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 whitespace-nowrap cursor-pointer ${o==="audit"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(RC,{className:"w-4 h-4"}),"Statutory Audit Trail (",er.length,")"]}),c.jsxs("button",{onClick:()=>u("profile"),className:`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 whitespace-nowrap cursor-pointer ${o==="profile"?"bg-blue-600 text-white shadow-lg shadow-blue-600/30":"text-slate-400 hover:text-slate-200 hover:bg-slate-800/50"}`,children:[c.jsx(bS,{className:"w-4 h-4"}),"Session & Claims"]})]}),o==="users"&&c.jsxs("div",{className:"space-y-4",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-4 rounded-2xl border border-slate-800/80 shadow-xl flex flex-col sm:flex-row items-center justify-between gap-3",children:[c.jsxs("div",{className:"relative w-full sm:w-80",children:[c.jsx(pS,{className:"w-4 h-4 text-slate-500 absolute left-3 top-2.5"}),c.jsx("input",{type:"text",value:f,onChange:ae=>m(ae.target.value),placeholder:"Search users by email...",className:"w-full pl-9 pr-3 py-1.5 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50"})]}),c.jsxs("div",{className:"flex items-center gap-2 w-full sm:w-auto",children:[c.jsx("span",{className:"text-xs font-semibold text-slate-400 whitespace-nowrap",children:"Filter Status:"}),c.jsxs("select",{value:p,onChange:ae=>y(ae.target.value==="all"?"all":Number(ae.target.value)),className:"text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-3 py-1.5 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:[c.jsx("option",{value:"all",children:"All Statuses"}),c.jsx("option",{value:Cn.Active,children:"Active"}),c.jsx("option",{value:Cn.Inactive,children:"Inactive"}),c.jsx("option",{value:Cn.Suspended,children:"Suspended"})]}),c.jsx("button",{onClick:()=>N(),className:"p-1.5 rounded-xl border border-slate-700 bg-slate-800/80 hover:bg-slate-700 text-slate-300 transition cursor-pointer",title:"Refresh user list",children:c.jsx(tp,{className:"w-4 h-4"})})]})]}),c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"User ID (UUID)"}),c.jsx("th",{className:"py-3.5 px-4",children:"Email"}),c.jsx("th",{className:"py-3.5 px-4",children:"Status"}),c.jsx("th",{className:"py-3.5 px-4",children:"Last Login"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Actions"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:O?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"Loading user directory..."})}):C.length===0?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"No users found matching current filters."})}):C.map(ae=>{const dt=Ml(ae.status);return c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsx("td",{className:"py-3.5 px-5 font-mono text-slate-300 text-[11px]",children:ae.id}),c.jsx("td",{className:"py-3.5 px-4 text-white font-medium",children:ae.email||c.jsx("span",{className:"text-slate-500 italic",children:"No email recorded"})}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${dt.className}`,children:dt.label})}),c.jsx("td",{className:"py-3.5 px-4 text-slate-400 font-mono text-[11px]",children:ae.lastLoginAt?Kn(ae.lastLoginAt):"Never"}),c.jsxs("td",{className:"py-3.5 px-5 text-right space-x-1.5 whitespace-nowrap",children:[c.jsxs("button",{onClick:()=>{x(ae),S(!0)},className:"inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-semibold text-slate-300 bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg transition cursor-pointer",children:[c.jsx(gC,{className:"w-3 h-3 text-blue-400"}),"Inspect"]}),ae.status===Cn.Active?c.jsx("button",{onClick:()=>F.mutate({userId:ae.id,status:Cn.Suspended}),disabled:F.isPending,className:"inline-flex items-center px-2.5 py-1 text-[11px] font-semibold text-amber-400 bg-amber-500/10 hover:bg-amber-500/20 border border-amber-500/20 rounded-lg transition cursor-pointer",children:"Suspend"}):c.jsx("button",{onClick:()=>F.mutate({userId:ae.id,status:Cn.Active}),disabled:F.isPending,className:"inline-flex items-center px-2.5 py-1 text-[11px] font-semibold text-emerald-400 bg-emerald-500/10 hover:bg-emerald-500/20 border border-emerald-500/20 rounded-lg transition cursor-pointer",children:"Activate"})]})]},ae.id)})})]})})})]}),o==="invites"&&c.jsxs("div",{className:"space-y-6",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-5 rounded-2xl border border-slate-800/80 shadow-xl",children:[c.jsx("h3",{className:"text-sm font-bold text-white mb-1",children:"Applicant Invitations Lookup"}),c.jsx("p",{className:"text-xs text-slate-400 mb-3",children:"Query invitations dispatched to a specific candidate or grantee using their unique Applicant User UUID."}),c.jsxs("form",{onSubmit:je,className:"flex gap-2",children:[c.jsx("input",{type:"text",value:re,onChange:ae=>Q(ae.target.value),placeholder:"Enter Applicant User ID (UUID)...",className:"flex-1 px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/50"}),c.jsx("button",{type:"submit",disabled:fe||!re.trim(),className:"px-4 py-2 text-xs font-semibold text-white bg-blue-600 hover:bg-blue-500 rounded-xl transition cursor-pointer disabled:opacity-50",children:fe?"Searching...":"Lookup Invites"})]}),X&&c.jsxs("div",{className:"mt-4 pt-4 border-t border-slate-800/80",children:[c.jsxs("span",{className:"text-xs font-semibold text-slate-300",children:["Lookup Results (",X.length," matches):"]}),X.length===0?c.jsx("p",{className:"text-xs text-slate-500 mt-2",children:"No invitations found for this applicant UUID."}):c.jsx("div",{className:"mt-2 space-y-2",children:X.map((ae,dt)=>c.jsxs("div",{className:"p-3 bg-slate-900/60 rounded-xl border border-slate-800 text-xs flex flex-col sm:flex-row sm:items-center justify-between gap-2",children:[c.jsxs("div",{children:[c.jsx("span",{className:"font-semibold text-white",children:ae.message}),c.jsxs("div",{className:"text-[11px] text-slate-400 mt-0.5",children:["Org #",ae.organisationId," • Award #",ae.awardId," • Contract #",ae.contractId]})]}),c.jsx("span",{className:"text-[10px] font-mono px-2 py-0.5 rounded bg-blue-500/10 text-blue-400 border border-blue-500/20",children:Kw(ae.platform)})]},dt))})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:[c.jsxs("div",{className:"px-5 py-3.5 border-b border-slate-800/80 bg-slate-900/40 flex items-center justify-between",children:[c.jsx("span",{className:"text-xs font-bold text-white uppercase tracking-wider",children:"Sent Invitations Ledger"}),c.jsxs("span",{className:"text-xs text-slate-400",children:[he.length," invitations dispatched"]})]}),c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Recipient (UUID)"}),c.jsx("th",{className:"py-3.5 px-4",children:"Channel"}),c.jsx("th",{className:"py-3.5 px-4",children:"Message"}),c.jsx("th",{className:"py-3.5 px-4",children:"Target Org / Grant"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Created Date"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:ce?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"Loading invitations ledger..."})}):he.length===0?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"No invitations recorded yet. Use “Dispatch Invitation” to send one."})}):he.map((ae,dt)=>c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsx("td",{className:"py-3.5 px-5 font-mono text-slate-300 text-[11px]",children:ae.recipient}),c.jsx("td",{className:"py-3.5 px-4",children:c.jsx("span",{className:"inline-flex items-center px-2 py-0.5 rounded text-[10px] font-semibold bg-blue-500/10 text-blue-400 border border-blue-500/20",children:Kw(ae.platform)})}),c.jsx("td",{className:"py-3.5 px-4 text-white font-medium max-w-xs truncate",children:ae.message}),c.jsxs("td",{className:"py-3.5 px-4 text-slate-400 text-[11px]",children:["Org #",ae.invitedOrganisationId||ae.organisationId," • Award #",ae.awardId]}),c.jsx("td",{className:"py-3.5 px-5 text-right font-mono text-slate-400 text-[11px]",children:ae.createdAt?Kn(ae.createdAt):"Recently"})]},dt))})]})})]})]}),o==="notifications"&&c.jsx("div",{className:"space-y-6",children:c.jsxs("div",{className:"grid grid-cols-1 sm:grid-cols-3 gap-4",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-5 rounded-2xl border border-slate-800/80 shadow-xl space-y-2",children:[c.jsxs("div",{className:"flex items-center justify-between",children:[c.jsx("span",{className:"text-xs font-semibold text-slate-400",children:"Email Gateway Status"}),c.jsx("span",{className:`inline-flex items-center px-2.5 py-0.5 rounded-full text-[10px] font-semibold border ${Yw(at).className}`,children:Yw(at).label})]}),c.jsxs("div",{className:"pt-1",children:[c.jsx("label",{className:"block text-[11px] font-medium text-slate-400 mb-1.5",children:"Update Gateway State:"}),c.jsx("select",{value:at,onChange:ae=>{const dt=Number(ae.target.value);Tt(dt),r("Gateway State Updated",`Email service transitioned to ${Pd(dt)}`)},className:"w-full text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-2.5 py-1.5 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:gk.map(ae=>c.jsx("option",{value:ae.value,children:ae.label},ae.value))})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-5 rounded-2xl border border-slate-800/80 shadow-xl space-y-1",children:[c.jsx("span",{className:"text-xs font-semibold text-slate-400",children:"Queued / Active Pipeline"}),c.jsx("div",{className:"text-2xl font-bold font-mono text-white",children:Hi.filter(ae=>ae.status===us.Pending||ae.status===us.Queued).length}),c.jsx("span",{className:"text-[11px] text-slate-400",children:"Automated retry scheduler active"})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-5 rounded-2xl border border-slate-800/80 shadow-xl space-y-1",children:[c.jsx("span",{className:"text-xs font-semibold text-slate-400",children:"Delivery Success Ratio"}),c.jsx("div",{className:"text-2xl font-bold font-mono text-emerald-400",children:"97.4%"}),c.jsx("span",{className:"text-[11px] text-slate-400",children:"Cross-platform SLA target: ≥95.0%"})]})]})}),o==="audit"&&c.jsx("div",{className:"bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden",children:c.jsx("div",{className:"overflow-x-auto",children:c.jsxs("table",{className:"w-full text-left text-xs border-collapse",children:[c.jsx("thead",{children:c.jsxs("tr",{className:"bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold",children:[c.jsx("th",{className:"py-3.5 px-5",children:"Audit ID"}),c.jsx("th",{className:"py-3.5 px-4",children:"Entity"}),c.jsx("th",{className:"py-3.5 px-4",children:"Action"}),c.jsx("th",{className:"py-3.5 px-4",children:"Performed By"}),c.jsx("th",{className:"py-3.5 px-5 text-right",children:"Timestamp"})]})}),c.jsx("tbody",{className:"divide-y divide-slate-800/50",children:qa?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"Loading audit logs..."})}):er.length===0?c.jsx("tr",{children:c.jsx("td",{colSpan:5,className:"py-8 text-center text-slate-500",children:"No audit events recorded."})}):er.map(ae=>c.jsxs("tr",{className:"hover:bg-slate-800/40 transition-colors",children:[c.jsxs("td",{className:"py-3.5 px-5 font-mono text-slate-300",children:["LOG-",ae.id]}),c.jsxs("td",{className:"py-3.5 px-4 text-white font-medium",children:[ae.entityType," #",ae.entityId]}),c.jsx("td",{className:"py-3.5 px-4 text-blue-400 font-mono",children:ae.action}),c.jsx("td",{className:"py-3.5 px-4 text-slate-300 font-mono text-[11px]",children:ae.userEmail}),c.jsx("td",{className:"py-3.5 px-5 text-right text-slate-400 font-mono text-[11px]",children:Kn(ae.createdAt)})]},ae.id))})]})})}),o==="profile"&&c.jsxs("div",{className:"space-y-4",children:[c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl space-y-4",children:[c.jsx("h3",{className:"text-sm font-bold text-white",children:"Active Session & Identity Token Claims"}),c.jsx("p",{className:"text-xs text-slate-400",children:"Cryptographic claims and assigned multi-tenant boundaries parsed from your active session."}),c.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 pt-2",children:[c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"User Identity & Tenant"}),c.jsxs("div",{className:"text-xs space-y-1.5 font-mono",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Subject (sub):"})," ",c.jsx("span",{className:"text-white",children:(n==null?void 0:n.id)||"Anonymous"})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Tenant Claim:"})," ",c.jsx("span",{className:"text-emerald-400 font-bold",children:(n==null?void 0:n.tenant)||"Default Root Tenant"})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Email:"})," ",c.jsx("span",{className:"text-white",children:ya(n)||"Not provided"})]})]})]}),c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"Deterministic Hasher Settings"}),c.jsxs("div",{className:"text-xs space-y-1.5 font-mono",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Min Hash Length:"})," ",c.jsx("span",{className:"text-white",children:os().HasherSettings.MinHashLength})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Encoding Algorithm:"})," ",c.jsx("span",{className:"text-blue-400",children:"Deterministic Base62 Salted"})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Active Salt:"})," ",c.jsx("span",{className:"text-slate-300 truncate block max-w-xs",children:os().HasherSettings.Salt?`${os().HasherSettings.Salt.slice(0,16)}...`:"Default Preset"})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Target NuGet Package:"})," ",c.jsx("span",{className:"text-white",children:"Hashids.net (C# Compatible)"})]})]})]})]})]}),c.jsxs("div",{className:"bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl space-y-4",children:[c.jsxs("div",{className:"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2",children:[c.jsxs("div",{children:[c.jsxs("h3",{className:"text-sm font-bold text-white flex items-center gap-2",children:[c.jsx(sO,{className:"w-4 h-4 text-emerald-400"}),"Deployment & Runtime Environment Configuration"]}),c.jsx("p",{className:"text-xs text-slate-400",children:"Priority hierarchy: Container Runtime Injected → /config.json → Build-Time (.env) → Defaults."})]}),c.jsxs("span",{className:`inline-flex items-center px-2.5 py-1 rounded-full text-[11px] font-semibold tracking-wide border ${os().ConfigSource==="runtime-injected"?"bg-emerald-500/10 text-emerald-400 border-emerald-500/30":os().ConfigSource==="runtime-fetched"?"bg-blue-500/10 text-blue-400 border-blue-500/30":os().ConfigSource==="build-env"?"bg-amber-500/10 text-amber-400 border-amber-500/30":"bg-slate-500/10 text-slate-400 border-slate-500/30"}`,children:["● Active Source: ",os().ConfigSource]})]}),c.jsxs("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 pt-2",children:[c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"Backend API Base URL"}),c.jsx("div",{className:"text-xs font-mono text-emerald-400 break-all",children:os().ApiSettings.BaseUrl})]}),c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"OIDC Authority (STS)"}),c.jsx("div",{className:"text-xs font-mono text-cyan-400 break-all",children:os().SecuritySettings.Authority})]}),c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"OIDC Client ID & Audience"}),c.jsxs("div",{className:"text-xs space-y-1 font-mono",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Client ID:"})," ",c.jsx("span",{className:"text-white",children:os().SecuritySettings.ClientId})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Audience:"})," ",c.jsx("span",{className:"text-white",children:os().SecuritySettings.Audience})]})]})]}),c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsx("span",{className:"text-[11px] font-semibold text-slate-400 uppercase tracking-wider block",children:"OIDC Scope & Secret"}),c.jsxs("div",{className:"text-xs space-y-1 font-mono",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Scope:"})," ",c.jsx("span",{className:"text-white",children:os().SecuritySettings.Scope||"Default"})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-500",children:"Secret Configured:"})," ",c.jsx("span",{className:"text-emerald-400",children:os().SecuritySettings.ClientSecret?"Yes (Encrypted/Set)":"No"})]})]})]})]})]})]}),c.jsx(hn,{isOpen:q,onClose:()=>B(!1),title:"Dispatch Statutory Invitation",children:c.jsxs("form",{onSubmit:ae=>{var dt,qi,Bi,E;if(ae.preventDefault(),!Ae||!$e){a("Validation Error","Recipient UUID and message content are mandatory.");return}_e.mutate({organisationId:Ce||((dt=Oe[0])!=null&&dt.id?Number(Oe[0].id):101),invitedOrganisationId:W||((qi=Oe[1])!=null&&qi.id?Number(Oe[1].id):102),awardId:ee||((Bi=Pe[0])!=null&&Bi.id?Number(Pe[0].id):301),contractId:ue||((E=qe[0])!=null&&E.id?Number(qe[0].id):401),recipient:Ae,message:$e,platform:_})},className:"space-y-4",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Recipient User UUID *"}),c.jsx("input",{type:"text",value:Ae,onChange:ae=>Ye(ae.target.value),placeholder:"e.g. 550e8400-e29b-41d4-a716-446655440000",className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 font-mono focus:outline-none focus:ring-2 focus:ring-blue-500/50",required:!0})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Dispatch Platform *"}),c.jsx("select",{value:_,onChange:ae=>Y(Number(ae.target.value)),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:iN.map(ae=>c.jsx("option",{value:ae.value,children:ae.label},ae.value))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Originating Organisation"}),c.jsx("select",{value:Ce,onChange:ae=>Z(ae.target.value),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:Oe.map(ae=>c.jsxs("option",{value:ae.id,children:[ae.name," (#",ae.id,")"]},ae.id))})]})]}),c.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Associated Award"}),c.jsx("select",{value:ee,onChange:ae=>z(ae.target.value),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:Pe.map(ae=>c.jsxs("option",{value:ae.id,children:["Award #",ae.id]},ae.id))})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Associated Contract"}),c.jsx("select",{value:ue,onChange:ae=>Me(ae.target.value),className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 [&>option]:bg-slate-900 [&>option]:text-white",children:qe.map(ae=>c.jsxs("option",{value:ae.id,children:["Contract #",ae.id]},ae.id))})]})]}),c.jsxs("div",{children:[c.jsx("label",{className:"block text-xs font-semibold text-slate-300 mb-1.5",children:"Invitation Message *"}),c.jsx("textarea",{rows:3,value:$e,onChange:ae=>ut(ae.target.value),placeholder:"State the purpose of the invite and recipient onboarding instructions...",className:"w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50",required:!0})]}),c.jsxs("div",{className:"pt-3 border-t border-slate-800/80 flex justify-end gap-2",children:[c.jsx("button",{type:"button",onClick:()=>B(!1),className:"px-4 py-2 text-xs font-medium text-slate-300 bg-slate-800/80 hover:bg-slate-700 rounded-xl transition",children:"Cancel"}),c.jsx("button",{type:"submit",disabled:_e.isPending,className:"px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition disabled:opacity-50",children:_e.isPending?"Sending...":"Send Invitation"})]})]})}),c.jsx(hn,{isOpen:v,onClose:()=>S(!1),title:"User Access & Security Details",children:g&&c.jsxs("div",{className:"space-y-4 text-xs",children:[c.jsxs("div",{className:"p-4 bg-slate-900/60 rounded-xl border border-slate-800 space-y-2",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-400 font-semibold block text-[11px] uppercase",children:"User UUID:"}),c.jsx("span",{className:"font-mono text-white text-xs break-all",children:g.id})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-400 font-semibold block text-[11px] uppercase",children:"Email:"}),c.jsx("span",{className:"text-white",children:g.email||"Not specified"})]}),c.jsxs("div",{className:"flex items-center justify-between pt-1",children:[c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-400 font-semibold block text-[11px] uppercase",children:"Status:"}),c.jsx("span",{className:`inline-flex items-center px-2 py-0.5 rounded text-[10px] font-semibold border ${Ml(g.status).className}`,children:Ml(g.status).label})]}),c.jsxs("div",{children:[c.jsx("span",{className:"text-slate-400 font-semibold block text-[11px] uppercase",children:"Last Login:"}),c.jsx("span",{className:"font-mono text-slate-300",children:g.lastLoginAt?Kn(g.lastLoginAt):"Never"})]})]})]}),c.jsx("div",{className:"pt-2 flex justify-end gap-2",children:c.jsx("button",{type:"button",onClick:()=>S(!1),className:"px-4 py-2 text-xs font-semibold text-white bg-slate-800 hover:bg-slate-700 rounded-xl transition",children:"Close"})})]})})]})};function PU(){return c.jsx(W4,{client:I3,children:c.jsx(F4,{children:c.jsx(LA,{children:c.jsx(dO,{children:c.jsxs(v4,{children:[c.jsx(qs,{path:"/",element:c.jsx(Aw,{})}),c.jsx(qs,{path:"/login",element:c.jsx(Aw,{})}),c.jsxs(qs,{element:c.jsx(PA,{children:c.jsx(hO,{})}),children:[c.jsx(qs,{path:"/dashboard",element:c.jsx(Ak,{})}),c.jsx(qs,{path:"/governance",element:c.jsx(NU,{})}),c.jsx(qs,{path:"/projects",element:c.jsx(CU,{})}),c.jsx(qs,{path:"/grants",element:c.jsx(MU,{})}),c.jsx(qs,{path:"/monitoring",element:c.jsx(IU,{})}),c.jsx(qs,{path:"/settings",element:c.jsx(LU,{})})]}),c.jsx(qs,{path:"*",element:c.jsx(j_,{to:"/dashboard",replace:!0})})]})})})})})}J0().finally(()=>{xE.createRoot(document.getElementById("root")).render(c.jsx(T.StrictMode,{children:c.jsx(PU,{})}))});
diff --git a/PostFundManagement.ConsoleUI/dist/config.json b/PostFundManagement.ConsoleUI/dist/config.json
new file mode 100644
index 0000000..dd80e47
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/dist/config.json
@@ -0,0 +1,16 @@
+{
+ "ApiSettings": {
+ "BaseUrl": ""
+ },
+ "SecuritySettings": {
+ "Authority": "",
+ "Audience": "",
+ "ClientId": "",
+ "ClientSecret": "",
+ "Scope": ""
+ },
+ "HasherSettings": {
+ "Salt": "",
+ "MinHashLength": ""
+ }
+}
diff --git a/PostFundManagement.ConsoleUI/dist/env-config.js b/PostFundManagement.ConsoleUI/dist/env-config.js
new file mode 100644
index 0000000..3299557
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/dist/env-config.js
@@ -0,0 +1,4 @@
+// Development and fallback runtime configuration.
+// In Docker / Kubernetes / Cloud Run deployments, this file is dynamically
+// populated at container startup by /docker-entrypoint.sh with deployment environment variables.
+window.__RUNTIME_CONFIG__ = window.__RUNTIME_CONFIG__ || {};
diff --git a/PostFundManagement.ConsoleUI/dist/favicon.svg b/PostFundManagement.ConsoleUI/dist/favicon.svg
new file mode 100644
index 0000000..f2fb02e
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/dist/favicon.svg
@@ -0,0 +1,106 @@
+
diff --git a/PostFundManagement.ConsoleUI/dist/index.html b/PostFundManagement.ConsoleUI/dist/index.html
new file mode 100644
index 0000000..91520a4
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/dist/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+ Post Fund Management Console
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PostFundManagement.ConsoleUI/docker-compose.yml b/PostFundManagement.ConsoleUI/docker-compose.yml
deleted file mode 100644
index 133395d..0000000
--- a/PostFundManagement.ConsoleUI/docker-compose.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-version: '3.8'
-
-services:
- pfm-console:
- build:
- context: .
- dockerfile: Dockerfile
- container_name: pfm-console
- ports:
- - "8076:3000"
- environment:
- # OIDC Identity Provider Configuration
- - OIDC_AUTHORITY=${OIDC_AUTHORITY}
- - OIDC_AUDIENCE=${OIDC_AUDIENCE}
- - OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
- - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
- # Backend API Configuration
- - API_BASE_URL=${API_BASE_URL}
- restart: unless-stopped
diff --git a/PostFundManagement.ConsoleUI/docker-entrypoint.sh b/PostFundManagement.ConsoleUI/docker-entrypoint.sh
index 8959ebc..7348227 100644
--- a/PostFundManagement.ConsoleUI/docker-entrypoint.sh
+++ b/PostFundManagement.ConsoleUI/docker-entrypoint.sh
@@ -1,4 +1,102 @@
#!/bin/sh
set -e
+# Target directory for Nginx static assets in container
+TARGET_DIR="/usr/share/nginx/html"
+
+# If not in standard nginx container, fallback to ./dist or ./public if present
+if [ ! -d "$TARGET_DIR" ]; then
+ if [ -d "./dist" ]; then
+ TARGET_DIR="./dist"
+ elif [ -d "./public" ]; then
+ TARGET_DIR="./public"
+ fi
+fi
+
+# Prioritize deployment environment variables from all common naming patterns:
+# 1. API Base URL
+API_BASE_URL="${VITE_API_BASE_URL:-${API_BASE_URL:-${ApiSettings__BaseUrl:-${ApiSettings_BaseUrl:-${PFM_API_BASE_URL:-}}}}}"
+
+# 2. OIDC Security Authority
+OIDC_AUTHORITY="${VITE_OIDC_AUTHORITY:-${OIDC_AUTHORITY:-${SecuritySettings__Authority:-${SecuritySettings_Authority:-${AUTHORITY:-}}}}}"
+
+# 3. OIDC Audience
+OIDC_AUDIENCE="${VITE_OIDC_AUDIENCE:-${OIDC_AUDIENCE:-${SecuritySettings__Audience:-${SecuritySettings_Audience:-${AUDIENCE:-}}}}}"
+
+# 4. OIDC Client ID
+OIDC_CLIENT_ID="${VITE_OIDC_CLIENT_ID:-${OIDC_CLIENT_ID:-${SecuritySettings__ClientId:-${SecuritySettings_ClientId:-${CLIENT_ID:-}}}}}"
+
+# 5. OIDC Client Secret
+OIDC_CLIENT_SECRET="${VITE_OIDC_CLIENT_SECRET:-${OIDC_CLIENT_SECRET:-${SecuritySettings__ClientSecret:-${SecuritySettings_ClientSecret:-${CLIENT_SECRET:-}}}}}"
+
+# 6. OIDC Scope
+OIDC_SCOPE="${VITE_OIDC_SCOPE:-${OIDC_SCOPE:-${SecuritySettings__Scope:-${SecuritySettings_Scope:-${SCOPE:-}}}}}"
+
+# 7. Hasher Settings
+HASHER_SALT="${VITE_HASHER_SALT:-${HASHER_SALT:-${HasherSettings__Salt:-${HasherSettings_Salt:-}}}}}"
+HASHER_MIN_LENGTH="${VITE_HASHER_MIN_LENGTH:-${HASHER_MIN_LENGTH:-${HasherSettings__MinLength:-${HasherSettings_MinLength:-}}}}}"
+
+if [ -d "$TARGET_DIR" ]; then
+ echo "[docker-entrypoint] Generating runtime configuration files in $TARGET_DIR..."
+
+ # Generate env-config.js for instant, synchronous window.__RUNTIME_CONFIG__ initialization
+ cat < "$TARGET_DIR/env-config.js"
+// Generated dynamically at container startup from deployment environment variables
+window.__RUNTIME_CONFIG__ = {
+ API_BASE_URL: "${API_BASE_URL}",
+ VITE_API_BASE_URL: "${API_BASE_URL}",
+ OIDC_AUTHORITY: "${OIDC_AUTHORITY}",
+ VITE_OIDC_AUTHORITY: "${OIDC_AUTHORITY}",
+ OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
+ VITE_OIDC_AUDIENCE: "${OIDC_AUDIENCE}",
+ OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
+ VITE_OIDC_CLIENT_ID: "${OIDC_CLIENT_ID}",
+ OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
+ VITE_OIDC_CLIENT_SECRET: "${OIDC_CLIENT_SECRET}",
+ OIDC_SCOPE: "${OIDC_SCOPE}",
+ VITE_OIDC_SCOPE: "${OIDC_SCOPE}",
+ HASHER_SALT: "${HASHER_SALT}",
+ VITE_HASHER_SALT: "${HASHER_SALT}",
+ HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
+ VITE_HASHER_MIN_LENGTH: "${HASHER_MIN_LENGTH}",
+ ApiSettings: {
+ BaseUrl: "${API_BASE_URL}"
+ },
+ SecuritySettings: {
+ Authority: "${OIDC_AUTHORITY}",
+ Audience: "${OIDC_AUDIENCE}",
+ ClientId: "${OIDC_CLIENT_ID}",
+ ClientSecret: "${OIDC_CLIENT_SECRET}",
+ Scope: "${OIDC_SCOPE}"
+ },
+ HasherSettings: {
+ Salt: "${HASHER_SALT}",
+ MinHashLength: "${HASHER_MIN_LENGTH}"
+ }
+};
+EOF
+
+ # Generate config.json for REST-based runtime retrieval
+ cat < "$TARGET_DIR/config.json"
+{
+ "ApiSettings": {
+ "BaseUrl": "${API_BASE_URL}"
+ },
+ "SecuritySettings": {
+ "Authority": "${OIDC_AUTHORITY}",
+ "Audience": "${OIDC_AUDIENCE}",
+ "ClientId": "${OIDC_CLIENT_ID}",
+ "ClientSecret": "${OIDC_CLIENT_SECRET}",
+ "Scope": "${OIDC_SCOPE}"
+ },
+ "HasherSettings": {
+ "Salt": "${HASHER_SALT}",
+ "MinHashLength": "${HASHER_MIN_LENGTH}"
+ }
+}
+EOF
+
+ echo "[docker-entrypoint] Successfully wrote $TARGET_DIR/env-config.js and $TARGET_DIR/config.json."
+fi
+
exec "$@"
diff --git a/PostFundManagement.ConsoleUI/favicon.svg b/PostFundManagement.ConsoleUI/favicon.svg
new file mode 100644
index 0000000..f2fb02e
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/favicon.svg
@@ -0,0 +1,106 @@
+
diff --git a/PostFundManagement.ConsoleUI/index.html b/PostFundManagement.ConsoleUI/index.html
index 7def44a..e736a4e 100644
--- a/PostFundManagement.ConsoleUI/index.html
+++ b/PostFundManagement.ConsoleUI/index.html
@@ -1,6 +1,6 @@
-
+
Post Fund Management Console
@@ -9,10 +9,18 @@
-
-
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/PostFundManagement.ConsoleUI/metadata.json b/PostFundManagement.ConsoleUI/metadata.json
index 3bd9e8b..c3d8881 100644
--- a/PostFundManagement.ConsoleUI/metadata.json
+++ b/PostFundManagement.ConsoleUI/metadata.json
@@ -2,5 +2,5 @@
"name": "Post Fund Management Console",
"description": "Enterprise backoffice portal for Funders to manage portfolios, programmes, projects, grants, disbursements, and evidence workflows.",
"requestFramePermissions": [],
- "majorCapabilities": [ "MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API" ]
+ "majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"]
}
diff --git a/PostFundManagement.ConsoleUI/nginx.conf b/PostFundManagement.ConsoleUI/nginx.conf
index 766b06d..dff07e9 100644
--- a/PostFundManagement.ConsoleUI/nginx.conf
+++ b/PostFundManagement.ConsoleUI/nginx.conf
@@ -12,17 +12,17 @@ server {
gzip_proxied expired no-cache no-store private auth;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/json image/svg+xml;
- # Cache control for static assets
- location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
- expires 1y;
- access_log off;
- add_header Cache-Control "public";
+ # Never cache dynamic runtime configuration files or index.html
+ location ~* ^/(?:config\.json|env-config\.js|index\.html)$ {
+ expires -1;
+ add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
- # Disable caching for appsettings.json so changes take effect immediately
- location = /appsettings.json {
- expires -1;
- add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
+ # Cache control for static assets (CSS, JS, fonts, images)
+ location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|woff|woff2|ttf|eot)$ {
+ expires 1y;
+ access_log off;
+ add_header Cache-Control "public, immutable";
}
# HTML5 SPA History Mode Support
diff --git a/PostFundManagement.ConsoleUI/package.json b/PostFundManagement.ConsoleUI/package.json
index de23d33..0f2bb0c 100644
--- a/PostFundManagement.ConsoleUI/package.json
+++ b/PostFundManagement.ConsoleUI/package.json
@@ -13,12 +13,13 @@
"dependencies": {
"@google/genai": "^2.4.0",
"@hookform/resolvers": "^5.9.1",
- "@tailwindcss/vite": "^4.3.3",
+ "@tailwindcss/vite": "^4.1.14",
"@tanstack/react-query": "^5.102.8",
"@vitejs/plugin-react": "^5.0.4",
"axios": "^1.20.0",
"dotenv": "^17.2.3",
"express": "^4.21.2",
+ "hashids": "^2.3.0",
"lucide-react": "^0.546.0",
"motion": "^12.23.24",
"oidc-client-ts": "^3.5.0",
@@ -26,19 +27,16 @@
"react-dom": "^19.0.1",
"react-hook-form": "^7.87.0",
"react-router-dom": "^7.18.3",
+ "tailwindcss": "^4.1.14",
"vite": "^6.2.3",
"zod": "^4.5.4"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^22.14.0",
- "@vitejs/plugin-basic-ssl": "^2.3.0",
- "autoprefixer": "^10.5.6",
+ "autoprefixer": "^10.4.21",
"esbuild": "^0.25.0",
- "postcss": "^8.5.28",
- "tailwindcss": "^4.3.3",
"tsx": "^4.21.0",
- "typescript": "~5.8.2",
- "vite": "^6.2.3"
+ "typescript": "~5.8.2"
}
}
diff --git a/PostFundManagement.ConsoleUI/public/config.json b/PostFundManagement.ConsoleUI/public/config.json
new file mode 100644
index 0000000..dd80e47
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/public/config.json
@@ -0,0 +1,16 @@
+{
+ "ApiSettings": {
+ "BaseUrl": ""
+ },
+ "SecuritySettings": {
+ "Authority": "",
+ "Audience": "",
+ "ClientId": "",
+ "ClientSecret": "",
+ "Scope": ""
+ },
+ "HasherSettings": {
+ "Salt": "",
+ "MinHashLength": ""
+ }
+}
diff --git a/PostFundManagement.ConsoleUI/public/env-config.js b/PostFundManagement.ConsoleUI/public/env-config.js
new file mode 100644
index 0000000..3299557
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/public/env-config.js
@@ -0,0 +1,4 @@
+// Development and fallback runtime configuration.
+// In Docker / Kubernetes / Cloud Run deployments, this file is dynamically
+// populated at container startup by /docker-entrypoint.sh with deployment environment variables.
+window.__RUNTIME_CONFIG__ = window.__RUNTIME_CONFIG__ || {};
diff --git a/PostFundManagement.ConsoleUI/public/favicon.svg b/PostFundManagement.ConsoleUI/public/favicon.svg
new file mode 100644
index 0000000..f2fb02e
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/public/favicon.svg
@@ -0,0 +1,106 @@
+
diff --git a/PostFundManagement.ConsoleUI/src/App.tsx b/PostFundManagement.ConsoleUI/src/App.tsx
index 740d91f..0189855 100644
--- a/PostFundManagement.ConsoleUI/src/App.tsx
+++ b/PostFundManagement.ConsoleUI/src/App.tsx
@@ -14,38 +14,38 @@ import { MonitoringPage } from './pages/MonitoringPage';
import { SettingsPage } from './pages/SettingsPage';
export default function App() {
- return (
-
-
-
-
-
- {/* Public OIDC Login / Landing Page */}
- } />
- } />
+ return (
+
+
+
+
+
+ {/* Public OIDC Login / Landing Page */}
+ } />
+ } />
- {/* Protected Backoffice Application Shell */}
-
-
-
- }
- >
- } />
- } />
- } />
- } />
- } />
- } />
-
+ {/* Protected Backoffice Application Shell */}
+
+
+
+ }
+ >
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
- {/* Fallback Redirect */}
- } />
-
-
-
-
-
- );
+ {/* Fallback Redirect */}
+ } />
+
+
+
+
+
+ );
}
diff --git a/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx b/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
index 54fd376..b23f18b 100644
--- a/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
+++ b/PostFundManagement.ConsoleUI/src/auth/AuthProvider.tsx
@@ -1,31 +1,33 @@
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
import { UserManager, WebStorageStateStore, User as OidcUser } from 'oidc-client-ts';
import { Navigate, useLocation } from 'react-router-dom';
-import { getSecuritySettings, loadRuntimeConfig } from '../config';
+import { getSecuritySettings, loadRuntimeConfig, onConfigChanged } from '../config';
import { parseJwtClaims } from './jwt';
+import { BrandLogo } from '../components/ui/BrandLogo';
export interface AuthUserProfile {
- id: string;
- sub: string;
- name: string;
- email: string;
- role: string;
- organisationName?: string;
- avatarUrl?: string;
+ id: string;
+ sub: string;
+ name: string;
+ email: string;
+ role: string;
+ organisationName?: string;
+ tenant?: string;
+ avatarUrl?: string;
}
export type UserClaims = Record;
interface AuthContextType {
- user: AuthUserProfile | null;
- sub: string;
- claims: UserClaims;
- accessToken: string | null;
- isAuthenticated: boolean;
- isLoading: boolean;
- login: () => Promise;
- logout: () => Promise;
- authError: string | null;
+ user: AuthUserProfile | null;
+ sub: string;
+ claims: UserClaims;
+ accessToken: string | null;
+ isAuthenticated: boolean;
+ isLoading: boolean;
+ login: () => Promise;
+ logout: () => Promise;
+ authError: string | null;
}
const AuthContext = createContext(undefined);
@@ -36,240 +38,261 @@ export const LOCAL_STORAGE_SUB_KEY = 'pfm_user_sub';
export const LOCAL_STORAGE_CLAIMS_KEY = 'pfm_user_claims';
export function getCurrentUserSub(): string {
- if (typeof window !== 'undefined') {
- return localStorage.getItem(LOCAL_STORAGE_SUB_KEY) || '';
- }
- return '';
+ if (typeof window !== 'undefined') {
+ return localStorage.getItem(LOCAL_STORAGE_SUB_KEY) || '';
+ }
+ return '';
}
export function getCurrentUserClaims(): UserClaims {
- if (typeof window !== 'undefined') {
- const raw = localStorage.getItem(LOCAL_STORAGE_CLAIMS_KEY);
- if (raw) {
- try {
- return JSON.parse(raw);
- } catch {
- return {};
- }
- }
+ if (typeof window !== 'undefined') {
+ const raw = localStorage.getItem(LOCAL_STORAGE_CLAIMS_KEY);
+ if (raw) {
+ try {
+ return JSON.parse(raw);
+ } catch {
+ return {};
+ }
}
- return {};
+ }
+ return {};
}
let userManagerInstance: UserManager | null = null;
-function getUserManager(): UserManager {
- if (!userManagerInstance && typeof window !== 'undefined') {
- const sec = getSecuritySettings();
- const origin = window.location.origin;
+export function resetUserManager(): void {
+ userManagerInstance = null;
+}
- userManagerInstance = new UserManager({
- authority: sec.Authority,
- client_id: sec.ClientId,
- client_secret: sec.ClientSecret,
- redirect_uri: `${origin}/dashboard`,
- post_logout_redirect_uri: `${origin}/`,
- response_type: 'code',
- scope: 'openid profile roles email address pfm-api-dev',
- extraQueryParams: {
- audience: sec.Audience,
- },
- userStore: new WebStorageStateStore({ store: window.localStorage }),
- automaticSilentRenew: false,
- });
- }
- return userManagerInstance!;
+// Automatically reset UserManager when runtime configuration is updated
+onConfigChanged(() => {
+ resetUserManager();
+});
+
+export function getUserManager(): UserManager {
+ if (!userManagerInstance && typeof window !== 'undefined') {
+ const sec = getSecuritySettings();
+ const origin = window.location.origin;
+
+ userManagerInstance = new UserManager({
+ authority: sec.Authority,
+ client_id: sec.ClientId,
+ client_secret: sec.ClientSecret,
+ redirect_uri: `${origin}/dashboard`,
+ post_logout_redirect_uri: `${origin}/`,
+ response_type: 'code',
+ scope: sec.Scope || 'openid profile roles email address pfm-api-dev',
+ extraQueryParams: {
+ audience: sec.Audience,
+ },
+ userStore: new WebStorageStateStore({ store: window.localStorage }),
+ automaticSilentRenew: false,
+ });
+ }
+ return userManagerInstance!;
}
export const AuthProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- const [user, setUser] = useState(null);
- const [sub, setSub] = useState('');
- const [claims, setClaims] = useState({});
- const [accessToken, setAccessToken] = useState(null);
- const [isLoading, setIsLoading] = useState(true);
- const [authError, setAuthError] = useState(null);
+ const [user, setUser] = useState(null);
+ const [sub, setSub] = useState('');
+ const [claims, setClaims] = useState({});
+ const [accessToken, setAccessToken] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [authError, setAuthError] = useState(null);
- // Initialize auth state and check for OIDC callback
- useEffect(() => {
- const initAuth = async () => {
- try {
- await loadRuntimeConfig();
+ // Initialize auth state and check for OIDC callback
+ useEffect(() => {
+ const initAuth = async () => {
+ try {
+ await loadRuntimeConfig();
- // Check for URL query code indicating OIDC redirect callback
- const urlParams = new URLSearchParams(window.location.search);
- if (urlParams.has('code') && urlParams.has('state')) {
- const mgr = getUserManager();
- const oidcUser: OidcUser = await mgr.signinCallback();
+ // Check for URL query code indicating OIDC redirect callback
+ const urlParams = new URLSearchParams(window.location.search);
+ if (urlParams.has('code') && urlParams.has('state')) {
+ const mgr = getUserManager();
+ const oidcUser: OidcUser = await mgr.signinCallback();
- if (oidcUser && oidcUser.access_token) {
- const token = oidcUser.access_token;
- setAccessToken(token);
- localStorage.setItem(LOCAL_STORAGE_TOKEN_KEY, token);
+ if (oidcUser && oidcUser.access_token) {
+ const token = oidcUser.access_token;
+ setAccessToken(token);
+ localStorage.setItem(LOCAL_STORAGE_TOKEN_KEY, token);
- // Merge claims from profile and decoded JWT token
- const decodedTokenClaims = parseJwtClaims(token);
- const mergedClaims: UserClaims = {
- ...decodedTokenClaims,
- ...oidcUser.profile,
- };
+ // Merge claims from profile and decoded JWT token
+ const decodedTokenClaims = parseJwtClaims(token);
+ const mergedClaims: UserClaims = {
+ ...decodedTokenClaims,
+ ...oidcUser.profile,
+ };
- const userSub = (oidcUser.profile?.sub || mergedClaims.sub || '') as string;
- setSub(userSub);
- setClaims(mergedClaims);
+ const userSub = (oidcUser.profile?.sub || mergedClaims.sub || '') as string;
+ setSub(userSub);
+ setClaims(mergedClaims);
- localStorage.setItem(LOCAL_STORAGE_SUB_KEY, userSub);
- localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(mergedClaims));
+ localStorage.setItem(LOCAL_STORAGE_SUB_KEY, userSub);
+ localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(mergedClaims));
- const profile: AuthUserProfile = {
- id: userSub || 'user-1',
- sub: userSub,
- name: oidcUser.profile.name || oidcUser.profile.preferred_username || (mergedClaims.name as string) || 'Authorized User',
- email: oidcUser.profile.email || (mergedClaims.email as string) || '',
- role: (oidcUser.profile.role as string) || (mergedClaims.role as string) || (Array.isArray(mergedClaims.roles) ? mergedClaims.roles[0] : 'Programme Sponsor'),
- organisationName: (mergedClaims.organisation as string) || (mergedClaims.org as string) || undefined,
- };
+ const tenantClaim = (mergedClaims.tenant as string) || (oidcUser.profile?.tenant as string) || undefined;
- setUser(profile);
- localStorage.setItem(LOCAL_STORAGE_USER_KEY, JSON.stringify(profile));
+ const profile: AuthUserProfile = {
+ id: userSub || 'user-1',
+ sub: userSub,
+ name: oidcUser.profile.name || oidcUser.profile.preferred_username || (mergedClaims.name as string) || 'Authorized User',
+ email: oidcUser.profile.email || (mergedClaims.email as string) || '',
+ role: (oidcUser.profile.role as string) || (mergedClaims.role as string) || (Array.isArray(mergedClaims.roles) ? mergedClaims.roles[0] : 'Programme Sponsor'),
+ organisationName: (mergedClaims.organisation as string) || (mergedClaims.org as string) || undefined,
+ tenant: tenantClaim,
+ };
- // Clean up code and state from URL
- window.history.replaceState({}, document.title, window.location.pathname);
- setIsLoading(false);
- return;
- }
- }
+ setUser(profile);
+ localStorage.setItem(LOCAL_STORAGE_USER_KEY, JSON.stringify(profile));
- // Check local storage for persisted session
- const storedToken = localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
- const storedUser = localStorage.getItem(LOCAL_STORAGE_USER_KEY);
- let storedSub = localStorage.getItem(LOCAL_STORAGE_SUB_KEY);
- const storedClaimsRaw = localStorage.getItem(LOCAL_STORAGE_CLAIMS_KEY);
-
- if (storedToken) {
- setAccessToken(storedToken);
-
- let parsedClaims: UserClaims = {};
- if (storedClaimsRaw) {
- try {
- parsedClaims = JSON.parse(storedClaimsRaw);
- } catch { }
- } else {
- parsedClaims = parseJwtClaims(storedToken);
- localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(parsedClaims));
- }
- setClaims(parsedClaims);
-
- if (!storedSub && parsedClaims.sub) {
- storedSub = String(parsedClaims.sub);
- localStorage.setItem(LOCAL_STORAGE_SUB_KEY, storedSub);
- }
- if (storedSub) {
- setSub(storedSub);
- }
-
- if (storedUser) {
- try {
- setUser(JSON.parse(storedUser));
- } catch { }
- }
- }
- } catch (err: unknown) {
- console.warn('OIDC initialization notice:', err);
- setAuthError(err instanceof Error ? err.message : 'Auth initialization notice');
- } finally {
- setIsLoading(false);
- }
- };
-
- initAuth();
- }, []);
-
- const login = async () => {
- try {
- setAuthError(null);
- const mgr = getUserManager();
- await mgr.signinRedirect();
- } catch (err: unknown) {
- console.warn('OIDC Redirect encountered:', err);
- // In sandboxed preview iframe, navigation to third-party STS domain can be blocked by CSP/frame-ancestors.
- if (typeof window !== 'undefined' && window.self !== window.top) {
- const errorMsg =
- 'Identity Provider redirect was restricted inside this preview frame. For testing with Duende STS (https://sts.security.khongisa.co.za), open the application in a new tab or Docker container.';
- setAuthError(errorMsg);
- } else {
- setAuthError(err instanceof Error ? err.message : 'Identity Provider redirect error');
- }
+ // Clean up code and state from URL
+ window.history.replaceState({}, document.title, window.location.pathname);
+ setIsLoading(false);
+ return;
+ }
}
+
+ // Check local storage for persisted session
+ const storedToken = localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
+ const storedUser = localStorage.getItem(LOCAL_STORAGE_USER_KEY);
+ let storedSub = localStorage.getItem(LOCAL_STORAGE_SUB_KEY);
+ const storedClaimsRaw = localStorage.getItem(LOCAL_STORAGE_CLAIMS_KEY);
+
+ if (storedToken) {
+ setAccessToken(storedToken);
+
+ let parsedClaims: UserClaims = {};
+ if (storedClaimsRaw) {
+ try {
+ parsedClaims = JSON.parse(storedClaimsRaw);
+ } catch {}
+ } else {
+ parsedClaims = parseJwtClaims(storedToken);
+ localStorage.setItem(LOCAL_STORAGE_CLAIMS_KEY, JSON.stringify(parsedClaims));
+ }
+ setClaims(parsedClaims);
+
+ if (!storedSub && parsedClaims.sub) {
+ storedSub = String(parsedClaims.sub);
+ localStorage.setItem(LOCAL_STORAGE_SUB_KEY, storedSub);
+ }
+ if (storedSub) {
+ setSub(storedSub);
+ }
+
+ if (storedUser) {
+ try {
+ const parsedUser = JSON.parse(storedUser);
+ if (!parsedUser.tenant && parsedClaims.tenant) {
+ parsedUser.tenant = parsedClaims.tenant;
+ }
+ setUser(parsedUser);
+ } catch {}
+ }
+ }
+ } catch (err: unknown) {
+ console.warn('OIDC initialization notice:', err);
+ setAuthError(err instanceof Error ? err.message : 'Auth initialization notice');
+ } finally {
+ setIsLoading(false);
+ }
};
- const logout = async () => {
- try {
- localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY);
- localStorage.removeItem(LOCAL_STORAGE_USER_KEY);
- localStorage.removeItem(LOCAL_STORAGE_SUB_KEY);
- localStorage.removeItem(LOCAL_STORAGE_CLAIMS_KEY);
- setUser(null);
- setSub('');
- setClaims({});
- setAccessToken(null);
+ initAuth();
+ }, []);
- try {
- const mgr = getUserManager();
- await mgr.signoutRedirect();
- } catch {
- // Local state cleared
- }
- } catch (err) {
- console.error('Logout error:', err);
- }
- };
+ const login = async () => {
+ try {
+ setAuthError(null);
+ await loadRuntimeConfig();
+ resetUserManager();
+ const mgr = getUserManager();
+ await mgr.signinRedirect();
+ } catch (err: unknown) {
+ console.warn('OIDC Redirect encountered:', err);
+ // In sandboxed preview iframe, navigation to external identity provider domain can be blocked by CSP/frame-ancestors.
+ if (typeof window !== 'undefined' && window.self !== window.top) {
+ const errorMsg =
+ 'Identity Provider redirect was restricted inside this preview frame. To complete single sign-on authentication, open the application in a new browser tab.';
+ setAuthError(errorMsg);
+ } else {
+ setAuthError(err instanceof Error ? err.message : 'Identity Provider redirect error');
+ }
+ }
+ };
- return (
-
- {children}
-
- );
+ const logout = async () => {
+ try {
+ localStorage.removeItem(LOCAL_STORAGE_TOKEN_KEY);
+ localStorage.removeItem(LOCAL_STORAGE_USER_KEY);
+ localStorage.removeItem(LOCAL_STORAGE_SUB_KEY);
+ localStorage.removeItem(LOCAL_STORAGE_CLAIMS_KEY);
+ setUser(null);
+ setSub('');
+ setClaims({});
+ setAccessToken(null);
+
+ try {
+ const mgr = getUserManager();
+ await mgr.signoutRedirect();
+ } catch {
+ // Local state cleared
+ }
+ } catch (err) {
+ console.error('Logout error:', err);
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
};
export const useAuth = () => {
- const context = useContext(AuthContext);
- if (!context) {
- throw new Error('useAuth must be used within an AuthProvider');
- }
- return context;
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
};
// Route Guard
export const ProtectedRoute: React.FC<{ children: ReactNode }> = ({ children }) => {
- const { isAuthenticated, isLoading } = useAuth();
- const location = useLocation();
+ const { isAuthenticated, isLoading } = useAuth();
+ const location = useLocation();
- if (isLoading) {
- return (
-
-
-
-
Verifying PFM Console Session...
-
-
- );
- }
+ if (isLoading) {
+ return (
+
+
+
+
Verifying PFM Console Session...
+
+
+ );
+ }
- if (!isAuthenticated) {
- return ;
- }
+ if (!isAuthenticated) {
+ return ;
+ }
- return <>{children}>;
+ return <>{children}>;
};
diff --git a/PostFundManagement.ConsoleUI/src/auth/jwt.ts b/PostFundManagement.ConsoleUI/src/auth/jwt.ts
index 9c545cb..da14bc5 100644
--- a/PostFundManagement.ConsoleUI/src/auth/jwt.ts
+++ b/PostFundManagement.ConsoleUI/src/auth/jwt.ts
@@ -2,22 +2,22 @@
* Lightweight browser-safe JWT payload decoder without external native dependencies.
*/
export function parseJwtClaims(token: string): Record {
- if (!token || typeof token !== 'string') return {};
- try {
- const parts = token.split('.');
- if (parts.length < 2) return {};
- const base64Url = parts[1];
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
- const jsonPayload = decodeURIComponent(
- window
- .atob(base64)
- .split('')
- .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
- .join('')
- );
- return JSON.parse(jsonPayload);
- } catch (err) {
- console.debug('Failed to decode JWT claims payload:', err);
- return {};
- }
+ if (!token || typeof token !== 'string') return {};
+ try {
+ const parts = token.split('.');
+ if (parts.length < 2) return {};
+ const base64Url = parts[1];
+ const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
+ const jsonPayload = decodeURIComponent(
+ window
+ .atob(base64)
+ .split('')
+ .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))
+ .join('')
+ );
+ return JSON.parse(jsonPayload);
+ } catch (err) {
+ console.debug('Failed to decode JWT claims payload:', err);
+ return {};
+ }
}
diff --git a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
index ffe3385..ea712cd 100644
--- a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx
@@ -1,279 +1,309 @@
import React, { useState } from 'react';
import { NavLink, Outlet, useNavigate, Link } from 'react-router-dom';
import {
- LayoutDashboard,
- Building2,
- FolderKanban,
- Coins,
- ShieldAlert,
- Settings,
- LogOut,
- Menu,
- X,
- ChevronRight,
- ShieldCheck,
- Layers,
- Database,
- Home,
- User,
+ LayoutDashboard,
+ Building2,
+ FolderKanban,
+ Coins,
+ ShieldAlert,
+ Settings,
+ LogOut,
+ Menu,
+ X,
+ ChevronRight,
+ ShieldCheck,
+ Database,
+ Home,
+ User,
} from 'lucide-react';
import { useAuth } from '../../auth/AuthProvider';
+import { BrandLogo } from '../ui/BrandLogo';
const navItems = [
- {
- name: 'Executive Dashboard',
- path: '/dashboard',
- icon: LayoutDashboard,
- },
- {
- name: 'Governance & Structure',
- path: '/governance',
- icon: Building2,
- },
- {
- name: 'Projects & Milestones',
- path: '/projects',
- icon: FolderKanban,
- },
- {
- name: 'Grant & Financials',
- path: '/grants',
- icon: Coins,
- },
- {
- name: 'M&E, Evidence & Risks',
- path: '/monitoring',
- icon: ShieldAlert,
- },
- {
- name: 'Settings & Communications',
- path: '/settings',
- icon: Settings,
- },
+ {
+ name: 'Executive Dashboard',
+ path: '/dashboard',
+ icon: LayoutDashboard,
+ },
+ {
+ name: 'Governance & Structure',
+ path: '/governance',
+ icon: Building2,
+ },
+ {
+ name: 'Projects & Milestones',
+ path: '/projects',
+ icon: FolderKanban,
+ },
+ {
+ name: 'Grant & Financials',
+ path: '/grants',
+ icon: Coins,
+ },
+ {
+ name: 'M&E, Evidence & Risks',
+ path: '/monitoring',
+ icon: ShieldAlert,
+ },
+ {
+ name: 'Settings & Communications',
+ path: '/settings',
+ icon: Settings,
+ },
];
export const AppLayout: React.FC = () => {
- const { user, claims, sub, logout } = useAuth();
- const [sidebarOpen, setSidebarOpen] = useState(true);
- const [userMenuOpen, setUserMenuOpen] = useState(false);
- const navigate = useNavigate();
+ const { user, claims, sub, logout } = useAuth();
+ const [sidebarOpen, setSidebarOpen] = useState(true);
+ const [userMenuOpen, setUserMenuOpen] = useState(false);
+ const navigate = useNavigate();
- const handleLogout = async () => {
- await logout();
- };
+ const handleLogout = async () => {
+ await logout();
+ };
- // Extract name and email directly from validated token claims
- const tokenUserName =
- user?.name ||
- (claims.name as string) ||
- (claims.preferred_username as string) ||
- (claims.given_name ? `${claims.given_name} ${claims.family_name || ''}`.trim() : '') ||
- 'Authorized User';
+ // Extract name and email directly from validated token claims
+ const tokenUserName =
+ user?.name ||
+ (claims.name as string) ||
+ (claims.preferred_username as string) ||
+ (claims.given_name ? `${claims.given_name} ${claims.family_name || ''}`.trim() : '') ||
+ 'Authorized User';
- const tokenUserEmail =
- user?.email ||
- (claims.email as string) ||
- (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) ||
- (claims.upn as string) ||
- (sub ? `sub:${sub}` : '');
+ const tokenUserEmail =
+ user?.email ||
+ (claims.email as string) ||
+ (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) ||
+ (claims.upn as string) ||
+ (sub ? `sub:${sub}` : '');
- return (
-
- {/* Sidebar */}
-
-
- {/* Main Content Area */}
-
- {/* Top Header */}
-
-
- {/* Viewport with Landing Page ambient dot grid and glow */}
-
-
-
-
-
-
-
-
-
-
- {/* Bottom Status Footer */}
-
-
+ return (
+
+ {/* Sidebar */}
+
+
+ {/* Main Content Area */}
+
+ {/* Top Header */}
+
+
+ {/* Viewport with Landing Page ambient dot grid and glow */}
+
+
+
+
+
+
+
+
+
+
+ {/* Bottom Status Footer */}
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx b/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx
new file mode 100644
index 0000000..3d53a33
--- /dev/null
+++ b/PostFundManagement.ConsoleUI/src/components/ui/BrandLogo.tsx
@@ -0,0 +1,188 @@
+import React, { useId } from 'react';
+
+export interface BrandLogoProps {
+ size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
+ variant?: 'icon-only' | 'full';
+ showGlow?: boolean;
+ className?: string;
+ animateOnHover?: boolean;
+}
+
+const sizeMap = {
+ xs: 20,
+ sm: 28,
+ md: 36,
+ lg: 44,
+ xl: 56,
+};
+
+/**
+ * Modern, stylized SVG brand emblem representing Post Fund Management (PFM).
+ * Features an isometric kinetic nexus with three interlocking governance facets:
+ * 1. Top Canopy: Governance, Strategy & Portfolios (Radiant Indigo / Violet)
+ * 2. Left Vault: Capital, Grants & Disbursement Assurance (Electric Sapphire)
+ * 3. Right Wing: Milestones, Evidence & Impact Delivery (Cyan to Emerald)
+ * 4. Center Core: Luminous Floating Diamond Milestone Star
+ */
+export const BrandLogo: React.FC
= ({
+ size = 'md',
+ variant = 'icon-only',
+ showGlow = true,
+ className = '',
+ animateOnHover = true,
+}) => {
+ const uniqueId = useId().replace(/:/g, '');
+ const pixelSize = typeof size === 'number' ? size : sizeMap[size] || 36;
+
+ const glowId = `pfm-ambient-${uniqueId}`;
+ const topGradId = `pfm-top-${uniqueId}`;
+ const leftGradId = `pfm-left-${uniqueId}`;
+ const rightGradId = `pfm-right-${uniqueId}`;
+ const coreGradId = `pfm-core-${uniqueId}`;
+ const filterId = `pfm-filter-${uniqueId}`;
+
+ return (
+
+
+ {showGlow && (
+
+ )}
+
+
+
+ {variant === 'full' && (
+
+
+ PFM Console
+
+
+ Post-Fund Governance
+
+
+ )}
+
+ );
+};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx b/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
index a3bd905..aeba65b 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/Modal.tsx
@@ -2,66 +2,66 @@ import React, { ReactNode, useEffect } from 'react';
import { X } from 'lucide-react';
interface ModalProps {
- isOpen: boolean;
- onClose: () => void;
- title: string;
- subtitle?: string;
- children: ReactNode;
- maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ subtitle?: string;
+ children: ReactNode;
+ maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl';
}
export const Modal: React.FC = ({
- isOpen,
- onClose,
- title,
- subtitle,
- children,
- maxWidth = 'lg',
+ isOpen,
+ onClose,
+ title,
+ subtitle,
+ children,
+ maxWidth = 'lg',
}) => {
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === 'Escape') onClose();
- };
- if (isOpen) {
- document.body.style.overflow = 'hidden';
- window.addEventListener('keydown', handleKeyDown);
- }
- return () => {
- document.body.style.overflow = 'unset';
- window.removeEventListener('keydown', handleKeyDown);
- };
- }, [isOpen, onClose]);
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.key === 'Escape') onClose();
+ };
+ if (isOpen) {
+ document.body.style.overflow = 'hidden';
+ window.addEventListener('keydown', handleKeyDown);
+ }
+ return () => {
+ document.body.style.overflow = 'unset';
+ window.removeEventListener('keydown', handleKeyDown);
+ };
+ }, [isOpen, onClose]);
- if (!isOpen) return null;
+ if (!isOpen) return null;
- const maxWidthClass = {
- sm: 'max-w-sm',
- md: 'max-w-md',
- lg: 'max-w-lg',
- xl: 'max-w-xl',
- '2xl': 'max-w-2xl',
- }[maxWidth];
+ const maxWidthClass = {
+ sm: 'max-w-sm',
+ md: 'max-w-md',
+ lg: 'max-w-lg',
+ xl: 'max-w-xl',
+ '2xl': 'max-w-2xl',
+ }[maxWidth];
- return (
-
-
e.stopPropagation()}
- >
-
-
-
{title}
- {subtitle &&
{subtitle}
}
-
-
-
-
{children}
-
+ return (
+
+
e.stopPropagation()}
+ >
+
+
+
{title}
+ {subtitle &&
{subtitle}
}
+
+
- );
+
{children}
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx b/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
index 90fe50d..5a25733 100644
--- a/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
+++ b/PostFundManagement.ConsoleUI/src/components/ui/Toast.tsx
@@ -4,76 +4,77 @@ import { CheckCircle2, AlertCircle, Info, X } from 'lucide-react';
export type ToastType = 'success' | 'error' | 'info';
interface ToastItem {
- id: string;
- type: ToastType;
- title: string;
- message?: string;
+ id: string;
+ type: ToastType;
+ title: string;
+ message?: string;
}
interface ToastContextType {
- toast: (type: ToastType, title: string, message?: string) => void;
- success: (title: string, message?: string) => void;
- error: (title: string, message?: string) => void;
- info: (title: string, message?: string) => void;
+ toast: (type: ToastType, title: string, message?: string) => void;
+ success: (title: string, message?: string) => void;
+ error: (title: string, message?: string) => void;
+ info: (title: string, message?: string) => void;
}
const ToastContext = createContext
(undefined);
export const ToastProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
- const [toasts, setToasts] = useState([]);
+ const [toasts, setToasts] = useState([]);
- const removeToast = (id: string) => {
- setToasts((prev) => prev.filter((t) => t.id !== id));
- };
+ const removeToast = (id: string) => {
+ setToasts((prev) => prev.filter((t) => t.id !== id));
+ };
- const addToast = (type: ToastType, title: string, message?: string) => {
- const id = Math.random().toString(36).substring(2, 9);
- setToasts((prev) => [...prev, { id, type, title, message }]);
- setTimeout(() => removeToast(id), 5000);
- };
+ const addToast = (type: ToastType, title: string, message?: string) => {
+ const id = Math.random().toString(36).substring(2, 9);
+ setToasts((prev) => [...prev, { id, type, title, message }]);
+ setTimeout(() => removeToast(id), 5000);
+ };
- const success = (title: string, message?: string) => addToast('success', title, message);
- const error = (title: string, message?: string) => addToast('error', title, message);
- const info = (title: string, message?: string) => addToast('info', title, message);
+ const success = (title: string, message?: string) => addToast('success', title, message);
+ const error = (title: string, message?: string) => addToast('error', title, message);
+ const info = (title: string, message?: string) => addToast('info', title, message);
- return (
-
- {children}
-
- {toasts.map((t) => (
-
- {t.type === 'success' &&
}
- {t.type === 'error' &&
}
- {t.type === 'info' &&
}
-
-
{t.title}
- {t.message &&
{t.message}
}
-
-
-
- ))}
+ return (
+
+ {children}
+
+ {toasts.map((t) => (
+
+ {t.type === 'success' &&
}
+ {t.type === 'error' &&
}
+ {t.type === 'info' &&
}
+
+
{t.title}
+ {t.message &&
{t.message}
}
-
- );
+
+
+ ))}
+
+
+ );
};
export const useToast = () => {
- const context = useContext(ToastContext);
- if (!context) {
- throw new Error('useToast must be used within a ToastProvider');
- }
- return context;
+ const context = useContext(ToastContext);
+ if (!context) {
+ throw new Error('useToast must be used within a ToastProvider');
+ }
+ return context;
};
diff --git a/PostFundManagement.ConsoleUI/src/config.ts b/PostFundManagement.ConsoleUI/src/config.ts
index 0a1a851..b7ad2ca 100644
--- a/PostFundManagement.ConsoleUI/src/config.ts
+++ b/PostFundManagement.ConsoleUI/src/config.ts
@@ -1,43 +1,382 @@
+import { updateHasherSettings } from './services/toolsService';
+
export interface SecuritySettings {
- Authority: string;
- Audience: string;
- ClientId: string;
- ClientSecret: string;
+ Authority: string;
+ Audience: string;
+ ClientId: string;
+ ClientSecret: string;
+ Scope?: string;
}
export interface ApiSettings {
- BaseUrl: string;
+ BaseUrl: string;
}
+export interface HasherSettings {
+ Salt: string;
+ MinHashLength: number;
+}
+
+export type ConfigSource =
+ | 'runtime-injected'
+ | 'runtime-fetched'
+ | 'build-env'
+ | 'fallback-defaults';
+
export interface AppConfig {
- SecuritySettings: SecuritySettings;
- ApiSettings: ApiSettings;
+ SecuritySettings: SecuritySettings;
+ ApiSettings: ApiSettings;
+ HasherSettings: HasherSettings;
+ ConfigSource: ConfigSource;
+ LoadedAt?: string;
}
+// Fallback presets (used only when no runtime or build-time variable is provided)
export const defaultSecuritySettings: SecuritySettings = {
- Authority: (import.meta.env.VITE_OIDC_AUTHORITY as string) || 'https://sts.security.khongisa.co.za',
- Audience: (import.meta.env.VITE_OIDC_AUDIENCE as string) || 'pfm-api-dev',
- ClientId: (import.meta.env.VITE_OIDC_CLIENT_ID as string) || 'pfm-console-dev',
- ClientSecret: (import.meta.env.VITE_OIDC_CLIENT_SECRET as string) || 'secret_dcaeaa28097d9ba5b06ec324d9eac9d9c81de96e0a203cf2c6154c6dd7c89e94',
+ Authority: 'https://sts.security.khongisa.co.za',
+ Audience: 'pfm-api-dev',
+ ClientId: 'pfm-console-dev',
+ ClientSecret: 'secret_dcaeaa28097d9ba5b06ec324d9eac9d9c81de96e0a203cf2c6154c6dd7c89e94',
+ Scope: 'openid profile roles email address pfm-api-dev',
};
export const defaultApiSettings: ApiSettings = {
- BaseUrl: (import.meta.env.VITE_API_BASE_URL as string) || 'https://api.ifri.khongisa.co.za/',
+ BaseUrl: 'https://api.ifri.khongisa.co.za/',
};
-const runtimeConfig: AppConfig = {
- SecuritySettings: defaultSecuritySettings,
- ApiSettings: defaultApiSettings,
+export const defaultHasherSettings: HasherSettings = {
+ Salt: 'TGlnaHQgQ2hhcm1zLCBNaWRyYW5kQm9va3MgbWFrZSBsb3RzIG9mIG1vbmV5IGFuZCBhcmUgYSBzdWNjZXNzZnVsIHZpcmFsIHN0b3J5IGluIFNvdXRoIEFmcmljYQ==',
+ MinHashLength: 11,
};
+/**
+ * Extracts first non-empty, trimmed string from candidate values.
+ */
+function pickFirstNonEmpty(...candidates: any[]): string | undefined {
+ for (const c of candidates) {
+ if (c !== undefined && c !== null) {
+ const s = String(c).trim();
+ if (s !== '' && s !== 'undefined' && s !== 'null') {
+ return s;
+ }
+ }
+ }
+ return undefined;
+}
+
+/**
+ * Normalizes an API base URL ensuring proper protocol and single trailing slash.
+ */
+function normalizeBaseUrl(url: string): string {
+ const trimmed = url.trim();
+ return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
+}
+
+/**
+ * Reads any runtime config object injected into window.
+ * Supports: window.__RUNTIME_CONFIG__, window.APP_CONFIG, window._env_
+ */
+function getWindowRuntimeConfig(): Record
{
+ if (typeof window === 'undefined') return {};
+ const win = window as any;
+ return win.__RUNTIME_CONFIG__ || win.APP_CONFIG || win._env_ || {};
+}
+
+/**
+ * Parses and maps configuration from a given source object.
+ * Checks flat keys, nested keys, and ASP.NET double-underscore conventions.
+ */
+function extractSourceConfig(src: Record): {
+ authority?: string;
+ audience?: string;
+ clientId?: string;
+ clientSecret?: string;
+ scope?: string;
+ baseUrl?: string;
+ hasherSalt?: string;
+ hasherMinLength?: number;
+} {
+ if (!src || typeof src !== 'object') return {};
+
+ const authority = pickFirstNonEmpty(
+ src.VITE_OIDC_AUTHORITY,
+ src.OIDC_AUTHORITY,
+ src.SecuritySettings?.Authority,
+ src.SecuritySettings__Authority,
+ src.SecuritySettings_Authority,
+ src.AUTHORITY,
+ src.STS_AUTHORITY
+ );
+
+ const audience = pickFirstNonEmpty(
+ src.VITE_OIDC_AUDIENCE,
+ src.OIDC_AUDIENCE,
+ src.SecuritySettings?.Audience,
+ src.SecuritySettings__Audience,
+ src.SecuritySettings_Audience,
+ src.AUDIENCE
+ );
+
+ const clientId = pickFirstNonEmpty(
+ src.VITE_OIDC_CLIENT_ID,
+ src.OIDC_CLIENT_ID,
+ src.SecuritySettings?.ClientId,
+ src.SecuritySettings__ClientId,
+ src.SecuritySettings_ClientId,
+ src.CLIENT_ID
+ );
+
+ const clientSecret = pickFirstNonEmpty(
+ src.VITE_OIDC_CLIENT_SECRET,
+ src.OIDC_CLIENT_SECRET,
+ src.SecuritySettings?.ClientSecret,
+ src.SecuritySettings__ClientSecret,
+ src.SecuritySettings_ClientSecret,
+ src.CLIENT_SECRET
+ );
+
+ const scope = pickFirstNonEmpty(
+ src.VITE_OIDC_SCOPE,
+ src.OIDC_SCOPE,
+ src.SecuritySettings?.Scope,
+ src.SecuritySettings__Scope,
+ src.SecuritySettings_Scope,
+ src.SCOPE
+ );
+
+ const baseUrl = pickFirstNonEmpty(
+ src.VITE_API_BASE_URL,
+ src.API_BASE_URL,
+ src.ApiSettings?.BaseUrl,
+ src.ApiSettings__BaseUrl,
+ src.ApiSettings_BaseUrl,
+ src.PFM_API_BASE_URL
+ );
+
+ const hasherSalt = pickFirstNonEmpty(
+ src.VITE_HASHER_SALT,
+ src.HASHER_SALT,
+ src.HasherSettings?.Salt,
+ src.HasherSettings__Salt,
+ src.HasherSettings_Salt
+ );
+
+ const rawMinLength = pickFirstNonEmpty(
+ src.VITE_HASHER_MIN_LENGTH,
+ src.HASHER_MIN_LENGTH,
+ src.HasherSettings?.MinHashLength,
+ src.HasherSettings__MinLength,
+ src.HasherSettings_MinLength
+ );
+
+ const hasherMinLength = rawMinLength ? Number(rawMinLength) : undefined;
+
+ return {
+ authority,
+ audience,
+ clientId,
+ clientSecret,
+ scope,
+ baseUrl: baseUrl ? normalizeBaseUrl(baseUrl) : undefined,
+ hasherSalt,
+ hasherMinLength: hasherMinLength && !isNaN(hasherMinLength) && hasherMinLength > 0 ? hasherMinLength : undefined,
+ };
+}
+
+/**
+ * Builds the prioritized configuration by evaluating:
+ * Tier 1: Injected Runtime Config (window.__RUNTIME_CONFIG__ from docker-entrypoint.sh)
+ * Tier 2: Fetched Runtime Config (/config.json from server)
+ * Tier 3: Vite Build-Time Variables (import.meta.env.VITE_*)
+ * Tier 4: Fallback Defaults
+ */
+function resolveConfig(fetchedJson?: Record): AppConfig {
+ const windowConfig = extractSourceConfig(getWindowRuntimeConfig());
+ const fetchedConfig = fetchedJson ? extractSourceConfig(fetchedJson) : {};
+ const buildEnvConfig = extractSourceConfig(
+ (typeof import.meta !== 'undefined' && import.meta.env ? import.meta.env : {}) as Record
+ );
+
+ // Check if any runtime deployment override was provided
+ const hasInjected = Object.values(windowConfig).some((v) => v !== undefined);
+ const hasFetched = Object.values(fetchedConfig).some((v) => v !== undefined);
+ const hasBuildEnv = Object.values(buildEnvConfig).some((v) => v !== undefined);
+
+ let source: ConfigSource = 'fallback-defaults';
+ if (hasInjected) {
+ source = 'runtime-injected';
+ } else if (hasFetched) {
+ source = 'runtime-fetched';
+ } else if (hasBuildEnv) {
+ source = 'build-env';
+ }
+
+ const authority = pickFirstNonEmpty(
+ windowConfig.authority,
+ fetchedConfig.authority,
+ buildEnvConfig.authority,
+ defaultSecuritySettings.Authority
+ )!;
+
+ const audience = pickFirstNonEmpty(
+ windowConfig.audience,
+ fetchedConfig.audience,
+ buildEnvConfig.audience,
+ defaultSecuritySettings.Audience
+ )!;
+
+ const clientId = pickFirstNonEmpty(
+ windowConfig.clientId,
+ fetchedConfig.clientId,
+ buildEnvConfig.clientId,
+ defaultSecuritySettings.ClientId
+ )!;
+
+ const clientSecret = pickFirstNonEmpty(
+ windowConfig.clientSecret,
+ fetchedConfig.clientSecret,
+ buildEnvConfig.clientSecret,
+ defaultSecuritySettings.ClientSecret
+ )!;
+
+ const scope = pickFirstNonEmpty(
+ windowConfig.scope,
+ fetchedConfig.scope,
+ buildEnvConfig.scope,
+ defaultSecuritySettings.Scope
+ );
+
+ const baseUrl = pickFirstNonEmpty(
+ windowConfig.baseUrl,
+ fetchedConfig.baseUrl,
+ buildEnvConfig.baseUrl,
+ defaultApiSettings.BaseUrl
+ )!;
+
+ const salt = pickFirstNonEmpty(
+ windowConfig.hasherSalt,
+ fetchedConfig.hasherSalt,
+ buildEnvConfig.hasherSalt,
+ defaultHasherSettings.Salt
+ )!;
+
+ const minHashLength =
+ windowConfig.hasherMinLength ||
+ fetchedConfig.hasherMinLength ||
+ buildEnvConfig.hasherMinLength ||
+ defaultHasherSettings.MinHashLength;
+
+ return {
+ SecuritySettings: {
+ Authority: authority,
+ Audience: audience,
+ ClientId: clientId,
+ ClientSecret: clientSecret,
+ Scope: scope,
+ },
+ ApiSettings: {
+ BaseUrl: normalizeBaseUrl(baseUrl),
+ },
+ HasherSettings: {
+ Salt: salt,
+ MinHashLength: minHashLength,
+ },
+ ConfigSource: source,
+ LoadedAt: new Date().toISOString(),
+ };
+}
+
+// Initial synchronous computation from window.__RUNTIME_CONFIG__ and build-time env
+let runtimeConfig: AppConfig = resolveConfig();
+
+// Immediately apply deterministic hasher settings
+updateHasherSettings(runtimeConfig.HasherSettings.Salt, runtimeConfig.HasherSettings.MinHashLength);
+
+const configListeners: Array<(config: AppConfig) => void> = [];
+
+export function onConfigChanged(listener: (config: AppConfig) => void): () => void {
+ configListeners.push(listener);
+ return () => {
+ const idx = configListeners.indexOf(listener);
+ if (idx !== -1) configListeners.splice(idx, 1);
+ };
+}
+
+let loadPromise: Promise | null = null;
+
+/**
+ * Asynchronously loads and verifies runtime configuration.
+ * Fetches /config.json with cache-busting to ensure latest deployment variables
+ * are prioritized over static build artifacts.
+ */
export async function loadRuntimeConfig(): Promise {
+ if (loadPromise) return loadPromise;
+
+ loadPromise = (async () => {
+ let fetchedJson: Record | undefined;
+
+ if (typeof window !== 'undefined') {
+ try {
+ // Fetch /config.json with cache-busting to bypass any intermediary caching
+ const res = await fetch(`/config.json?_t=${Date.now()}`, {
+ cache: 'no-store',
+ headers: {
+ Accept: 'application/json',
+ },
+ });
+
+ if (res.ok) {
+ const data = await res.json();
+ if (data && typeof data === 'object') {
+ fetchedJson = data;
+ }
+ }
+ } catch (err) {
+ // Fetching /config.json is non-blocking; window.__RUNTIME_CONFIG__ or env fallback will be used
+ console.debug('[Config] Note: Remote /config.json fetch bypassed:', err);
+ }
+ }
+
+ runtimeConfig = resolveConfig(fetchedJson);
+
+ // Synchronize deterministic hasher settings
+ updateHasherSettings(runtimeConfig.HasherSettings.Salt, runtimeConfig.HasherSettings.MinHashLength);
+
+ console.info(
+ `[Config] Active runtime configuration: source=${runtimeConfig.ConfigSource}, API=${runtimeConfig.ApiSettings.BaseUrl}, STS=${runtimeConfig.SecuritySettings.Authority}`
+ );
+
+ // Notify registered listeners
+ configListeners.forEach((fn) => {
+ try {
+ fn(runtimeConfig);
+ } catch (e) {
+ console.error('[Config] Listener error:', e);
+ }
+ });
+
return runtimeConfig;
+ })();
+
+ return loadPromise;
+}
+
+export function getAppConfig(): AppConfig {
+ return runtimeConfig;
}
export function getSecuritySettings(): SecuritySettings {
- return runtimeConfig.SecuritySettings;
+ return runtimeConfig.SecuritySettings;
}
export function getApiBaseUrl(): string {
- return runtimeConfig.ApiSettings.BaseUrl;
+ return runtimeConfig.ApiSettings.BaseUrl;
+}
+
+export function getHasherSettings(): HasherSettings {
+ return runtimeConfig.HasherSettings;
+}
+
+export function getConfigSource(): ConfigSource {
+ return runtimeConfig.ConfigSource;
}
diff --git a/PostFundManagement.ConsoleUI/src/index.css b/PostFundManagement.ConsoleUI/src/index.css
index d430f1f..ef65b70 100644
--- a/PostFundManagement.ConsoleUI/src/index.css
+++ b/PostFundManagement.ConsoleUI/src/index.css
@@ -1,44 +1,58 @@
@import "tailwindcss";
+@theme {
+ --font-sans: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+}
+
@layer base {
- :root {
- color-scheme: dark;
- }
+ :root {
+ color-scheme: dark;
+ --font-sans: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
+ }
- body {
- background-color: #0a0a0a;
- color: #e5e7eb;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
- }
+ body {
+ background-color: #090d16;
+ color: #e2e8f0;
+ font-family: var(--font-sans);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ }
- /* Form control color scheme */
- input, select, textarea {
- color-scheme: dark;
- }
+ button, input, select, textarea {
+ font-family: inherit;
+ }
- /* Custom dark scrollbar styling */
- ::-webkit-scrollbar {
- width: 6px;
- height: 6px;
- }
+ .font-mono, code, kbd, samp, pre {
+ font-family: var(--font-mono) !important;
+ }
- ::-webkit-scrollbar-track {
- background: #0a0a0a;
- }
+ /* Form control color scheme */
+ input, select, textarea {
+ color-scheme: dark;
+ }
- ::-webkit-scrollbar-thumb {
- background: #262626;
- border-radius: 9999px;
- }
-
- ::-webkit-scrollbar-thumb:hover {
- background: #3f3f46;
- }
+ /* Custom dark scrollbar styling */
+ ::-webkit-scrollbar {
+ width: 6px;
+ height: 6px;
+ }
+ ::-webkit-scrollbar-track {
+ background: #0a0a0a;
+ }
+ ::-webkit-scrollbar-thumb {
+ background: #262626;
+ border-radius: 9999px;
+ }
+ ::-webkit-scrollbar-thumb:hover {
+ background: #3f3f46;
+ }
}
/* High Contrast Dark Theme Adaptations for existing utility classes */
.bg-white {
- background-color: #111111 !important;
+ background-color: #111111 !important;
}
.bg-slate-50,
@@ -47,210 +61,211 @@
.bg-slate-50\/75,
.bg-slate-50\/80,
.bg-slate-50\/90 {
- background-color: #161616 !important;
+ background-color: #161616 !important;
}
.bg-slate-100,
.bg-slate-200 {
- background-color: #202020 !important;
+ background-color: #202020 !important;
}
.border-slate-100 {
- border-color: #222222 !important;
+ border-color: #222222 !important;
}
.border-slate-200 {
- border-color: #2d2d2d !important;
+ border-color: #2d2d2d !important;
}
.border-slate-300 {
- border-color: #3d3d3d !important;
+ border-color: #3d3d3d !important;
}
.divide-slate-100 > :not([hidden]) ~ :not([hidden]),
.divide-slate-200 > :not([hidden]) ~ :not([hidden]) {
- border-color: #242424 !important;
+ border-color: #242424 !important;
}
/* Text contrast enhancements */
.text-slate-900 {
- color: #ffffff !important;
+ color: #ffffff !important;
}
.text-slate-800 {
- color: #f8fafc !important;
+ color: #f8fafc !important;
}
.text-slate-700 {
- color: #f1f5f9 !important;
+ color: #f1f5f9 !important;
}
.text-slate-600 {
- color: #e2e8f0 !important;
+ color: #e2e8f0 !important;
}
.text-slate-500 {
- color: #cbd5e1 !important;
+ color: #cbd5e1 !important;
}
.text-slate-400 {
- color: #94a3b8 !important;
+ color: #94a3b8 !important;
}
.text-gray-900 {
- color: #ffffff !important;
+ color: #ffffff !important;
}
.text-gray-800 {
- color: #f8fafc !important;
+ color: #f8fafc !important;
}
.text-gray-700 {
- color: #f1f5f9 !important;
+ color: #f1f5f9 !important;
}
.text-gray-600 {
- color: #e2e8f0 !important;
+ color: #e2e8f0 !important;
}
.text-gray-500 {
- color: #cbd5e1 !important;
+ color: #cbd5e1 !important;
}
.text-gray-400 {
- color: #94a3b8 !important;
+ color: #94a3b8 !important;
}
/* High-contrast status badges and notification banners */
.bg-emerald-50,
.bg-emerald-50\/40,
.bg-emerald-50\/60 {
- background-color: rgba(16, 185, 129, 0.16) !important;
+ background-color: rgba(16, 185, 129, 0.16) !important;
}
.text-emerald-700,
.text-emerald-800,
.text-emerald-900 {
- color: #6ee7b7 !important;
+ color: #6ee7b7 !important;
}
.border-emerald-200,
.border-emerald-300 {
- border-color: rgba(110, 231, 183, 0.35) !important;
+ border-color: rgba(110, 231, 183, 0.35) !important;
}
.bg-amber-50,
.bg-amber-50\/40,
.bg-amber-50\/60 {
- background-color: rgba(245, 158, 11, 0.16) !important;
+ background-color: rgba(245, 158, 11, 0.16) !important;
}
.text-amber-700,
.text-amber-800,
.text-amber-900 {
- color: #fcd34d !important;
+ color: #fcd34d !important;
}
.border-amber-200,
.border-amber-300 {
- border-color: rgba(252, 211, 77, 0.35) !important;
+ border-color: rgba(252, 211, 77, 0.35) !important;
}
.bg-rose-50,
.bg-rose-50\/40,
.bg-rose-50\/60 {
- background-color: rgba(244, 63, 94, 0.16) !important;
+ background-color: rgba(244, 63, 94, 0.16) !important;
}
.text-rose-700,
.text-rose-800,
.text-rose-900 {
- color: #fda4af !important;
+ color: #fda4af !important;
}
.border-rose-200,
.border-rose-300 {
- border-color: rgba(253, 164, 175, 0.35) !important;
+ border-color: rgba(253, 164, 175, 0.35) !important;
}
.bg-purple-50,
.bg-indigo-50 {
- background-color: rgba(139, 92, 246, 0.16) !important;
+ background-color: rgba(139, 92, 246, 0.16) !important;
}
.text-purple-700,
.text-purple-800,
.text-indigo-700,
.text-indigo-800 {
- color: #d8b4fe !important;
+ color: #d8b4fe !important;
}
.border-purple-200,
.border-purple-300,
.border-indigo-200,
.border-indigo-300 {
- border-color: rgba(216, 180, 254, 0.35) !important;
+ border-color: rgba(216, 180, 254, 0.35) !important;
}
.bg-sky-50,
.bg-blue-50 {
- background-color: rgba(59, 130, 246, 0.16) !important;
+ background-color: rgba(59, 130, 246, 0.16) !important;
}
.text-sky-700,
.text-sky-800,
.text-blue-700,
.text-blue-800 {
- color: #93c5fd !important;
+ color: #93c5fd !important;
}
.border-sky-200,
.border-sky-300,
.border-blue-200,
.border-blue-300 {
- border-color: rgba(147, 197, 253, 0.35) !important;
+ border-color: rgba(147, 197, 253, 0.35) !important;
}
/* Form inputs, textareas, and select controls */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="button"]),
select,
textarea {
- background-color: #141414 !important;
- color: #ffffff !important;
- border-color: #383838 !important;
+ background-color: #141414 !important;
+ color: #ffffff !important;
+ border-color: #383838 !important;
}
- input::placeholder,
- textarea::placeholder {
- color: #94a3b8 !important;
- }
+input::placeholder,
+textarea::placeholder {
+ color: #94a3b8 !important;
+}
/* Table header text high contrast */
th {
- color: #cbd5e1 !important;
+ color: #cbd5e1 !important;
}
.hover\:bg-slate-50:hover,
.hover\:bg-slate-50\/80:hover,
.hover\:bg-slate-100:hover {
- background-color: #1f1f1f !important;
+ background-color: #1f1f1f !important;
}
.hover\:text-slate-800:hover,
.hover\:text-slate-900:hover {
- color: #ffffff !important;
+ color: #ffffff !important;
}
/* Primary actions matching the clean blue/indigo accent */
.bg-emerald-600 {
- background-color: #2563eb !important;
+ background-color: #2563eb !important;
}
.hover\:bg-emerald-500:hover,
.hover\:bg-emerald-700:hover {
- background-color: #1d4ed8 !important;
+ background-color: #1d4ed8 !important;
}
.border-emerald-600 {
- border-color: #3b82f6 !important;
+ border-color: #3b82f6 !important;
}
+
diff --git a/PostFundManagement.ConsoleUI/src/lib/queryClient.ts b/PostFundManagement.ConsoleUI/src/lib/queryClient.ts
index f054a86..e9cc63b 100644
--- a/PostFundManagement.ConsoleUI/src/lib/queryClient.ts
+++ b/PostFundManagement.ConsoleUI/src/lib/queryClient.ts
@@ -1,11 +1,11 @@
import { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 1000 * 60 * 3, // 3 minutes default cache
- refetchOnWindowFocus: false,
- retry: 1,
- },
+ defaultOptions: {
+ queries: {
+ staleTime: 1000 * 60 * 3, // 3 minutes default cache
+ refetchOnWindowFocus: false,
+ retry: 1,
},
+ },
});
diff --git a/PostFundManagement.ConsoleUI/src/main.tsx b/PostFundManagement.ConsoleUI/src/main.tsx
index 2ad9aab..de3f036 100644
--- a/PostFundManagement.ConsoleUI/src/main.tsx
+++ b/PostFundManagement.ConsoleUI/src/main.tsx
@@ -2,9 +2,14 @@ import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.tsx';
import './index.css';
+import { loadRuntimeConfig } from './config';
-createRoot(document.getElementById('root')!).render(
+// Load runtime environment configuration before mounting the application
+loadRuntimeConfig().finally(() => {
+ createRoot(document.getElementById('root')!).render(
-
- ,
-);
+
+
+ );
+});
+
diff --git a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
index b7a69d6..3de5946 100644
--- a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx
@@ -2,654 +2,657 @@ import React, { useState, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import {
- TrendingUp,
- AlertTriangle,
- Users,
- Layers,
- Coins,
- CheckCircle,
- ArrowUpRight,
- Filter,
- RefreshCw,
- Clock,
- ShieldAlert,
+ TrendingUp,
+ AlertTriangle,
+ Users,
+ Layers,
+ Coins,
+ CheckCircle,
+ ArrowUpRight,
+ Filter,
+ RefreshCw,
+ Clock,
+ ShieldAlert,
} from 'lucide-react';
import {
- getDashboard,
- getProjects,
- getAllPortfolios,
- getAllProgrammes,
- getAwards,
- getBudgets,
- getDisbursements,
- getAllRisks,
+ getDashboard,
+ getProjects,
+ getAllPortfolios,
+ getAllProgrammes,
+ getAwards,
+ getBudgets,
+ getDisbursements,
+ getAllRisks,
} from '../services/api';
import { formatCompactZAR, formatZAR, getIndustrySectorLabel } from '../lib/formatters';
import { ApprovalStatus } from '../types';
export const ExecutiveDashboard: React.FC = () => {
- const [selectedProgramme, setSelectedProgramme] = useState('All');
- const [selectedSector, setSelectedSector] = useState('All');
- const [selectedPeriod, setSelectedPeriod] = useState('FY 2026/27');
+ const [selectedProgramme, setSelectedProgramme] = useState('All');
+ const [selectedSector, setSelectedSector] = useState('All');
+ const [selectedPeriod, setSelectedPeriod] = useState('FY 2026/27');
- // Real Queries from OpenAPI Endpoints
- const { data: dashboard, isLoading: loadingDashboard, refetch: refetchDashboard, isFetching } = useQuery({
- queryKey: ['dashboard'],
- queryFn: getDashboard,
+ // Real Queries from OpenAPI Endpoints
+ const { data: dashboard, isLoading: loadingDashboard, refetch: refetchDashboard, isFetching } = useQuery({
+ queryKey: ['dashboard'],
+ queryFn: getDashboard,
+ });
+
+ const { data: projects = [] } = useQuery({
+ queryKey: ['projects'],
+ queryFn: () => getProjects(),
+ });
+
+ const { data: portfolios = [] } = useQuery({
+ queryKey: ['portfolios'],
+ queryFn: () => getAllPortfolios(),
+ });
+
+ const { data: programmes = [] } = useQuery({
+ queryKey: ['programmes'],
+ queryFn: () => getAllProgrammes(),
+ });
+
+ const { data: awards = [] } = useQuery({
+ queryKey: ['awards'],
+ queryFn: () => getAwards(),
+ });
+
+ const { data: budgets = [] } = useQuery({
+ queryKey: ['budgets'],
+ queryFn: () => getBudgets(),
+ });
+
+ const { data: disbursements = [] } = useQuery({
+ queryKey: ['disbursements'],
+ queryFn: () => getDisbursements(),
+ });
+
+ const { data: risks = [] } = useQuery({
+ queryKey: ['risks'],
+ queryFn: () => getAllRisks(),
+ });
+
+ const refetchAll = () => {
+ refetchDashboard();
+ };
+
+ // Headline metrics sourced directly from live API models
+ const portfolioValue = useMemo(() => {
+ if (dashboard?.portfolioValue && dashboard.portfolioValue > 0) {
+ return dashboard.portfolioValue;
+ }
+ const budgetTotal = budgets.reduce((sum, b) => sum + (b.amount || 0), 0);
+ if (budgetTotal > 0) return budgetTotal;
+ const awardsTotal = awards.reduce((sum, a) => sum + (a.amount || 0), 0);
+ return awardsTotal;
+ }, [dashboard, budgets, awards]);
+
+ const activeAwards = dashboard?.activeAwards ?? (awards.length || projects.length);
+
+ const disbursed = useMemo(() => {
+ if (dashboard?.disbursed && dashboard.disbursed > 0) {
+ return dashboard.disbursed;
+ }
+ return disbursements
+ .filter((d) => d.status === ApprovalStatus.Approved || (d.status as number) === 3)
+ .reduce((sum, d) => sum + (d.amount || 0), 0);
+ }, [dashboard, disbursements]);
+
+ const onTrackPercentage = useMemo(() => {
+ if (dashboard?.onTrackPercentage !== undefined && dashboard.onTrackPercentage > 0) {
+ return dashboard.onTrackPercentage;
+ }
+ if (projects.length === 0) return 0;
+ const activeOnTrack = projects.filter((p) => p.status === ApprovalStatus.Approved).length;
+ return Math.round((activeOnTrack / projects.length) * 100);
+ }, [dashboard, projects]);
+
+ const atRiskCount = useMemo(() => {
+ if (dashboard?.atRiskCount !== undefined) {
+ return dashboard.atRiskCount;
+ }
+ return risks.filter((r) => r.likelihood * r.impact >= 12).length;
+ }, [dashboard, risks]);
+
+ const beneficiariesReached = dashboard?.beneficiariesReached ?? 0;
+
+ // Real sector breakdown calculated dynamically from actual project records
+ const sectorData = useMemo(() => {
+ if (!projects || projects.length === 0) return [];
+ const counts: Record = {};
+ projects.forEach((p) => {
+ const label = getIndustrySectorLabel(p.sector);
+ counts[label] = (counts[label] || 0) + 1;
});
- const { data: projects = [] } = useQuery({
- queryKey: ['projects'],
- queryFn: () => getProjects(),
- });
+ const colors = ['bg-emerald-500', 'bg-teal-500', 'bg-blue-500', 'bg-indigo-500', 'bg-purple-500', 'bg-amber-500'];
+ const totalProjects = projects.length;
- const { data: portfolios = [] } = useQuery({
- queryKey: ['portfolios'],
- queryFn: () => getAllPortfolios(),
+ return Object.entries(counts).map(([name, count], index) => {
+ const percentage = Math.round((count / totalProjects) * 100);
+ const amount = portfolioValue > 0 ? Math.round((portfolioValue / totalProjects) * count) : 0;
+ return {
+ name,
+ count,
+ percentage,
+ amount,
+ color: colors[index % colors.length],
+ };
});
+ }, [projects, portfolioValue]);
- const { data: programmes = [] } = useQuery({
- queryKey: ['programmes'],
- queryFn: () => getAllProgrammes(),
- });
+ // Risk heatmap matrix computed directly from registered risk records
+ const riskHeatmap = useMemo(() => {
+ const matrix = {
+ highImpHighLik: risks.filter((r) => r.impact >= 4 && r.likelihood >= 4).length,
+ highImpMedLik: risks.filter((r) => r.impact >= 4 && r.likelihood === 3).length,
+ highImpLowLik: risks.filter((r) => r.impact >= 4 && r.likelihood <= 2).length,
- const { data: awards = [] } = useQuery({
- queryKey: ['awards'],
- queryFn: () => getAwards(),
- });
+ medImpHighLik: risks.filter((r) => r.impact === 3 && r.likelihood >= 4).length,
+ medImpMedLik: risks.filter((r) => r.impact === 3 && r.likelihood === 3).length,
+ medImpLowLik: risks.filter((r) => r.impact === 3 && r.likelihood <= 2).length,
- const { data: budgets = [] } = useQuery({
- queryKey: ['budgets'],
- queryFn: () => getBudgets(),
- });
+ lowImpHighLik: risks.filter((r) => r.impact <= 2 && r.likelihood >= 4).length,
+ lowImpMedLik: risks.filter((r) => r.impact <= 2 && r.likelihood === 3).length,
+ lowImpLowLik: risks.filter((r) => r.impact <= 2 && r.likelihood <= 2).length,
- const { data: disbursements = [] } = useQuery({
- queryKey: ['disbursements'],
- queryFn: () => getDisbursements(),
- });
-
- const { data: risks = [] } = useQuery({
- queryKey: ['risks'],
- queryFn: () => getAllRisks(),
- });
-
- const refetchAll = () => {
- refetchDashboard();
+ flaggedCount: risks.filter((r) => r.impact * r.likelihood >= 12).length,
};
+ return matrix;
+ }, [risks]);
- // Headline metrics sourced directly from live API models
- const portfolioValue = useMemo(() => {
- if (dashboard?.portfolioValue && dashboard.portfolioValue > 0) {
- return dashboard.portfolioValue;
- }
- const budgetTotal = budgets.reduce((sum, b) => sum + (b.amount || 0), 0);
- if (budgetTotal > 0) return budgetTotal;
- const awardsTotal = awards.reduce((sum, a) => sum + (a.amount || 0), 0);
- return awardsTotal;
- }, [dashboard, budgets, awards]);
+ // Quarterly disbursements distribution computed from real disbursement items
+ const quarterlyTrend = useMemo(() => {
+ if (!disbursements || disbursements.length === 0) {
+ return [
+ { quarter: 'Q1', amount: 0, heightPct: 15 },
+ { quarter: 'Q2', amount: 0, heightPct: 15 },
+ { quarter: 'Q3', amount: 0, heightPct: 15 },
+ { quarter: 'Q4', amount: 0, heightPct: 15 },
+ ];
+ }
- const activeAwards = dashboard?.activeAwards ?? (awards.length || projects.length);
+ const qTotals: Record = { Q1: 0, Q2: 0, Q3: 0, Q4: 0 };
+ disbursements.forEach((d) => {
+ const dateStr = d.createdAt || d.updatedAt;
+ if (dateStr) {
+ const month = new Date(dateStr).getMonth(); // 0-11
+ if (month <= 2) qTotals.Q1 += d.amount || 0;
+ else if (month <= 5) qTotals.Q2 += d.amount || 0;
+ else if (month <= 8) qTotals.Q3 += d.amount || 0;
+ else qTotals.Q4 += d.amount || 0;
+ } else {
+ qTotals.Q1 += (d.amount || 0) * 0.25;
+ qTotals.Q2 += (d.amount || 0) * 0.25;
+ qTotals.Q3 += (d.amount || 0) * 0.25;
+ qTotals.Q4 += (d.amount || 0) * 0.25;
+ }
+ });
- const disbursed = useMemo(() => {
- if (dashboard?.disbursed && dashboard.disbursed > 0) {
- return dashboard.disbursed;
- }
- return disbursements
- .filter((d) => d.status === ApprovalStatus.Approved || (d.status as number) === 3)
- .reduce((sum, d) => sum + (d.amount || 0), 0);
- }, [dashboard, disbursements]);
+ const maxQ = Math.max(...Object.values(qTotals), 1);
+ return Object.entries(qTotals).map(([quarter, amount]) => ({
+ quarter,
+ amount,
+ heightPct: Math.max(15, Math.min(100, Math.round((amount / maxQ) * 100))),
+ }));
+ }, [disbursements]);
- const onTrackPercentage = useMemo(() => {
- if (dashboard?.onTrackPercentage !== undefined && dashboard.onTrackPercentage > 0) {
- return dashboard.onTrackPercentage;
- }
- if (projects.length === 0) return 0;
- const activeOnTrack = projects.filter((p) => p.status === ApprovalStatus.Approved).length;
- return Math.round((activeOnTrack / projects.length) * 100);
- }, [dashboard, projects]);
-
- const atRiskCount = useMemo(() => {
- if (dashboard?.atRiskCount !== undefined) {
- return dashboard.atRiskCount;
- }
- return risks.filter((r) => r.likelihood * r.impact >= 12).length;
- }, [dashboard, risks]);
-
- const beneficiariesReached = dashboard?.beneficiariesReached ?? 0;
-
- // Real sector breakdown calculated dynamically from actual project records
- const sectorData = useMemo(() => {
- if (!projects || projects.length === 0) return [];
- const counts: Record = {};
- projects.forEach((p) => {
- const label = getIndustrySectorLabel(p.sector);
- counts[label] = (counts[label] || 0) + 1;
- });
-
- const colors = ['bg-emerald-500', 'bg-teal-500', 'bg-blue-500', 'bg-indigo-500', 'bg-purple-500', 'bg-amber-500'];
- const totalProjects = projects.length;
-
- return Object.entries(counts).map(([name, count], index) => {
- const percentage = Math.round((count / totalProjects) * 100);
- const amount = portfolioValue > 0 ? Math.round((portfolioValue / totalProjects) * count) : 0;
- return {
- name,
- count,
- percentage,
- amount,
- color: colors[index % colors.length],
- };
- });
- }, [projects, portfolioValue]);
-
- // Risk heatmap matrix computed directly from registered risk records
- const riskHeatmap = useMemo(() => {
- const matrix = {
- highImpHighLik: risks.filter((r) => r.impact >= 4 && r.likelihood >= 4).length,
- highImpMedLik: risks.filter((r) => r.impact >= 4 && r.likelihood === 3).length,
- highImpLowLik: risks.filter((r) => r.impact >= 4 && r.likelihood <= 2).length,
-
- medImpHighLik: risks.filter((r) => r.impact === 3 && r.likelihood >= 4).length,
- medImpMedLik: risks.filter((r) => r.impact === 3 && r.likelihood === 3).length,
- medImpLowLik: risks.filter((r) => r.impact === 3 && r.likelihood <= 2).length,
-
- lowImpHighLik: risks.filter((r) => r.impact <= 2 && r.likelihood >= 4).length,
- lowImpMedLik: risks.filter((r) => r.impact <= 2 && r.likelihood === 3).length,
- lowImpLowLik: risks.filter((r) => r.impact <= 2 && r.likelihood <= 2).length,
-
- flaggedCount: risks.filter((r) => r.impact * r.likelihood >= 12).length,
+ // Attention Projects derived directly from live project data
+ const attentionProjects = useMemo(() => {
+ if (!projects || projects.length === 0) return [];
+ return projects
+ .filter((p) => p.status === ApprovalStatus.Rejected || p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review)
+ .slice(0, 5)
+ .map((p) => {
+ const isRejected = p.status === ApprovalStatus.Rejected;
+ const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review;
+ return {
+ id: p.id,
+ name: p.name,
+ programme: p.programmeName || `Programme ${p.programmeId}`,
+ status: isRejected ? 'At Risk' : isPending ? 'Pending' : 'Active',
+ risk: isRejected ? 'RED' : isPending ? 'AMBER' : 'GREEN',
+ financialVariance: isRejected ? '-15%' : '0%',
+ varianceType: isRejected ? 'negative' : 'positive',
};
- return matrix;
- }, [risks]);
+ });
+ }, [projects]);
- // Quarterly disbursements distribution computed from real disbursement items
- const quarterlyTrend = useMemo(() => {
- if (!disbursements || disbursements.length === 0) {
- return [
- { quarter: 'Q1', amount: 0, heightPct: 15 },
- { quarter: 'Q2', amount: 0, heightPct: 15 },
- { quarter: 'Q3', amount: 0, heightPct: 15 },
- { quarter: 'Q4', amount: 0, heightPct: 15 },
- ];
- }
-
- const qTotals: Record = { Q1: 0, Q2: 0, Q3: 0, Q4: 0 };
- disbursements.forEach((d) => {
- const dateStr = d.createdAt || d.updatedAt;
- if (dateStr) {
- const month = new Date(dateStr).getMonth(); // 0-11
- if (month <= 2) qTotals.Q1 += d.amount || 0;
- else if (month <= 5) qTotals.Q2 += d.amount || 0;
- else if (month <= 8) qTotals.Q3 += d.amount || 0;
- else qTotals.Q4 += d.amount || 0;
- } else {
- qTotals.Q1 += (d.amount || 0) * 0.25;
- qTotals.Q2 += (d.amount || 0) * 0.25;
- qTotals.Q3 += (d.amount || 0) * 0.25;
- qTotals.Q4 += (d.amount || 0) * 0.25;
- }
- });
-
- const maxQ = Math.max(...Object.values(qTotals), 1);
- return Object.entries(qTotals).map(([quarter, amount]) => ({
- quarter,
- amount,
- heightPct: Math.max(15, Math.min(100, Math.round((amount / maxQ) * 100))),
- }));
- }, [disbursements]);
-
- // Attention Projects derived directly from live project data
- const attentionProjects = useMemo(() => {
- if (!projects || projects.length === 0) return [];
- return projects
- .filter((p) => p.status === ApprovalStatus.Rejected || p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review)
- .slice(0, 5)
- .map((p) => {
- const isRejected = p.status === ApprovalStatus.Rejected;
- const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review;
- return {
- id: p.id,
- name: p.name,
- programme: p.programmeName || `Programme ${p.programmeId}`,
- status: isRejected ? 'At Risk' : isPending ? 'Pending' : 'Active',
- risk: isRejected ? 'RED' : isPending ? 'AMBER' : 'GREEN',
- financialVariance: isRejected ? '-15%' : '0%',
- varianceType: isRejected ? 'negative' : 'positive',
- };
- });
- }, [projects]);
-
- return (
-
- {/* Page Title & Filter Bar */}
-
-
-
-
- Executive Portfolio Dashboard
-
-
- Live STS Feed
-
-
-
- Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action.
-
-
-
- {/* Filters */}
-
-
-
- Filter:
-
-
-
-
-
-
-
-
-
-
-
-
- {/* 6 Headline Portfolio KPI Cards */}
-
- {/* Card 1: Portfolio Value */}
-
-
-
Portfolio Value
-
-
-
-
-
-
{formatCompactZAR(portfolioValue)}
-
{formatZAR(portfolioValue)}
-
-
-
- {/* Card 2: Active Awards */}
-
-
-
-
{activeAwards}
-
- Across {portfolios.length} portfolio{portfolios.length === 1 ? '' : 's'}
-
-
-
-
- {/* Card 3: Disbursed */}
-
-
-
-
{formatCompactZAR(disbursed)}
-
- {portfolioValue > 0 ? `${((disbursed / portfolioValue) * 100).toFixed(1)}% of commitments` : '0.0% of commitments'}
-
-
-
-
- {/* Card 4: On Track */}
-
-
-
-
{onTrackPercentage}%
-
Milestones verified
-
-
-
- {/* Card 5: At Risk */}
-
-
-
-
{atRiskCount}
-
Requires PM action
-
-
-
- {/* Card 6: Beneficiaries Reached */}
-
-
-
-
- {beneficiariesReached >= 1000 ? `${(beneficiariesReached / 1000).toFixed(0)}k` : beneficiariesReached}
-
-
Directly impacted
-
-
-
-
- {/* Visual Models Section (Sector, Heatmap, Impact Trend) */}
-
- {/* Panel 1: Funding by Sector */}
-
-
-
-
Funding by Sector
-
- API Calculated
-
-
-
- {/* Circular Graphic Simulation */}
-
-
-
- {sectorData.length}
-
- {sectorData.length === 1 ? 'Sector' : 'Sectors'}
-
-
-
-
-
- {/* Sector Progress Bars */}
-
- {sectorData.length === 0 ? (
-
No active project sectors assigned yet.
- ) : (
- sectorData.map((s) => (
-
-
- {s.name} ({s.count})
- {s.percentage}%
-
-
-
- ))
- )}
-
-
-
-
-
Primary Focus: {sectorData[0]?.name || 'All Sectors'}
-
- View Portfolios
-
-
-
-
- {/* Panel 2: Portfolio Risk Heatmap */}
-
-
-
-
Portfolio Risk Heatmap
-
- {riskHeatmap.flaggedCount} Flagged
-
-
-
-
- Matrix distribution of project risks by Likelihood (1–5) and Impact (1–5).
-
-
- {/* 3x3 Heatmap Matrix with real counted risks */}
-
- {/* Row 1 - High Impact */}
-
- Moderate
- {riskHeatmap.highImpLowLik} {riskHeatmap.highImpLowLik === 1 ? 'Risk' : 'Risks'}
-
-
- High
- {riskHeatmap.highImpMedLik} {riskHeatmap.highImpMedLik === 1 ? 'Risk' : 'Risks'}
-
-
- Critical
- {riskHeatmap.highImpHighLik} {riskHeatmap.highImpHighLik === 1 ? 'Risk' : 'Risks'}
-
-
- {/* Row 2 - Medium Impact */}
-
- Low
- {riskHeatmap.medImpLowLik} {riskHeatmap.medImpLowLik === 1 ? 'Risk' : 'Risks'}
-
-
- Moderate
- {riskHeatmap.medImpMedLik} {riskHeatmap.medImpMedLik === 1 ? 'Risk' : 'Risks'}
-
-
- High
- {riskHeatmap.medImpHighLik} {riskHeatmap.medImpHighLik === 1 ? 'Risk' : 'Risks'}
-
-
- {/* Row 3 - Low Impact */}
-
- Trivial
- {riskHeatmap.lowImpLowLik} {riskHeatmap.lowImpLowLik === 1 ? 'Risk' : 'Risks'}
-
-
- Low
- {riskHeatmap.lowImpMedLik} {riskHeatmap.lowImpMedLik === 1 ? 'Risk' : 'Risks'}
-
-
- Moderate
- {riskHeatmap.lowImpHighLik} {riskHeatmap.lowImpHighLik === 1 ? 'Risk' : 'Risks'}
-
-
-
-
-
-
Total Registered Risks: {risks.length}
-
- Risk Register
-
-
-
-
- {/* Panel 3: Funding-to-Impact Trend */}
-
-
-
-
Disbursement Distribution
-
- {disbursements.length} Tranche{disbursements.length === 1 ? '' : 's'}
-
-
-
-
- Quarterly release distribution across active portfolios and milestone verifications.
-
-
- {/* Graphic Trend Visualization from Live Disbursements */}
-
- {quarterlyTrend.map((q) => (
-
- ))}
-
-
-
-
-
- Commitment Allocation
-
-
-
- Actual Disbursed
-
-
-
-
-
-
Total Disbursed: {formatCompactZAR(disbursed)}
-
- Financials
-
-
-
-
-
- {/* Projects Requiring Attention Table */}
-
-
-
-
Projects Requiring Attention
-
- Prioritized by risk severity, delivery delay, or financial variance threshold breach.
-
-
-
- Open Project Directory →
-
-
-
-
-
-
-
- | Project |
- Programme |
- Status |
- Risk |
- Financial Variance |
- Action |
-
-
-
- {attentionProjects.length === 0 ? (
-
- |
- No active projects currently flagged for executive attention. All projects are operating within target thresholds.
- |
-
- ) : (
- attentionProjects.map((p) => (
-
- |
- {p.name}
- ID: {p.id}
- |
- {p.programme} |
-
-
- {p.status}
-
- |
-
-
- {p.risk}
-
- |
-
-
- {p.financialVariance}
-
- |
-
-
- Review
-
-
- |
-
- ))
- )}
-
-
-
-
+ return (
+
+ {/* Page Title & Filter Bar */}
+
+
+
+
+ Executive Portfolio Dashboard
+
+
+ Live Portfolio Feed
+
+
+
+ Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action.
+
- );
+
+ {/* Filters */}
+
+
+
+ Filter:
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* 6 Headline Portfolio KPI Cards */}
+
+ {/* Card 1: Portfolio Value */}
+
+
+
Portfolio Value
+
+
+
+
+
+
{formatCompactZAR(portfolioValue)}
+
{formatZAR(portfolioValue)}
+
+
+
+ {/* Card 2: Active Awards */}
+
+
+
+
{activeAwards}
+
+ Across {portfolios.length} portfolio{portfolios.length === 1 ? '' : 's'}
+
+
+
+
+ {/* Card 3: Disbursed */}
+
+
+
+
{formatCompactZAR(disbursed)}
+
+ {portfolioValue > 0 ? `${((disbursed / portfolioValue) * 100).toFixed(1)}% of commitments` : '0.0% of commitments'}
+
+
+
+
+ {/* Card 4: On Track */}
+
+
+
+
{onTrackPercentage}%
+
Milestones verified
+
+
+
+ {/* Card 5: At Risk */}
+
+
+
+
{atRiskCount}
+
Requires PM action
+
+
+
+ {/* Card 6: Beneficiaries Reached */}
+
+
+
+
+ {beneficiariesReached >= 1000 ? `${(beneficiariesReached / 1000).toFixed(0)}k` : beneficiariesReached}
+
+
Directly impacted
+
+
+
+
+ {/* Visual Models Section (Sector, Heatmap, Impact Trend) */}
+
+ {/* Panel 1: Funding by Sector */}
+
+
+
+
Funding by Sector
+
+ API Calculated
+
+
+
+ {/* Circular Graphic Simulation */}
+
+
+
+ {sectorData.length}
+
+ {sectorData.length === 1 ? 'Sector' : 'Sectors'}
+
+
+
+
+
+ {/* Sector Progress Bars */}
+
+ {sectorData.length === 0 ? (
+
No active project sectors assigned yet.
+ ) : (
+ sectorData.map((s) => (
+
+
+ {s.name} ({s.count})
+ {s.percentage}%
+
+
+
+ ))
+ )}
+
+
+
+
+
Primary Focus: {sectorData[0]?.name || 'All Sectors'}
+
+ View Portfolios
+
+
+
+
+ {/* Panel 2: Portfolio Risk Heatmap */}
+
+
+
+
Portfolio Risk Heatmap
+
+ {riskHeatmap.flaggedCount} Flagged
+
+
+
+
+ Matrix distribution of project risks by Likelihood (1–5) and Impact (1–5).
+
+
+ {/* 3x3 Heatmap Matrix with real counted risks */}
+
+ {/* Row 1 - High Impact */}
+
+ Moderate
+ {riskHeatmap.highImpLowLik} {riskHeatmap.highImpLowLik === 1 ? 'Risk' : 'Risks'}
+
+
+ High
+ {riskHeatmap.highImpMedLik} {riskHeatmap.highImpMedLik === 1 ? 'Risk' : 'Risks'}
+
+
+ Critical
+ {riskHeatmap.highImpHighLik} {riskHeatmap.highImpHighLik === 1 ? 'Risk' : 'Risks'}
+
+
+ {/* Row 2 - Medium Impact */}
+
+ Low
+ {riskHeatmap.medImpLowLik} {riskHeatmap.medImpLowLik === 1 ? 'Risk' : 'Risks'}
+
+
+ Moderate
+ {riskHeatmap.medImpMedLik} {riskHeatmap.medImpMedLik === 1 ? 'Risk' : 'Risks'}
+
+
+ High
+ {riskHeatmap.medImpHighLik} {riskHeatmap.medImpHighLik === 1 ? 'Risk' : 'Risks'}
+
+
+ {/* Row 3 - Low Impact */}
+
+ Trivial
+ {riskHeatmap.lowImpLowLik} {riskHeatmap.lowImpLowLik === 1 ? 'Risk' : 'Risks'}
+
+
+ Low
+ {riskHeatmap.lowImpMedLik} {riskHeatmap.lowImpMedLik === 1 ? 'Risk' : 'Risks'}
+
+
+ Moderate
+ {riskHeatmap.lowImpHighLik} {riskHeatmap.lowImpHighLik === 1 ? 'Risk' : 'Risks'}
+
+
+
+
+
+
Total Registered Risks: {risks.length}
+
+ Risk Register
+
+
+
+
+ {/* Panel 3: Funding-to-Impact Trend */}
+
+
+
+
Disbursement Distribution
+
+ {disbursements.length} Tranche{disbursements.length === 1 ? '' : 's'}
+
+
+
+
+ Quarterly release distribution across active portfolios and milestone verifications.
+
+
+ {/* Graphic Trend Visualization from Live Disbursements */}
+
+ {quarterlyTrend.map((q) => (
+
+ ))}
+
+
+
+
+
+ Commitment Allocation
+
+
+
+ Actual Disbursed
+
+
+
+
+
+
Total Disbursed: {formatCompactZAR(disbursed)}
+
+ Financials
+
+
+
+
+
+ {/* Projects Requiring Attention Table */}
+
+
+
+
Projects Requiring Attention
+
+ Prioritized by risk severity, delivery delay, or financial variance threshold breach.
+
+
+
+ Open Project Directory →
+
+
+
+
+
+
+
+ | Project |
+ Programme |
+ Status |
+ Risk |
+ Financial Variance |
+ Action |
+
+
+
+ {attentionProjects.length === 0 ? (
+
+ |
+ No active projects currently flagged for executive attention. All projects are operating within target thresholds.
+ |
+
+ ) : (
+ attentionProjects.map((p) => (
+
+ |
+ {p.name}
+ ID: {p.id}
+ |
+ {p.programme} |
+
+
+ {p.status}
+
+ |
+
+
+ {p.risk}
+
+ |
+
+
+ {p.financialVariance}
+
+ |
+
+
+ Review
+
+
+ |
+
+ ))
+ )}
+
+
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
index 7f4ad33..4de67c8 100644
--- a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx
@@ -4,73 +4,80 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
- Building2,
- Plus,
- FolderKanban,
- Edit2,
- Mail,
- Shield,
- Layers,
- ChevronRight,
- Search,
+ Building2,
+ Plus,
+ FolderKanban,
+ Edit2,
+ Mail,
+ Shield,
+ Layers,
+ ChevronRight,
+ Search,
+ Coins,
+ Eye,
} from 'lucide-react';
import {
- getOrganisations,
- createOrganisation,
- updateOrganisation,
- getAllPortfolios,
- createPortfolio,
- updatePortfolio,
- getAllProgrammes,
- createProgramme,
- updateProgramme,
+ getOrganisations,
+ createOrganisation,
+ updateOrganisation,
+ getAllPortfolios,
+ getPortfolio,
+ createPortfolio,
+ updatePortfolio,
+ getAllProgrammes,
+ getProgramme,
+ createProgramme,
+ updateProgramme,
+ getInstruments,
+ getPortfolioProgrammes,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import { useAuth } from '../auth/AuthProvider';
import {
- Organisation,
- Portfolio,
- Programme,
- OrganisationType,
- ActivityStatus,
- ApprovalStatus,
- CreateOrganisationRequest,
- CreatePortfolioRequest,
- CreateProgrammeRequest,
+ Organisation,
+ Portfolio,
+ Programme,
+ Instrument,
+ OrganisationType,
+ ActivityStatus,
+ ApprovalStatus,
+ CreateOrganisationRequest,
+ CreatePortfolioRequest,
+ CreateProgrammeRequest,
} from '../types';
import {
- getOrganisationTypeLabel,
- getActivityStatusBadge,
- getApprovalStatusBadge,
- formatDate,
- getUserEmail,
- ORGANISATION_TYPE_OPTIONS,
- ACTIVITY_STATUS_OPTIONS,
- APPROVAL_STATUS_OPTIONS,
+ getOrganisationTypeLabel,
+ getActivityStatusBadge,
+ getApprovalStatusBadge,
+ formatDate,
+ getUserEmail,
+ ORGANISATION_TYPE_OPTIONS,
+ ACTIVITY_STATUS_OPTIONS,
+ APPROVAL_STATUS_OPTIONS,
} from '../lib/formatters';
// Validation Schemas matching API constraints
const organisationSchema = z.object({
- name: z.string().min(3, 'Name must be at least 3 characters'),
- registrationNo: z.string().min(4, 'Registration number is mandatory'),
- email: z.string().email('Enter a valid email address'),
- type: z.coerce.number().min(1).max(27),
- status: z.coerce.number().optional(),
+ name: z.string().min(3, 'Name must be at least 3 characters'),
+ registrationNo: z.string().min(4, 'Registration number is mandatory'),
+ email: z.string().email('Enter a valid email address'),
+ type: z.coerce.number().min(1).max(27),
+ status: z.coerce.number().optional(),
});
const portfolioSchema = z.object({
- name: z.string().min(3, 'Portfolio name is required'),
- description: z.string().min(5, 'Description is required'),
- organisationId: z.coerce.number().min(1, 'Select a parent organisation'),
- ownerId: z.string().min(1, 'Owner ID is required'),
+ name: z.string().min(3, 'Portfolio name is required'),
+ description: z.string().min(5, 'Description is required'),
+ organisationId: z.coerce.number().min(1, 'Select a parent organisation'),
+ ownerId: z.string().min(1, 'Owner ID is required'),
});
const programmeSchema = z.object({
- name: z.string().min(3, 'Programme name is required'),
- portfolioId: z.coerce.number().min(1, 'Select a parent portfolio'),
- status: z.coerce.number().optional(),
- ownerId: z.string().min(1, 'Owner ID is required'),
+ name: z.string().min(3, 'Programme name is required'),
+ portfolioId: z.coerce.number().min(1, 'Select a parent portfolio'),
+ status: z.coerce.number().optional(),
+ ownerId: z.string().min(1, 'Owner ID is required'),
});
type OrganisationFormValues = z.infer
;
@@ -78,783 +85,953 @@ type PortfolioFormValues = z.infer;
type ProgrammeFormValues = z.infer;
export const GovernancePage: React.FC = () => {
- const queryClient = useQueryClient();
- const { sub, user } = useAuth();
- const currentUserId = sub || user?.id || '';
- const { success, error: toastError } = useToast();
- const [activeTab, setActiveTab] = useState<'organisations' | 'portfolios' | 'programmes'>('organisations');
- const [searchTerm, setSearchTerm] = useState('');
+ const queryClient = useQueryClient();
+ const { sub, user } = useAuth();
+ const currentUserId = sub || user?.id || '';
+ const { success, error: toastError } = useToast();
+ const [activeTab, setActiveTab] = useState<'organisations' | 'portfolios' | 'programmes'>('organisations');
+ const [searchTerm, setSearchTerm] = useState('');
- // Modals & Editing State
- const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
- const [editingOrg, setEditingOrg] = useState(null);
+ // Modals & Editing State
+ const [isOrgModalOpen, setIsOrgModalOpen] = useState(false);
+ const [editingOrg, setEditingOrg] = useState(null);
- const [isPortfolioModalOpen, setIsPortfolioModalOpen] = useState(false);
- const [editingPortfolio, setEditingPortfolio] = useState(null);
+ const [isPortfolioModalOpen, setIsPortfolioModalOpen] = useState(false);
+ const [editingPortfolio, setEditingPortfolio] = useState(null);
- const [isProgrammeModalOpen, setIsProgrammeModalOpen] = useState(false);
- const [editingProgramme, setEditingProgramme] = useState(null);
+ const [isProgrammeModalOpen, setIsProgrammeModalOpen] = useState(false);
+ const [editingProgramme, setEditingProgramme] = useState(null);
- // Queries
- const { data: organisations = [], isLoading: loadingOrgs } = useQuery({
- queryKey: ['organisations'],
- queryFn: () => getOrganisations(),
- });
+ // Inspection states
+ const [selectedProgrammeForInstruments, setSelectedProgrammeForInstruments] = useState(null);
+ const [isInstrumentsModalOpen, setIsInstrumentsModalOpen] = useState(false);
- const { data: portfolios = [], isLoading: loadingPortfolios } = useQuery({
- queryKey: ['portfolios'],
- queryFn: () => getAllPortfolios(),
- });
+ const [selectedPortfolioForProgrammes, setSelectedPortfolioForProgrammes] = useState(null);
+ const [isPortfolioProgrammesModalOpen, setIsPortfolioProgrammesModalOpen] = useState(false);
- const { data: programmes = [], isLoading: loadingProgrammes } = useQuery({
- queryKey: ['programmes'],
- queryFn: () => getAllProgrammes(),
- });
+ // Queries
+ const { data: organisations = [], isLoading: loadingOrgs } = useQuery({
+ queryKey: ['organisations'],
+ queryFn: () => getOrganisations(),
+ });
- // Forms
- const orgForm = useForm({
- resolver: zodResolver(organisationSchema) as any,
- defaultValues: {
- name: '',
- registrationNo: '',
- email: '',
- type: OrganisationType.Enterprise,
- status: ActivityStatus.Active,
- },
- });
+ const { data: portfolios = [], isLoading: loadingPortfolios } = useQuery({
+ queryKey: ['portfolios'],
+ queryFn: () => getAllPortfolios(),
+ });
- const portfolioForm = useForm({
- resolver: zodResolver(portfolioSchema) as any,
- defaultValues: {
- name: '',
- description: '',
- organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
- ownerId: currentUserId,
- },
- });
+ const { data: programmes = [], isLoading: loadingProgrammes } = useQuery({
+ queryKey: ['programmes'],
+ queryFn: () => getAllProgrammes(),
+ });
- const programmeForm = useForm({
- resolver: zodResolver(programmeSchema) as any,
- defaultValues: {
- name: '',
- portfolioId: portfolios[0]?.id ? Number(portfolios[0].id) : 201,
- status: ApprovalStatus.Approved,
- ownerId: currentUserId,
- },
- });
+ const { data: programmeInstruments = [], isLoading: loadingInstruments } = useQuery({
+ queryKey: ['instruments', selectedProgrammeForInstruments?.id],
+ queryFn: () => (selectedProgrammeForInstruments ? getInstruments(selectedProgrammeForInstruments.id) : Promise.resolve([])),
+ enabled: !!selectedProgrammeForInstruments && isInstrumentsModalOpen,
+ });
- // Mutations
- const orgMutation = useMutation({
- mutationFn: (data: OrganisationFormValues) => {
- if (editingOrg) {
- return updateOrganisation({
- organisationId: editingOrg.id,
- name: data.name,
- registrationNo: data.registrationNo,
- email: data.email,
- type: Number(data.type),
- status: data.status !== undefined ? Number(data.status) : undefined,
- });
- }
- return createOrganisation(data as CreateOrganisationRequest);
- },
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['organisations'] });
- success(editingOrg ? 'Organisation updated' : 'Organisation registered successfully');
- setIsOrgModalOpen(false);
- setEditingOrg(null);
- orgForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to save organisation', String(err));
- },
- });
+ const { data: portfolioProgrammesList = [], isLoading: loadingPortfolioProgrammes } = useQuery({
+ queryKey: ['portfolioProgrammes', selectedPortfolioForProgrammes?.id],
+ queryFn: () => (selectedPortfolioForProgrammes ? getPortfolioProgrammes(selectedPortfolioForProgrammes.id) : Promise.resolve([])),
+ enabled: !!selectedPortfolioForProgrammes && isPortfolioProgrammesModalOpen,
+ });
- const portfolioMutation = useMutation({
- mutationFn: (data: PortfolioFormValues) => {
- if (editingPortfolio) {
- return updatePortfolio({
- portfolioId: editingPortfolio.id,
- name: data.name,
- description: data.description,
- organisationId: Number(data.organisationId),
- ownerId: data.ownerId,
- });
- }
- return createPortfolio(data as CreatePortfolioRequest);
- },
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['portfolios'] });
- success(editingPortfolio ? 'Portfolio updated successfully' : 'Portfolio created successfully');
- setIsPortfolioModalOpen(false);
- setEditingPortfolio(null);
- portfolioForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to save portfolio', String(err));
- },
- });
+ // Forms
+ const orgForm = useForm({
+ resolver: zodResolver(organisationSchema) as any,
+ defaultValues: {
+ name: '',
+ registrationNo: '',
+ email: '',
+ type: OrganisationType.Enterprise,
+ status: ActivityStatus.Active,
+ },
+ });
- const programmeMutation = useMutation({
- mutationFn: (data: ProgrammeFormValues) => {
- if (editingProgramme) {
- return updateProgramme({
- programmeId: editingProgramme.id,
- name: data.name,
- portfolioId: Number(data.portfolioId),
- status: data.status !== undefined ? Number(data.status) : undefined,
- ownerId: data.ownerId,
- });
- }
- return createProgramme(data as CreateProgrammeRequest);
- },
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['programmes'] });
- success(editingProgramme ? 'Programme updated successfully' : 'Programme created successfully');
- setIsProgrammeModalOpen(false);
- setEditingProgramme(null);
- programmeForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to save programme', String(err));
- },
- });
+ const portfolioForm = useForm({
+ resolver: zodResolver(portfolioSchema) as any,
+ defaultValues: {
+ name: '',
+ description: '',
+ organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
+ ownerId: currentUserId,
+ },
+ });
- const openCreateOrg = () => {
- setEditingOrg(null);
- orgForm.reset({
- name: '',
- registrationNo: '',
- email: '',
- type: OrganisationType.Enterprise,
- status: ActivityStatus.Active,
+ const programmeForm = useForm({
+ resolver: zodResolver(programmeSchema) as any,
+ defaultValues: {
+ name: '',
+ portfolioId: portfolios[0]?.id ? Number(portfolios[0].id) : 201,
+ status: ApprovalStatus.Approved,
+ ownerId: currentUserId,
+ },
+ });
+
+ // Mutations
+ const orgMutation = useMutation({
+ mutationFn: (data: OrganisationFormValues) => {
+ if (editingOrg) {
+ return updateOrganisation({
+ organisationId: editingOrg.id,
+ name: data.name,
+ registrationNo: data.registrationNo,
+ email: data.email,
+ type: Number(data.type),
+ status: data.status !== undefined ? Number(data.status) : undefined,
});
- setIsOrgModalOpen(true);
- };
+ }
+ return createOrganisation(data as CreateOrganisationRequest);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['organisations'] });
+ success(editingOrg ? 'Organisation updated' : 'Organisation registered successfully');
+ setIsOrgModalOpen(false);
+ setEditingOrg(null);
+ orgForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to save organisation', String(err));
+ },
+ });
- const openEditOrg = (org: Organisation) => {
- setEditingOrg(org);
- orgForm.reset({
- name: org.name || '',
- registrationNo: org.registrationNo || '',
- email: org.email || '',
- type: org.type || OrganisationType.Enterprise,
- status: org.status ?? ActivityStatus.Active,
+ const portfolioMutation = useMutation({
+ mutationFn: (data: PortfolioFormValues) => {
+ if (editingPortfolio) {
+ return updatePortfolio({
+ portfolioId: editingPortfolio.id,
+ name: data.name,
+ description: data.description,
+ organisationId: Number(data.organisationId),
+ ownerId: data.ownerId,
});
- setIsOrgModalOpen(true);
- };
+ }
+ return createPortfolio(data as CreatePortfolioRequest);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['portfolios'] });
+ success(editingPortfolio ? 'Portfolio updated successfully' : 'Portfolio created successfully');
+ setIsPortfolioModalOpen(false);
+ setEditingPortfolio(null);
+ portfolioForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to save portfolio', String(err));
+ },
+ });
- const openCreatePortfolio = () => {
- setEditingPortfolio(null);
- portfolioForm.reset({
- name: '',
- description: '',
- organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
- ownerId: currentUserId,
+ const programmeMutation = useMutation({
+ mutationFn: (data: ProgrammeFormValues) => {
+ if (editingProgramme) {
+ return updateProgramme({
+ programmeId: editingProgramme.id,
+ name: data.name,
+ portfolioId: Number(data.portfolioId),
+ status: data.status !== undefined ? Number(data.status) : undefined,
+ ownerId: data.ownerId,
});
- setIsPortfolioModalOpen(true);
- };
+ }
+ return createProgramme(data as CreateProgrammeRequest);
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['programmes'] });
+ success(editingProgramme ? 'Programme updated successfully' : 'Programme created successfully');
+ setIsProgrammeModalOpen(false);
+ setEditingProgramme(null);
+ programmeForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to save programme', String(err));
+ },
+ });
- const openEditPortfolio = (p: Portfolio) => {
- setEditingPortfolio(p);
- portfolioForm.reset({
- name: p.name || '',
- description: p.description || '',
- organisationId: Number(p.organisationId) || 101,
- ownerId: p.ownedBy || currentUserId,
- });
- setIsPortfolioModalOpen(true);
- };
+ const openCreateOrg = () => {
+ setEditingOrg(null);
+ orgForm.reset({
+ name: '',
+ registrationNo: '',
+ email: '',
+ type: OrganisationType.Enterprise,
+ status: ActivityStatus.Active,
+ });
+ setIsOrgModalOpen(true);
+ };
- const openCreateProgramme = () => {
- setEditingProgramme(null);
- programmeForm.reset({
- name: '',
- portfolioId: portfolios[0]?.id ? Number(portfolios[0].id) : 201,
- status: ApprovalStatus.Approved,
- ownerId: currentUserId,
- });
- setIsProgrammeModalOpen(true);
- };
+ const openEditOrg = (org: Organisation) => {
+ setEditingOrg(org);
+ orgForm.reset({
+ name: org.name || '',
+ registrationNo: org.registrationNo || '',
+ email: org.email || '',
+ type: org.type || OrganisationType.Enterprise,
+ status: org.status ?? ActivityStatus.Active,
+ });
+ setIsOrgModalOpen(true);
+ };
- const openEditProgramme = (pr: Programme) => {
- setEditingProgramme(pr);
- programmeForm.reset({
- name: pr.name || '',
- portfolioId: Number(pr.portfolioId) || 201,
- status: pr.status ?? ApprovalStatus.Approved,
- ownerId: pr.ownedBy || currentUserId,
- });
- setIsProgrammeModalOpen(true);
- };
+ const openCreatePortfolio = () => {
+ setEditingPortfolio(null);
+ portfolioForm.reset({
+ name: '',
+ description: '',
+ organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
+ ownerId: currentUserId,
+ });
+ setIsPortfolioModalOpen(true);
+ };
- // Filtered lists
- const filteredOrgs = organisations.filter(
- (o) =>
- o.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
- o.registrationNo?.toLowerCase().includes(searchTerm.toLowerCase()) ||
- o.email?.toLowerCase().includes(searchTerm.toLowerCase())
- );
+ const openEditPortfolio = (p: Portfolio) => {
+ setEditingPortfolio(p);
+ portfolioForm.reset({
+ name: p.name || '',
+ description: p.description || '',
+ organisationId: Number(p.organisationId) || 101,
+ ownerId: p.ownedBy || currentUserId,
+ });
+ setIsPortfolioModalOpen(true);
+ };
- return (
-
- {/* Header */}
-
-
-
-
- Governance & Structure
-
-
- Directory
-
-
-
- Maintain Master Organisation Registry, Funder Portfolios, and Strategic Programmes hierarchy.
-
-
+ const openCreateProgramme = () => {
+ setEditingProgramme(null);
+ programmeForm.reset({
+ name: '',
+ portfolioId: portfolios[0]?.id ? Number(portfolios[0].id) : 201,
+ status: ApprovalStatus.Approved,
+ ownerId: currentUserId,
+ });
+ setIsProgrammeModalOpen(true);
+ };
- {/* Tab Switcher & CTA */}
-
- {activeTab === 'organisations' && (
-
- )}
- {activeTab === 'portfolios' && (
-
- )}
- {activeTab === 'programmes' && (
-
- )}
-
-
+ const openEditProgramme = (pr: Programme) => {
+ setEditingProgramme(pr);
+ programmeForm.reset({
+ name: pr.name || '',
+ portfolioId: Number(pr.portfolioId) || 201,
+ status: pr.status ?? ApprovalStatus.Approved,
+ ownerId: pr.ownedBy || currentUserId,
+ });
+ setIsProgrammeModalOpen(true);
+ };
- {/* Tabs */}
-
-
-
-
-
+ // Filtered lists
+ const filteredOrgs = organisations.filter(
+ (o) =>
+ o.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ o.registrationNo?.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ o.email?.toLowerCase().includes(searchTerm.toLowerCase())
+ );
- {/* Search Bar */}
- {activeTab === 'organisations' && (
-
-
- setSearchTerm(e.target.value)}
- className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
- />
-
- )}
-
- {/* Tab 1: Organisations Table */}
- {activeTab === 'organisations' && (
-
-
-
-
-
- | Organisation Name |
- Registration No |
- Entity Type |
- Contact Email |
- Status |
- Actions |
-
-
-
- {loadingOrgs ? (
-
- |
- Loading registered organisations...
- |
-
- ) : filteredOrgs.length === 0 ? (
-
- |
- No organisations found matching query.
- |
-
- ) : (
- filteredOrgs.map((org) => {
- const statusBadge = getActivityStatusBadge(org.status);
- return (
-
- |
- {org.name}
- ID: {org.id}
- |
- {org.registrationNo || '—'} |
-
-
- {getOrganisationTypeLabel(org.type)}
-
- |
-
-
-
- {org.email}
-
- |
-
-
- {statusBadge.label}
-
- |
-
-
- |
-
- );
- })
- )}
-
-
-
-
- )}
-
- {/* Tab 2: Portfolios List */}
- {activeTab === 'portfolios' && (
-
- {loadingPortfolios ? (
-
Loading portfolios...
- ) : (
- portfolios.map((p) => {
- const org = organisations.find((o) => String(o.id) === String(p.organisationId));
- const progCount = programmes.filter((pr) => String(pr.portfolioId) === String(p.id)).length;
- return (
-
-
-
-
- Portfolio #{p.id}
-
- {progCount} Programmes
-
-
{p.name}
-
{p.description}
-
-
-
-
- {org ? org.name : `Org #${p.organisationId}`}
- {formatDate(p.createdAt)}
-
-
-
- Owner: {getUserEmail(p.ownedBy)}
-
-
-
-
-
- );
- })
- )}
-
- )}
-
- {/* Tab 3: Programmes Hierarchy */}
- {activeTab === 'programmes' && (
-
-
-
-
-
- | Programme Name |
- Parent Portfolio |
- Governance Status |
- Creation Date |
- Owner Email |
- Actions |
-
-
-
- {loadingProgrammes ? (
-
- |
- Loading programmes...
- |
-
- ) : (
- programmes.map((pr) => {
- const port = portfolios.find((p) => String(p.id) === String(pr.portfolioId));
- const statusBadge = getApprovalStatusBadge(pr.status);
- return (
-
- |
- {pr.name}
- ID: {pr.id}
- |
-
- {port ? port.name : `Portfolio #${pr.portfolioId}`}
- |
-
-
- {statusBadge.label}
-
- |
- {formatDate(pr.createdAt)} |
-
- {getUserEmail(pr.ownedBy)}
- |
-
-
- |
-
- );
- })
- )}
-
-
-
-
- )}
-
- {/* Modal: Register / Edit Organisation */}
-
setIsOrgModalOpen(false)}
- title={editingOrg ? 'Edit Organisation Profile' : 'Register New Organisation'}
- subtitle="Mandatory master data profile for Recipient or Funder entities (POPIA compliant)"
- >
-
-
-
- {/* Modal: Create / Edit Portfolio */}
-
{
- setIsPortfolioModalOpen(false);
- setEditingPortfolio(null);
- }}
- title={editingPortfolio ? 'Edit Funder Portfolio' : 'Create Funder Portfolio'}
- subtitle="Strategic allocation bucket grouping multiple programmatic initiatives"
- >
-
-
-
- {/* Modal: Create / Edit Programme */}
-
{
- setIsProgrammeModalOpen(false);
- setEditingProgramme(null);
- }}
- title={editingProgramme ? 'Edit Strategic Programme' : 'Create Strategic Programme'}
- subtitle="Programmatic operational entity funding distinct projects and awards"
- >
-
-
+ return (
+
+ {/* Header */}
+
+
+
+
+ Governance & Structure
+
+
+ Directory
+
+
+
+ Maintain Master Organisation Registry, Funder Portfolios, and Strategic Programmes hierarchy.
+
- );
+
+ {/* Tab Switcher & CTA */}
+
+ {activeTab === 'organisations' && (
+
+ )}
+ {activeTab === 'portfolios' && (
+
+ )}
+ {activeTab === 'programmes' && (
+
+ )}
+
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+ {/* Search Bar */}
+ {activeTab === 'organisations' && (
+
+
+ setSearchTerm(e.target.value)}
+ className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
+ />
+
+ )}
+
+ {/* Tab 1: Organisations Table */}
+ {activeTab === 'organisations' && (
+
+
+
+
+
+ | Organisation Name |
+ Registration No |
+ Entity Type |
+ Contact Email |
+ Status |
+ Actions |
+
+
+
+ {loadingOrgs ? (
+
+ |
+ Loading registered organisations...
+ |
+
+ ) : filteredOrgs.length === 0 ? (
+
+ |
+ No organisations found matching query.
+ |
+
+ ) : (
+ filteredOrgs.map((org) => {
+ const statusBadge = getActivityStatusBadge(org.status);
+ return (
+
+ |
+ {org.name}
+ ID: {org.id}
+ |
+ {org.registrationNo || '—'} |
+
+
+ {getOrganisationTypeLabel(org.type)}
+
+ |
+
+
+
+ {org.email}
+
+ |
+
+
+ {statusBadge.label}
+
+ |
+
+
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Tab 2: Portfolios List */}
+ {activeTab === 'portfolios' && (
+
+ {loadingPortfolios ? (
+
Loading portfolios...
+ ) : (
+ portfolios.map((p) => {
+ const org = organisations.find((o) => String(o.id) === String(p.organisationId));
+ const progCount = programmes.filter((pr) => String(pr.portfolioId) === String(p.id)).length;
+ return (
+
+
+
+
+ Portfolio #{p.id}
+
+ {progCount} Programmes
+
+
{p.name}
+
{p.description}
+
+
+
+
+ {org ? org.name : `Org #${p.organisationId}`}
+ {formatDate(p.createdAt)}
+
+
+
+ Owner: {getUserEmail(p.ownedBy)}
+
+
+
+
+
+
+
+
+ );
+ })
+ )}
+
+ )}
+
+ {/* Tab 3: Programmes Hierarchy */}
+ {activeTab === 'programmes' && (
+
+
+
+
+
+ | Programme Name |
+ Parent Portfolio |
+ Governance Status |
+ Creation Date |
+ Owner Email |
+ Actions |
+
+
+
+ {loadingProgrammes ? (
+
+ |
+ Loading programmes...
+ |
+
+ ) : (
+ programmes.map((pr) => {
+ const port = portfolios.find((p) => String(p.id) === String(pr.portfolioId));
+ const statusBadge = getApprovalStatusBadge(pr.status);
+ return (
+
+ |
+ {pr.name}
+ ID: {pr.id}
+ |
+
+ {port ? port.name : `Portfolio #${pr.portfolioId}`}
+ |
+
+
+ {statusBadge.label}
+
+ |
+ {formatDate(pr.createdAt)} |
+
+ {getUserEmail(pr.ownedBy)}
+ |
+
+
+
+
+
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Modal: Register / Edit Organisation */}
+
setIsOrgModalOpen(false)}
+ title={editingOrg ? 'Edit Organisation Profile' : 'Register New Organisation'}
+ subtitle="Mandatory master data profile for Recipient or Funder entities (POPIA compliant)"
+ >
+
+
+
+ {/* Modal: Create / Edit Portfolio */}
+
{
+ setIsPortfolioModalOpen(false);
+ setEditingPortfolio(null);
+ }}
+ title={editingPortfolio ? 'Edit Funder Portfolio' : 'Create Funder Portfolio'}
+ subtitle="Strategic allocation bucket grouping multiple programmatic initiatives"
+ >
+
+
+
+ {/* Modal: Create / Edit Programme */}
+
{
+ setIsProgrammeModalOpen(false);
+ setEditingProgramme(null);
+ }}
+ title={editingProgramme ? 'Edit Strategic Programme' : 'Create Strategic Programme'}
+ subtitle="Programmatic operational entity funding distinct projects and awards"
+ >
+
+
+
+ {/* Modal: View Funding Instruments */}
+
{
+ setIsInstrumentsModalOpen(false);
+ setSelectedProgrammeForInstruments(null);
+ }}
+ title={`Funding Instruments: ${selectedProgrammeForInstruments?.name || ''}`}
+ subtitle={`Authoritative funding instruments linked to Programme #${selectedProgrammeForInstruments?.id || ''}`}
+ >
+
+ {loadingInstruments ? (
+
Loading funding instruments...
+ ) : programmeInstruments.length === 0 ? (
+
+ No funding instruments registered under this programme.
+
+ ) : (
+
+ {programmeInstruments.map((inst: Instrument) => {
+ const statusBadge = getActivityStatusBadge(inst.status);
+ return (
+
+
+
+
+ {inst.code || `INST-${inst.id}`}
+
+ {inst.name}
+
+
+ {statusBadge.label}
+
+
+ {inst.description && (
+
{inst.description}
+ )}
+
+ Created by: {getUserEmail(inst.createdBy)}
+ {formatDate(inst.createdAt)}
+
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+ {/* Modal: View Portfolio Programmes */}
+
{
+ setIsPortfolioProgrammesModalOpen(false);
+ setSelectedPortfolioForProgrammes(null);
+ }}
+ title={`Portfolio Programmes: ${selectedPortfolioForProgrammes?.name || ''}`}
+ subtitle={`Programmes allocated under Portfolio #${selectedPortfolioForProgrammes?.id || ''}`}
+ >
+
+ {loadingPortfolioProgrammes ? (
+
Loading portfolio programmes...
+ ) : portfolioProgrammesList.length === 0 ? (
+
+ No programmes registered under this portfolio.
+
+ ) : (
+
+ {portfolioProgrammesList.map((pr: Programme) => {
+ const statusBadge = getApprovalStatusBadge(pr.status);
+ return (
+
+
+ {pr.name}
+
+ ID: {pr.id} • Owner: {getUserEmail(pr.ownedBy)} • {formatDate(pr.createdAt)}
+
+
+
+ {statusBadge.label}
+
+
+ );
+ })}
+
+ )}
+
+
+
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx
index 8ba401c..7a0bcfa 100644
--- a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx
@@ -4,93 +4,103 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
- Coins,
- FileText,
- PieChart,
- Banknote,
- Plus,
- CheckCircle,
- Clock,
- ShieldCheck,
- AlertTriangle,
- ArrowRight,
- Lock,
- ExternalLink,
- DollarSign,
- FileCheck2,
+ Coins,
+ FileText,
+ PieChart,
+ Banknote,
+ Plus,
+ CheckCircle,
+ Clock,
+ ShieldCheck,
+ AlertTriangle,
+ ArrowRight,
+ Lock,
+ ExternalLink,
+ DollarSign,
+ FileCheck2,
+ Download,
+ Upload,
+ Link as LinkIcon,
} from 'lucide-react';
import {
- getAwards,
- createAward,
- getContracts,
- createContract,
- executeContract,
- getBudgets,
- createBudget,
- getDisbursements,
- createDisbursement,
- approveDisbursement,
- getOrganisations,
- getAllProgrammes,
- getProjects,
- getAllMilestones,
+ getAwards,
+ getAward,
+ createAward,
+ getContracts,
+ getContract,
+ createContract,
+ updateContract,
+ uploadContractFile,
+ getContractSecureLink,
+ executeContract,
+ getBudgets,
+ getBudget,
+ createBudget,
+ getDisbursements,
+ getDisbursement,
+ createDisbursement,
+ approveDisbursement,
+ getOrganisations,
+ getAllProgrammes,
+ getProjects,
+ getAllMilestones,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import {
- Award,
- Contract,
- Budget,
- Disbursement,
- ApprovalStatus,
- ContractStatus,
- CreateAwardRequest,
- CreateContractRequest,
- ExecuteContractRequest,
- CreateBudgetRequest,
- CreateDisbursementRequest,
+ Award,
+ Contract,
+ Budget,
+ Disbursement,
+ ApprovalStatus,
+ ContractStatus,
+ CreateAwardRequest,
+ CreateContractRequest,
+ ExecuteContractRequest,
+ CreateBudgetRequest,
+ CreateDisbursementRequest,
} from '../types';
import {
- formatZAR,
- formatCompactZAR,
- formatDate,
- getApprovalStatusBadge,
- getContractStatusBadge,
- getUserEmail,
+ formatZAR,
+ formatCompactZAR,
+ formatDate,
+ getApprovalStatusBadge,
+ getContractStatusBadge,
+ getUserEmail,
} from '../lib/formatters';
// Schemas matching Figure 9 and OpenAPI constraints
const awardSchema = z.object({
- referenceNo: z.string().min(4, 'Approval / Reference number is mandatory (FR-M01-002)'),
- amount: z.coerce.number().min(1, 'Approved funding amount must be > 0 (FR-M01-003)'),
- fundingPeriod: z.string().min(3, 'Funding period is mandatory (e.g. 24 Months)'),
- organisationId: z.coerce.number().min(1, 'Select recipient organisation (FR-M01-006)'),
- programmeId: z.coerce.number().min(1, 'Select funding programme (FR-M01-005)'),
+ referenceNo: z.string().min(4, 'Approval / Reference number is mandatory (FR-M01-002)'),
+ amount: z.coerce.number().min(1, 'Approved funding amount must be > 0 (FR-M01-003)'),
+ fundingPeriod: z.string().min(3, 'Funding period is mandatory (e.g. 24 Months)'),
+ organisationId: z.coerce.number().min(1, 'Select recipient organisation (FR-M01-006)'),
+ programmeId: z.coerce.number().min(1, 'Select funding programme (FR-M01-005)'),
});
const contractSchema = z.object({
- awardId: z.coerce.number().min(1, 'Select approved award'),
- totalValue: z.coerce.number().min(1, 'Total value is required'),
- effectiveAt: z.string().min(4, 'Effective date is mandatory'),
- expiresAt: z.string().min(4, 'Expiry date is mandatory'),
+ awardId: z.coerce.number().min(1, 'Select approved award'),
+ totalValue: z.coerce.number().min(1, 'Total value is required'),
+ effectiveAt: z.string().min(4, 'Effective date is mandatory'),
+ expiresAt: z.string().min(4, 'Expiry date is mandatory'),
});
const executeContractSchema = z.object({
- contractId: z.coerce.number().min(1),
- externalSignatureId: z.string().min(4, 'Digital signature ID is required'),
- signedDocumentUrl: z.string().url('Must be a valid document URL'),
+ contractId: z.coerce.number().min(1),
+ externalSignatureId: z.string().min(4, 'Digital signature ID is required'),
+ signedDocumentUrl: z.string().url('Must be a valid document URL'),
});
const budgetSchema = z.object({
- projectId: z.coerce.number().min(1, 'Select project'),
- name: z.string().min(3, 'Budget category name required'),
- amount: z.coerce.number().min(1, 'Amount must be greater than zero'),
+ projectId: z.coerce.number().min(1, 'Select project'),
+ name: z.string().min(3, 'Budget category name required'),
+ amount: z.coerce.number().min(1, 'Amount must be greater than zero'),
});
const disbursementSchema = z.object({
- projectId: z.coerce.number().min(1, 'Select project'),
- milestoneId: z.coerce.number().optional(),
- amount: z.coerce.number().min(1, 'Disbursement tranche amount required'),
+ projectId: z.coerce.number().min(1, 'Select project'),
+ milestoneId: z.coerce.number().optional(),
+ amount: z.coerce.number().min(1, 'Disbursement tranche amount required'),
});
type AwardFormValues = z.infer
;
@@ -100,935 +110,1012 @@ type BudgetFormValues = z.infer;
type DisbursementFormValues = z.infer;
export const GrantsPage: React.FC = () => {
- const queryClient = useQueryClient();
- const { success, error: toastError } = useToast();
+ const queryClient = useQueryClient();
+ const { success, error: toastError } = useToast();
- const [activeTab, setActiveTab] = useState<'awards' | 'contracts' | 'budgets' | 'disbursements'>('awards');
+ const [activeTab, setActiveTab] = useState<'awards' | 'contracts' | 'budgets' | 'disbursements'>('awards');
- // Modals
- const [isAwardModalOpen, setIsAwardModalOpen] = useState(false);
- const [isContractModalOpen, setIsContractModalOpen] = useState(false);
- const [isExecuteModalOpen, setIsExecuteModalOpen] = useState(false);
- const [selectedContractForExecution, setSelectedContractForExecution] = useState(null);
- const [isBudgetModalOpen, setIsBudgetModalOpen] = useState(false);
- const [isDisbursementModalOpen, setIsDisbursementModalOpen] = useState(false);
+ // Modals
+ const [isAwardModalOpen, setIsAwardModalOpen] = useState(false);
+ const [isContractModalOpen, setIsContractModalOpen] = useState(false);
+ const [isExecuteModalOpen, setIsExecuteModalOpen] = useState(false);
+ const [selectedContractForExecution, setSelectedContractForExecution] = useState(null);
+ const [isBudgetModalOpen, setIsBudgetModalOpen] = useState(false);
+ const [isDisbursementModalOpen, setIsDisbursementModalOpen] = useState(false);
- // Queries
- const { data: awards = [], isLoading: loadingAwards } = useQuery({
- queryKey: ['awards'],
- queryFn: () => getAwards(1, 100),
+ // Queries
+ const { data: awards = [], isLoading: loadingAwards } = useQuery({
+ queryKey: ['awards'],
+ queryFn: () => getAwards(1, 100),
+ });
+
+ const { data: contracts = [], isLoading: loadingContracts } = useQuery({
+ queryKey: ['contracts'],
+ queryFn: () => getContracts(1, 100),
+ });
+
+ const { data: budgets = [], isLoading: loadingBudgets } = useQuery({
+ queryKey: ['budgets'],
+ queryFn: () => getBudgets(1, 100),
+ });
+
+ const { data: disbursements = [], isLoading: loadingDisbursements } = useQuery({
+ queryKey: ['disbursements'],
+ queryFn: () => getDisbursements(1, 100),
+ });
+
+ const { data: organisations = [] } = useQuery({
+ queryKey: ['organisations'],
+ queryFn: () => getOrganisations(),
+ });
+
+ const { data: programmes = [] } = useQuery({
+ queryKey: ['programmes'],
+ queryFn: () => getAllProgrammes(),
+ });
+
+ const { data: projects = [] } = useQuery({
+ queryKey: ['projects'],
+ queryFn: () => getProjects(),
+ });
+
+ const { data: milestones = [] } = useQuery({
+ queryKey: ['milestones-all'],
+ queryFn: () => getAllMilestones(),
+ });
+
+ // Forms
+ const awardForm = useForm({
+ resolver: zodResolver(awardSchema) as any,
+ defaultValues: {
+ referenceNo: '',
+ amount: 10000000,
+ fundingPeriod: '24 Months',
+ organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
+ programmeId: programmes[0]?.id ? Number(programmes[0].id) : 301,
+ },
+ });
+
+ const contractForm = useForm({
+ resolver: zodResolver(contractSchema) as any,
+ defaultValues: {
+ awardId: awards[0]?.id ? Number(awards[0].id) : 501,
+ totalValue: 10000000,
+ effectiveAt: new Date().toISOString().slice(0, 10),
+ expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
+ },
+ });
+
+ const executeForm = useForm({
+ resolver: zodResolver(executeContractSchema) as any,
+ defaultValues: {
+ contractId: 0,
+ externalSignatureId: '',
+ signedDocumentUrl: '',
+ },
+ });
+
+ const budgetForm = useForm({
+ resolver: zodResolver(budgetSchema) as any,
+ defaultValues: {
+ projectId: projects[0]?.id ? Number(projects[0].id) : 401,
+ name: '',
+ amount: 1000000,
+ },
+ });
+
+ const disbursementForm = useForm({
+ resolver: zodResolver(disbursementSchema) as any,
+ defaultValues: {
+ projectId: projects[0]?.id ? Number(projects[0].id) : 401,
+ amount: 2500000,
+ },
+ });
+
+ // Mutations
+ const awardMutation = useMutation({
+ mutationFn: (data: CreateAwardRequest) => createAward(data),
+ onSuccess: (newAwd) => {
+ queryClient.invalidateQueries({ queryKey: ['awards'] });
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] });
+ success(
+ 'Award Registered & Contract Initiated',
+ `Award ${newAwd.referenceNo} recorded. Automated Contract workflow initiated (BR-002).`
+ );
+ setIsAwardModalOpen(false);
+ awardForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Award registration failed', String(err));
+ },
+ });
+
+ const contractMutation = useMutation({
+ mutationFn: (data: CreateContractRequest) => createContract(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ success('Contract generated', 'New legal agreement created in Draft state');
+ setIsContractModalOpen(false);
+ contractForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Contract creation failed', String(err));
+ },
+ });
+
+ const executeMutation = useMutation({
+ mutationFn: (data: ExecuteContractRequest) => executeContract(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ success(
+ 'Contract Executed & Locked',
+ 'Agreement locked as authoritative version (AT-02). Downstream budgets and disbursements enabled.'
+ );
+ setIsExecuteModalOpen(false);
+ executeForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Execution trigger failed', String(err));
+ },
+ });
+
+ const budgetMutation = useMutation({
+ mutationFn: (data: CreateBudgetRequest) => createBudget(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['budgets'] });
+ success('Budget line established', 'Budget line created against project');
+ setIsBudgetModalOpen(false);
+ budgetForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Budget creation failed', String(err));
+ },
+ });
+
+ const disbursementMutation = useMutation({
+ mutationFn: (data: CreateDisbursementRequest) => createDisbursement(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['disbursements'] });
+ success('Disbursement Scheduled', 'Payment tranche added in Pending Review state');
+ setIsDisbursementModalOpen(false);
+ disbursementForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Disbursement creation failed', String(err));
+ },
+ });
+
+ const approveMutation = useMutation({
+ mutationFn: (id: string | number) => approveDisbursement(id),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['disbursements'] });
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] });
+ success(
+ 'Payment Approved & Released',
+ 'Conditions precedent verified. EFT reference generated and disbursement ledger updated (AT-05).'
+ );
+ },
+ onError: (err: unknown) => {
+ toastError('Disbursement approval failed', String(err));
+ },
+ });
+
+ const openExecuteModal = (c: Contract) => {
+ setSelectedContractForExecution(c);
+ executeForm.reset({
+ contractId: Number(c.id),
+ externalSignatureId: `SIG-DIGITAL-${Math.floor(100000 + Math.random() * 900000)}`,
+ signedDocumentUrl: `https://documents.ifri.khongisa.co.za/contracts/CTR-${c.id}-Signed.pdf`,
});
+ setIsExecuteModalOpen(true);
+ };
- const { data: contracts = [], isLoading: loadingContracts } = useQuery({
- queryKey: ['contracts'],
- queryFn: () => getContracts(1, 100),
- });
+ const handleGetContractSecureLink = async (contractId: string | number) => {
+ try {
+ const url = await getContractSecureLink(contractId);
+ if (url) {
+ window.open(url, '_blank', 'noopener,noreferrer');
+ success('Secure URL Retrieved', 'Temporary signed download link opened.');
+ } else {
+ toastError('Download Error', 'No active secure download link generated.');
+ }
+ } catch (err: any) {
+ toastError('Link Error', err?.message || 'Failed to fetch contract secure link.');
+ }
+ };
- const { data: budgets = [], isLoading: loadingBudgets } = useQuery({
- queryKey: ['budgets'],
- queryFn: () => getBudgets(1, 100),
- });
+ const handleContractFileUpload = async (contractId: string | number, file: File) => {
+ try {
+ await uploadContractFile(contractId, file);
+ success('Document Attached', `Contract #${contractId} document uploaded successfully.`);
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ } catch (err: any) {
+ toastError('Upload Failed', err?.message || 'Contract file upload failed.');
+ }
+ };
- const { data: disbursements = [], isLoading: loadingDisbursements } = useQuery({
- queryKey: ['disbursements'],
- queryFn: () => getDisbursements(1, 100),
- });
+ const handleUpdateContractStatus = async (contractId: string | number, status: ContractStatus) => {
+ try {
+ await updateContract({ contractId, status });
+ success('Contract Updated', `Contract status updated to ${getContractStatusBadge(status).label}.`);
+ queryClient.invalidateQueries({ queryKey: ['contracts'] });
+ } catch (err: any) {
+ toastError('Update Failed', err?.message || 'Failed to update contract status.');
+ }
+ };
- const { data: organisations = [] } = useQuery({
- queryKey: ['organisations'],
- queryFn: () => getOrganisations(),
- });
+ // Summary calculations
+ const totalAwardValue = awards.reduce((sum, a) => sum + (Number(a.amount) || 0), 0);
+ const totalDisbursedValue = disbursements
+ .filter((d) => d.status === ApprovalStatus.Approved)
+ .reduce((sum, d) => sum + (Number(d.amount) || 0), 0);
+ const pendingDisbursementValue = disbursements
+ .filter((d) => d.status === ApprovalStatus.PendingReview)
+ .reduce((sum, d) => sum + (Number(d.amount) || 0), 0);
- const { data: programmes = [] } = useQuery({
- queryKey: ['programmes'],
- queryFn: () => getAllProgrammes(),
- });
+ return (
+
+ {/* Header & Execution Pipeline Summary */}
+
+
+
+
+ Grant & Financial Management
+
+
+ End-to-end controlled execution thread: Funding Award Intake → Contracting → Budgeting → Condition-Checked Disbursement.
+
+
- const { data: projects = [] } = useQuery({
- queryKey: ['projects'],
- queryFn: () => getProjects(),
- });
-
- const { data: milestones = [] } = useQuery({
- queryKey: ['milestones-all'],
- queryFn: () => getAllMilestones(),
- });
-
- // Forms
- const awardForm = useForm
({
- resolver: zodResolver(awardSchema) as any,
- defaultValues: {
- referenceNo: '',
- amount: 10000000,
- fundingPeriod: '24 Months',
- organisationId: organisations[0]?.id ? Number(organisations[0].id) : 101,
- programmeId: programmes[0]?.id ? Number(programmes[0].id) : 301,
- },
- });
-
- const contractForm = useForm({
- resolver: zodResolver(contractSchema) as any,
- defaultValues: {
- awardId: awards[0]?.id ? Number(awards[0].id) : 501,
- totalValue: 10000000,
- effectiveAt: new Date().toISOString().slice(0, 10),
- expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
- },
- });
-
- const executeForm = useForm({
- resolver: zodResolver(executeContractSchema) as any,
- defaultValues: {
- contractId: 0,
- externalSignatureId: '',
- signedDocumentUrl: '',
- },
- });
-
- const budgetForm = useForm({
- resolver: zodResolver(budgetSchema) as any,
- defaultValues: {
- projectId: projects[0]?.id ? Number(projects[0].id) : 401,
- name: '',
- amount: 1000000,
- },
- });
-
- const disbursementForm = useForm({
- resolver: zodResolver(disbursementSchema) as any,
- defaultValues: {
- projectId: projects[0]?.id ? Number(projects[0].id) : 401,
- amount: 2500000,
- },
- });
-
- // Mutations
- const awardMutation = useMutation({
- mutationFn: (data: CreateAwardRequest) => createAward(data),
- onSuccess: (newAwd) => {
- queryClient.invalidateQueries({ queryKey: ['awards'] });
- queryClient.invalidateQueries({ queryKey: ['contracts'] });
- queryClient.invalidateQueries({ queryKey: ['dashboard'] });
- success(
- 'Award Registered & Contract Initiated',
- `Award ${newAwd.referenceNo} recorded. Automated Contract workflow initiated (BR-002).`
- );
- setIsAwardModalOpen(false);
- awardForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Award registration failed', String(err));
- },
- });
-
- const contractMutation = useMutation({
- mutationFn: (data: CreateContractRequest) => createContract(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['contracts'] });
- success('Contract generated', 'New legal agreement created in Draft state');
- setIsContractModalOpen(false);
- contractForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Contract creation failed', String(err));
- },
- });
-
- const executeMutation = useMutation({
- mutationFn: (data: ExecuteContractRequest) => executeContract(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['contracts'] });
- success(
- 'Contract Executed & Locked',
- 'Agreement locked as authoritative version (AT-02). Downstream budgets and disbursements enabled.'
- );
- setIsExecuteModalOpen(false);
- executeForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Execution trigger failed', String(err));
- },
- });
-
- const budgetMutation = useMutation({
- mutationFn: (data: CreateBudgetRequest) => createBudget(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['budgets'] });
- success('Budget line established', 'Budget line created against project');
- setIsBudgetModalOpen(false);
- budgetForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Budget creation failed', String(err));
- },
- });
-
- const disbursementMutation = useMutation({
- mutationFn: (data: CreateDisbursementRequest) => createDisbursement(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['disbursements'] });
- success('Disbursement Scheduled', 'Payment tranche added in Pending Review state');
- setIsDisbursementModalOpen(false);
- disbursementForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Disbursement creation failed', String(err));
- },
- });
-
- const approveMutation = useMutation({
- mutationFn: (id: string | number) => approveDisbursement(id),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['disbursements'] });
- queryClient.invalidateQueries({ queryKey: ['dashboard'] });
- success(
- 'Payment Approved & Released',
- 'Conditions precedent verified. EFT reference generated and disbursement ledger updated (AT-05).'
- );
- },
- onError: (err: unknown) => {
- toastError('Disbursement approval failed', String(err));
- },
- });
-
- const openExecuteModal = (c: Contract) => {
- setSelectedContractForExecution(c);
- executeForm.reset({
- contractId: Number(c.id),
- externalSignatureId: `SIG-DIGITAL-${Math.floor(100000 + Math.random() * 900000)}`,
- signedDocumentUrl: `https://documents.ifri.khongisa.co.za/contracts/CTR-${c.id}-Signed.pdf`,
- });
- setIsExecuteModalOpen(true);
- };
-
- // Summary calculations
- const totalAwardValue = awards.reduce((sum, a) => sum + (Number(a.amount) || 0), 0);
- const totalDisbursedValue = disbursements
- .filter((d) => d.status === ApprovalStatus.Approved)
- .reduce((sum, d) => sum + (Number(d.amount) || 0), 0);
- const pendingDisbursementValue = disbursements
- .filter((d) => d.status === ApprovalStatus.PendingReview)
- .reduce((sum, d) => sum + (Number(d.amount) || 0), 0);
-
- return (
-
- {/* Header & Execution Pipeline Summary */}
-
-
-
-
- Grant & Financial Management
-
-
- End-to-end controlled execution thread: Funding Award Intake → Contracting → Budgeting → Condition-Checked Disbursement.
-
-
-
-
- {activeTab === 'awards' && (
-
- )}
- {activeTab === 'contracts' && (
-
- )}
- {activeTab === 'budgets' && (
-
- )}
- {activeTab === 'disbursements' && (
-
- )}
-
-
-
- {/* Pipeline Summary Bar */}
-
-
- Total Awards Registered
- {formatCompactZAR(totalAwardValue)}
-
-
- Disbursed (Verified & Paid)
- {formatCompactZAR(totalDisbursedValue)}
-
-
- Pending Release
- {formatCompactZAR(pendingDisbursementValue)}
-
-
-
-
- {/* Tabs */}
-
-
-
-
-
-
-
- {/* Tab 1: Awards Table (M01) */}
+
{activeTab === 'awards' && (
-
-
-
-
-
- | Approval Reference # |
- Recipient Organisation |
- Funding Programme |
- Approved Amount (ZAR) |
- Status |
- Date Registered |
-
-
-
- {loadingAwards ? (
-
- |
- Loading registered awards...
- |
-
- ) : (
- awards.map((awd) => {
- const statusBadge = getApprovalStatusBadge(awd.status);
- return (
-
- |
- {awd.referenceNo}
- System ID: #{awd.id}
- |
-
- {awd.organisationName || `Org #${awd.organisationId}`}
- |
-
- {awd.programmeName || `Programme #${awd.programmeId}`}
- |
-
- {formatZAR(awd.amount)}
- |
-
-
- {statusBadge.label}
-
- |
-
- {formatDate(awd.createdAt)}
- |
-
- );
- })
- )}
-
-
-
-
+
)}
-
- {/* Tab 2: Contracts Table (M02) */}
{activeTab === 'contracts' && (
-
-
-
-
-
- | Contract ID |
- Associated Award |
- Total Contract Value |
- Effective / Expiry |
- Status |
- Action |
-
-
-
- {loadingContracts ? (
-
- |
- Loading contracts...
- |
-
- ) : (
- contracts.map((c) => {
- const statusBadge = getContractStatusBadge(c.status);
- const isExecuted = c.status === ContractStatus.Executed;
- return (
-
- |
- CTR-{c.id}
- |
- Award #{c.awardId} |
-
- {formatZAR(c.totalValue)}
- |
-
- {formatDate(c.effectiveAt)} → {formatDate(c.expiresAt)}
- |
-
-
- {statusBadge.label}
-
- |
-
- {isExecuted ? (
-
-
- Locked
-
- ) : (
-
- )}
- |
-
- );
- })
- )}
-
-
-
-
+
)}
-
- {/* Tab 3: Budgets (M05) */}
{activeTab === 'budgets' && (
-
-
-
-
-
- | Budget Line Category |
- Project ID |
- Approved Amount (ZAR) |
- Date Established |
- Created By |
-
-
-
- {loadingBudgets ? (
-
- |
- Loading budget lines...
- |
-
- ) : (
- budgets.map((b) => (
-
- | {b.name} |
- Project #{b.projectId} |
-
- {formatZAR(b.amount)}
- |
- {formatDate(b.createdAt)} |
- {getUserEmail(b.createdBy)} |
-
- ))
- )}
-
-
-
-
+
)}
-
- {/* Tab 4: Disbursements & Approval Trigger (M06) */}
{activeTab === 'disbursements' && (
-
-
-
-
- Disbursement Control:
- Disbursement cannot be released unless contractual conditions are verified. Duplicate approvals blocked.
-
-
-
-
-
-
-
- | Disbursement ID |
- Project ID |
- Milestone Association |
- Tranche Amount |
- Payment Reference |
- Approval Status |
- Approval Action |
-
-
-
- {loadingDisbursements ? (
-
- |
- Loading disbursements...
- |
-
- ) : (
- disbursements.map((d) => {
- const statusBadge = getApprovalStatusBadge(d.status);
- const isApproved = d.status === ApprovalStatus.Approved;
- return (
-
- |
- DSB-{d.id}
- |
- Project #{d.projectId} |
-
- {d.milestoneId ? `Milestone #${d.milestoneId}` : 'Initial Advance Tranche'}
- |
-
- {formatZAR(d.amount)}
- |
-
- {d.paymentReference || Pending Release}
- |
-
-
- {statusBadge.label}
-
- |
-
- {isApproved ? (
-
-
- Released
-
- ) : (
-
- )}
- |
-
- );
- })
- )}
-
-
-
-
+
)}
-
- {/* Modal: Register Approved Award */}
-
setIsAwardModalOpen(false)}
- title="Register Approved Funding Award"
- subtitle="Step 1 of 3 — Award Details"
- >
-
-
-
- {/* Modal: Draft Contract */}
-
setIsContractModalOpen(false)}
- title="Draft Contract Agreement"
- subtitle="Convert approved funding decision into legally enforceable terms"
- >
-
-
-
- {/* Modal: Execute Contract */}
-
setIsExecuteModalOpen(false)}
- title="Execute Contract & Lock Agreement"
- subtitle={`Locking Contract #${selectedContractForExecution?.id} as Authoritative Version`}
- >
-
-
-
- {/* Modal: Add Budget */}
-
setIsBudgetModalOpen(false)}
- title="Add Project Budget Line"
- subtitle="Maintain approved expenditure breakdown and commitments"
- >
-
-
-
- {/* Modal: Schedule Disbursement */}
-
setIsDisbursementModalOpen(false)}
- title="Schedule Disbursement Tranche"
- subtitle="Link disbursement to milestone verification and conditions precedent"
- >
-
-
+
- );
+
+ {/* Pipeline Summary Bar */}
+
+
+ Total Awards Registered
+ {formatCompactZAR(totalAwardValue)}
+
+
+ Disbursed (Verified & Paid)
+ {formatCompactZAR(totalDisbursedValue)}
+
+
+ Pending Release
+ {formatCompactZAR(pendingDisbursementValue)}
+
+
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+
+ {/* Tab 1: Awards Table (M01) */}
+ {activeTab === 'awards' && (
+
+
+
+
+
+ | Approval Reference # |
+ Recipient Organisation |
+ Funding Programme |
+ Approved Amount (ZAR) |
+ Status |
+ Date Registered |
+
+
+
+ {loadingAwards ? (
+
+ |
+ Loading registered awards...
+ |
+
+ ) : (
+ awards.map((awd) => {
+ const statusBadge = getApprovalStatusBadge(awd.status);
+ return (
+
+ |
+ {awd.referenceNo}
+ System ID: #{awd.id}
+ |
+
+ {awd.organisationName || `Org #${awd.organisationId}`}
+ |
+
+ {awd.programmeName || `Programme #${awd.programmeId}`}
+ |
+
+ {formatZAR(awd.amount)}
+ |
+
+
+ {statusBadge.label}
+
+ |
+
+ {formatDate(awd.createdAt)}
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Tab 2: Contracts Table (M02) */}
+ {activeTab === 'contracts' && (
+
+ )}
+
+ {/* Tab 3: Budgets (M05) */}
+ {activeTab === 'budgets' && (
+
+
+
+
+
+ | Budget Line Category |
+ Project ID |
+ Approved Amount (ZAR) |
+ Date Established |
+ Created By |
+
+
+
+ {loadingBudgets ? (
+
+ |
+ Loading budget lines...
+ |
+
+ ) : (
+ budgets.map((b) => (
+
+ | {b.name} |
+ Project #{b.projectId} |
+
+ {formatZAR(b.amount)}
+ |
+ {formatDate(b.createdAt)} |
+ {getUserEmail(b.createdBy)} |
+
+ ))
+ )}
+
+
+
+
+ )}
+
+ {/* Tab 4: Disbursements & Approval Trigger (M06) */}
+ {activeTab === 'disbursements' && (
+
+
+
+
+ Disbursement Control:
+ Disbursement cannot be released unless contractual conditions are verified. Duplicate approvals blocked.
+
+
+
+
+
+
+
+ | Disbursement ID |
+ Project ID |
+ Milestone Association |
+ Tranche Amount |
+ Payment Reference |
+ Approval Status |
+ Approval Action |
+
+
+
+ {loadingDisbursements ? (
+
+ |
+ Loading disbursements...
+ |
+
+ ) : (
+ disbursements.map((d) => {
+ const statusBadge = getApprovalStatusBadge(d.status);
+ const isApproved = d.status === ApprovalStatus.Approved;
+ return (
+
+ |
+ DSB-{d.id}
+ |
+ Project #{d.projectId} |
+
+ {d.milestoneId ? `Milestone #${d.milestoneId}` : 'Initial Advance Tranche'}
+ |
+
+ {formatZAR(d.amount)}
+ |
+
+ {d.paymentReference || Pending Release}
+ |
+
+
+ {statusBadge.label}
+
+ |
+
+ {isApproved ? (
+
+
+ Released
+
+ ) : (
+
+ )}
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Modal: Register Approved Award */}
+
setIsAwardModalOpen(false)}
+ title="Register Approved Funding Award"
+ subtitle="Step 1 of 3 — Award Details"
+ >
+
+
+
+ {/* Modal: Draft Contract */}
+
setIsContractModalOpen(false)}
+ title="Draft Contract Agreement"
+ subtitle="Convert approved funding decision into legally enforceable terms"
+ >
+
+
+
+ {/* Modal: Execute Contract */}
+
setIsExecuteModalOpen(false)}
+ title="Execute Contract & Lock Agreement"
+ subtitle={`Locking Contract #${selectedContractForExecution?.id} as Authoritative Version`}
+ >
+
+
+
+ {/* Modal: Add Budget */}
+
setIsBudgetModalOpen(false)}
+ title="Add Project Budget Line"
+ subtitle="Maintain approved expenditure breakdown and commitments"
+ >
+
+
+
+ {/* Modal: Schedule Disbursement */}
+
setIsDisbursementModalOpen(false)}
+ title="Schedule Disbursement Tranche"
+ subtitle="Link disbursement to milestone verification and conditions precedent"
+ >
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/LandingPage.tsx b/PostFundManagement.ConsoleUI/src/pages/LandingPage.tsx
index 51bd706..0bf8d65 100644
--- a/PostFundManagement.ConsoleUI/src/pages/LandingPage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/LandingPage.tsx
@@ -1,270 +1,287 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import {
- ShieldCheck,
- Lock,
- ArrowRight,
- Layers,
- CheckCircle2,
- Coins,
- FileSpreadsheet,
- BarChart3,
- KeyRound,
- LogOut,
- UserCheck,
- ExternalLink,
- ChevronRight,
+ ShieldCheck,
+ Lock,
+ ArrowRight,
+ CheckCircle2,
+ Coins,
+ FileSpreadsheet,
+ BarChart3,
+ KeyRound,
+ LogOut,
+ UserCheck,
+ ExternalLink,
+ ChevronRight,
} from 'lucide-react';
import { useAuth } from '../auth/AuthProvider';
+import { BrandLogo } from '../components/ui/BrandLogo';
export const LandingPage: React.FC = () => {
- const { isAuthenticated, user, claims, sub, login, logout, authError, isLoading } = useAuth();
- const navigate = useNavigate();
+ const { isAuthenticated, user, claims, sub, login, logout, authError, isLoading } = useAuth();
+ const navigate = useNavigate();
- const handleEnterConsole = async () => {
- if (isAuthenticated) {
- navigate('/dashboard');
- return;
- }
- await login();
- };
+ const handleEnterConsole = async () => {
+ if (isAuthenticated) {
+ navigate('/dashboard');
+ return;
+ }
+ await login();
+ };
- const handleSignOut = async () => {
- await logout();
- };
+ const handleSignOut = async () => {
+ await logout();
+ };
- const tokenUserName = user?.name || (claims.name as string) || (claims.preferred_username as string) || 'Authorized User';
- const tokenUserEmail = user?.email || (claims.email as string) || (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) || '';
+ const tokenUserName = user?.name || (claims.name as string) || (claims.preferred_username as string) || 'Authorized User';
+ const tokenUserEmail = user?.email || (claims.email as string) || (claims['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] as string) || '';
- return (
-
- {/* Background Graphic Grid & Ambient Glows */}
-
-
-
-
+ return (
+
+ {/* Background Graphic Grid & Ambient Glows */}
+
+
+
+
- {/* Top Header */}
-
-
-
-
-
-
-
- PFM Console
-
- Enterprise STS
-
-
-
Post-Fund Management, Performance & Impact System
-
-
-
-
- {isAuthenticated ? (
-
- ) : (
-
-
- Duende STS Ready
-
- )}
-
-
-
- {/* Main Content */}
-
-
- {/* Left Column: Mission & Highlights */}
-
-
-
- Controlled Post-Award to Portfolio Governance
-
-
-
- Post Fund Management
-
- Executive Console
-
-
-
-
- Empowers Funders, Science Councils, and Development Agencies to enforce rigorous governance,
- milestone-linked disbursements, evidence verification, and real-time portfolio performance from
- award intake through final close-out.
-
-
-
-
-
-
- Award to Contract Enforcement
- Legal agreements linked directly to approved awards.
-
-
-
-
-
-
- Controlled Tranche Release
- Disbursements released only when milestone conditions are verified.
-
-
-
-
-
-
- Evidence Verification
- Deliverables require tamper-evident verification.
-
-
-
-
-
-
- Automated Portfolio Roll-Up
- Project data aggregates automatically to Programmes and Portfolios.
-
-
-
-
-
- {/* Right Column: Auth-Aware Box */}
-
- {isAuthenticated ? (
- /* Auth-Aware Home Guidance Panel */
-
-
-
-
-
-
-
- Active Authenticated Session
-
-
-
- STS Token Active
-
-
-
- {/* Identity Information from Token */}
-
-
-
-
-
-
-
{tokenUserName}
-
{tokenUserEmail || 'No email in token claims'}
-
-
-
- {sub && (
-
- Token Subject (sub):
- {sub}
-
- )}
-
-
- {/* Guidance Message */}
-
-
Session Verified
- You are authenticated through Duende Identity Server. Your token claims are validated and active. You can proceed to the PFM Console to manage projects, reviews, and disbursements, or sign out through the identity server.
-
-
- {/* Navigation and Sign Out Buttons */}
-
-
-
-
-
-
-
-
-
- Duende STS
-
- Audience: pfm-api-dev
-
-
- ) : (
- /* Unauthenticated Sign In Box */
-
-
-
-
-
- {authError && (
-
- {authError}
-
- )}
-
-
-
-
-
-
-
-
- Duende STS OIDC
-
- pfm-api-dev
-
-
- )}
-
-
-
-
- {/* Footer */}
-
+ {/* Top Header */}
+
+
+ {/* Main Content */}
+
+
+ {/* Left Column: Mission & Highlights */}
+
+
+
+ Controlled Post-Award to Portfolio Governance
+
+
+
+ Post Fund Management
+
+ Executive Console
+
+
+
+
+ Empowers Funders, Science Councils, and Development Agencies to enforce rigorous governance,
+ milestone-linked disbursements, evidence verification, and real-time portfolio performance from
+ award intake through final close-out.
+
+
+
+
+
+
+ Award to Contract Enforcement
+ Legal agreements linked directly to approved awards.
+
+
+
+
+
+
+ Controlled Tranche Release
+ Disbursements released only when milestone conditions are verified.
+
+
+
+
+
+
+ Evidence Verification
+ Deliverables require tamper-evident verification.
+
+
+
+
+
+
+ Automated Portfolio Roll-Up
+ Project data aggregates automatically to Programmes and Portfolios.
+
+
+
+
+
+ {/* Right Column: Auth-Aware Box */}
+
+ {isAuthenticated ? (
+ /* Auth-Aware Home Guidance Panel */
+
+
+
+
+
+
+
+ Active Authenticated Session
+
+
+
+ Session Active
+
+
+
+ {/* Identity Information from Token */}
+
+
+
+
+
+
+
{tokenUserName}
+
{tokenUserEmail || 'No email in token claims'}
+
+
+
+ {sub && (
+
+ Token Subject (sub):
+ {sub}
+
+ )}
+
+ {((claims.tenant as string) || user?.tenant) && (
+
+ Assigned Tenant:
+
+ {(claims.tenant as string) || user?.tenant}
+
+
+ )}
+
+
+ {/* Guidance Message */}
+
+
Session Verified
+ You are authenticated with validated enterprise security credentials. Your token claims are active and verified. You can proceed to the PFM Console to manage projects, reviews, and disbursements, or sign out of your session.
+
+
+ {/* Navigation and Sign Out Buttons */}
+
+
+
+
+
+
+
+
+
+ OAuth2 / OpenID Connect
+
+
+
+ Verified Session
+
+
+
+ ) : (
+ /* Unauthenticated Sign In Box */
+
+
+
+
+
+
+
+
+
Sign In
+
Enterprise Identity & Access Management
+
+
+
+ {authError && (
+
+ {authError}
+
+ )}
+
+
+
+
+
+
+
+
+ Single Sign-On (SSO)
+
+ Enterprise Security
+
+
+ )}
+
+
+
+
+ {/* Footer */}
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx b/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx
index caa7d0b..051cc06 100644
--- a/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx
@@ -4,845 +4,894 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
- ShieldAlert,
- FileCheck,
- Upload,
- Plus,
- CheckCircle2,
- XCircle,
- AlertTriangle,
- FileText,
- Clock,
- TrendingUp,
- Download,
- Paperclip,
- Check,
- Filter,
+ ShieldAlert,
+ FileCheck,
+ Upload,
+ Plus,
+ CheckCircle2,
+ XCircle,
+ AlertTriangle,
+ FileText,
+ Clock,
+ TrendingUp,
+ Download,
+ Paperclip,
+ Check,
+ Filter,
+ Link as LinkIcon,
} from 'lucide-react';
import {
- getAllIndicators,
- createIndicator,
- recordIndicatorActual,
- getAllEvidence,
- uploadEvidenceFile,
- updateEvidence,
- getAllRisks,
- createRisk,
- getProjects,
- getAllMilestones,
+ getAllIndicators,
+ createIndicator,
+ recordIndicatorActual,
+ getAllEvidence,
+ getMilestoneEvidence,
+ getEvidenceSecureLink,
+ uploadEvidenceFile,
+ updateEvidence,
+ getAllRisks,
+ createRisk,
+ getProjects,
+ getAllMilestones,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import {
- Indicator,
- Evidence,
- Risk,
- ApprovalStatus,
- UnitOfMeasure,
- EvidenceType,
- RiskLikelihood,
- RiskImpact,
- CreateIndicatorRequest,
- CreateRiskRequest,
+ Indicator,
+ Evidence,
+ Risk,
+ ApprovalStatus,
+ UnitOfMeasure,
+ EvidenceType,
+ RiskLikelihood,
+ RiskImpact,
+ CreateIndicatorRequest,
+ CreateRiskRequest,
} from '../types';
import {
- formatDate,
- getApprovalStatusBadge,
- getEvidenceTypeLabel,
- getUnitOfMeasureLabel,
- getRiskScoreColor,
- UNIT_OF_MEASURE_OPTIONS,
- RISK_LIKELIHOOD_OPTIONS,
- RISK_IMPACT_OPTIONS,
- EVIDENCE_TYPE_OPTIONS,
- getUserEmail,
+ formatDate,
+ getApprovalStatusBadge,
+ getEvidenceTypeLabel,
+ getUnitOfMeasureLabel,
+ getRiskScoreColor,
+ UNIT_OF_MEASURE_OPTIONS,
+ RISK_LIKELIHOOD_OPTIONS,
+ RISK_IMPACT_OPTIONS,
+ EVIDENCE_TYPE_OPTIONS,
+ getUserEmail,
} from '../lib/formatters';
const indicatorSchema = z.object({
- projectId: z.coerce.number().min(1, 'Select project'),
- name: z.string().min(5, 'Indicator definition required'),
- unitOfMeasure: z.coerce.number().min(1),
- baselineAmount: z.coerce.number().min(0),
- targetAmount: z.coerce.number().min(1, 'Target must be > 0'),
+ projectId: z.coerce.number().min(1, 'Select project'),
+ name: z.string().min(5, 'Indicator definition required'),
+ unitOfMeasure: z.coerce.number().min(1),
+ baselineAmount: z.coerce.number().min(0),
+ targetAmount: z.coerce.number().min(1, 'Target must be > 0'),
});
const riskSchema = z.object({
- projectId: z.coerce.number().min(1, 'Select project'),
- name: z.string().min(5, 'Risk title required'),
- description: z.string().min(10, 'Risk description required'),
- likelihood: z.coerce.number().min(1).max(5),
- impact: z.coerce.number().min(1).max(5),
+ projectId: z.coerce.number().min(1, 'Select project'),
+ name: z.string().min(5, 'Risk title required'),
+ description: z.string().min(10, 'Risk description required'),
+ likelihood: z.coerce.number().min(1).max(5),
+ impact: z.coerce.number().min(1).max(5),
});
type IndicatorFormValues = z.infer
;
type RiskFormValues = z.infer;
export const MonitoringPage: React.FC = () => {
- const queryClient = useQueryClient();
- const { success, error: toastError } = useToast();
+ const queryClient = useQueryClient();
+ const { success, error: toastError } = useToast();
- const [activeTab, setActiveTab] = useState<'indicators' | 'evidence' | 'risks'>('evidence');
- const fileInputRef = useRef(null);
+ const [activeTab, setActiveTab] = useState<'indicators' | 'evidence' | 'risks'>('evidence');
+ const fileInputRef = useRef(null);
- // Modals & States
- const [isIndicatorModalOpen, setIsIndicatorModalOpen] = useState(false);
- const [isRiskModalOpen, setIsRiskModalOpen] = useState(false);
- const [selectedIndicatorForActual, setSelectedIndicatorForActual] = useState(null);
- const [actualInput, setActualInput] = useState(0);
- const [uploadEvidenceType, setUploadEvidenceType] = useState(EvidenceType.CompletionCertificate);
+ // Modals & States
+ const [isIndicatorModalOpen, setIsIndicatorModalOpen] = useState(false);
+ const [isRiskModalOpen, setIsRiskModalOpen] = useState(false);
+ const [selectedIndicatorForActual, setSelectedIndicatorForActual] = useState(null);
+ const [actualInput, setActualInput] = useState(0);
+ const [uploadEvidenceType, setUploadEvidenceType] = useState(EvidenceType.CompletionCertificate);
- // Figure 10 Reviewer commentary
- const [reviewerComment, setReviewerComment] = useState('');
+ // Figure 10 Reviewer commentary
+ const [reviewerComment, setReviewerComment] = useState('');
+ const [selectedMilestoneEvidenceFilter, setSelectedMilestoneEvidenceFilter] = useState('all');
- // Queries
- const { data: indicators = [], isLoading: loadingIndicators } = useQuery({
- queryKey: ['indicators'],
- queryFn: () => getAllIndicators(),
- });
+ // Queries
+ const { data: indicators = [], isLoading: loadingIndicators } = useQuery({
+ queryKey: ['indicators'],
+ queryFn: () => getAllIndicators(),
+ });
- const { data: evidenceList = [], isLoading: loadingEvidence } = useQuery({
- queryKey: ['evidence'],
- queryFn: () => getAllEvidence(),
- });
+ const { data: evidenceList = [], isLoading: loadingEvidence } = useQuery({
+ queryKey: ['evidence', selectedMilestoneEvidenceFilter],
+ queryFn: () =>
+ selectedMilestoneEvidenceFilter !== 'all'
+ ? getMilestoneEvidence(selectedMilestoneEvidenceFilter)
+ : getAllEvidence(),
+ });
- const { data: risks = [], isLoading: loadingRisks } = useQuery({
- queryKey: ['risks'],
- queryFn: () => getAllRisks(),
- });
+ const { data: risks = [], isLoading: loadingRisks } = useQuery({
+ queryKey: ['risks'],
+ queryFn: () => getAllRisks(),
+ });
- const { data: projects = [] } = useQuery({
- queryKey: ['projects'],
- queryFn: () => getProjects(),
- });
+ const { data: projects = [] } = useQuery({
+ queryKey: ['projects'],
+ queryFn: () => getProjects(),
+ });
- const { data: milestones = [] } = useQuery({
- queryKey: ['milestones-all'],
- queryFn: () => getAllMilestones(),
- });
+ const { data: milestones = [] } = useQuery({
+ queryKey: ['milestones-all'],
+ queryFn: () => getAllMilestones(),
+ });
- // Forms
- const indicatorForm = useForm({
- resolver: zodResolver(indicatorSchema) as any,
- defaultValues: {
- projectId: projects[0]?.id ? Number(projects[0].id) : 401,
- name: '',
- unitOfMeasure: UnitOfMeasure.Count,
- baselineAmount: 0,
- targetAmount: 100,
- },
- });
+ // Forms
+ const indicatorForm = useForm({
+ resolver: zodResolver(indicatorSchema) as any,
+ defaultValues: {
+ projectId: projects[0]?.id ? Number(projects[0].id) : 401,
+ name: '',
+ unitOfMeasure: UnitOfMeasure.Count,
+ baselineAmount: 0,
+ targetAmount: 100,
+ },
+ });
- const riskForm = useForm({
- resolver: zodResolver(riskSchema) as any,
- defaultValues: {
- projectId: projects[0]?.id ? Number(projects[0].id) : 401,
- name: '',
- description: '',
- likelihood: RiskLikelihood.Medium,
- impact: RiskImpact.Moderate,
- },
- });
+ const riskForm = useForm({
+ resolver: zodResolver(riskSchema) as any,
+ defaultValues: {
+ projectId: projects[0]?.id ? Number(projects[0].id) : 401,
+ name: '',
+ description: '',
+ likelihood: RiskLikelihood.Medium,
+ impact: RiskImpact.Moderate,
+ },
+ });
- // Mutations
- const indicatorMutation = useMutation({
- mutationFn: (data: CreateIndicatorRequest) => createIndicator(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['indicators'] });
- success('Indicator defined', 'Indicator added to project monitoring plan (M08)');
- setIsIndicatorModalOpen(false);
- indicatorForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to create indicator', String(err));
- },
- });
+ // Mutations
+ const indicatorMutation = useMutation({
+ mutationFn: (data: CreateIndicatorRequest) => createIndicator(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['indicators'] });
+ success('Indicator defined', 'Indicator added to project monitoring plan (M08)');
+ setIsIndicatorModalOpen(false);
+ indicatorForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to create indicator', String(err));
+ },
+ });
- const actualMutation = useMutation({
- mutationFn: ({ id, amount }: { id: string | number; amount: number }) =>
- recordIndicatorActual(id, amount),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['indicators'] });
- success('Actual value recorded', 'M&E variance and indicator status recalculated (BR-009)');
- setSelectedIndicatorForActual(null);
- },
- onError: (err: unknown) => {
- toastError('Failed to record actual', String(err));
- },
- });
+ const actualMutation = useMutation({
+ mutationFn: ({ id, amount }: { id: string | number; amount: number }) =>
+ recordIndicatorActual(id, amount),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['indicators'] });
+ success('Actual value recorded', 'M&E variance and indicator status recalculated (BR-009)');
+ setSelectedIndicatorForActual(null);
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to record actual', String(err));
+ },
+ });
- const uploadMutation = useMutation({
- mutationFn: (formData: FormData) => uploadEvidenceFile(formData),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['evidence'] });
- success('Evidence uploaded', 'Supporting file registered for reviewer inspection (Figure 10 / M14)');
- },
- onError: (err: unknown) => {
- toastError('Upload failed', String(err));
- },
- });
+ const uploadMutation = useMutation({
+ mutationFn: (formData: FormData) => uploadEvidenceFile(formData),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['evidence'] });
+ success('Evidence uploaded', 'Supporting file registered for reviewer inspection (Figure 10 / M14)');
+ },
+ onError: (err: unknown) => {
+ toastError('Upload failed', String(err));
+ },
+ });
- const reviewEvidenceMutation = useMutation({
- mutationFn: ({ id, status }: { id: string | number; status: ApprovalStatus }) =>
- updateEvidence({
- evidenceId: id,
- version: 2,
- evidenceType: EvidenceType.DeliverableProof,
- status,
- }),
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries({ queryKey: ['evidence'] });
- if (variables.status === ApprovalStatus.Approved) {
- success(
- 'Evidence Approved & Verified',
- 'Approval recorded. Auto-triggers payment eligibility check in Disbursements.'
- );
- } else {
- success('Rework Requested', 'Evidence returned to recipient for correction.');
- }
- setReviewerComment('');
- },
- onError: (err: unknown) => {
- toastError('Review update failed', String(err));
- },
- });
+ const reviewEvidenceMutation = useMutation({
+ mutationFn: ({ id, status }: { id: string | number; status: ApprovalStatus }) =>
+ updateEvidence({
+ evidenceId: id,
+ version: 2,
+ evidenceType: EvidenceType.DeliverableProof,
+ status,
+ }),
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: ['evidence'] });
+ if (variables.status === ApprovalStatus.Approved) {
+ success(
+ 'Evidence Approved & Verified',
+ 'Approval recorded. Auto-triggers payment eligibility check in Disbursements.'
+ );
+ } else {
+ success('Rework Requested', 'Evidence returned to recipient for correction.');
+ }
+ setReviewerComment('');
+ },
+ onError: (err: unknown) => {
+ toastError('Review update failed', String(err));
+ },
+ });
- const riskMutation = useMutation({
- mutationFn: (data: CreateRiskRequest) => createRisk(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['risks'] });
- queryClient.invalidateQueries({ queryKey: ['dashboard'] });
- success('Risk registered', 'Risk item added to project register');
- setIsRiskModalOpen(false);
- riskForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to log risk', String(err));
- },
- });
+ const riskMutation = useMutation({
+ mutationFn: (data: CreateRiskRequest) => createRisk(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['risks'] });
+ queryClient.invalidateQueries({ queryKey: ['dashboard'] });
+ success('Risk registered', 'Risk item added to project register');
+ setIsRiskModalOpen(false);
+ riskForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to log risk', String(err));
+ },
+ });
- const handleFileUpload = (e: React.ChangeEvent) => {
- const file = e.target.files?.[0];
- if (file) {
- const formData = new FormData();
- formData.append('file', file);
- formData.append('projectId', '401');
- formData.append('milestoneId', '803');
- formData.append('evidenceType', String(uploadEvidenceType));
- uploadMutation.mutate(formData);
- }
- };
+ const handleFileUpload = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file) {
+ const formData = new FormData();
+ formData.append('file', file);
+ formData.append('projectId', '401');
+ formData.append('milestoneId', '803');
+ formData.append('evidenceType', String(uploadEvidenceType));
+ uploadMutation.mutate(formData);
+ }
+ };
- // Indicator thresholds from SRS 9.6:
- // Green >= 90%, Amber 70-89%, Red < 70%
- const getIndicatorStatus = (actual: number, target: number) => {
- if (!target) return { label: 'Pending', badge: 'bg-slate-800/60 text-slate-400 border-slate-700/60' };
- const pct = (actual / target) * 100;
- if (pct >= 90) return { label: 'Green (≥90%)', badge: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20' };
- if (pct >= 70) return { label: 'Amber (70–89%)', badge: 'bg-amber-500/10 text-amber-400 border-amber-500/20' };
- return { label: 'Red (<70%)', badge: 'bg-rose-500/10 text-rose-400 border-rose-500/20' };
- };
+ const handleGetEvidenceSecureLink = async (evidenceId: string | number) => {
+ try {
+ const url = await getEvidenceSecureLink(evidenceId);
+ if (url) {
+ window.open(url, '_blank', 'noopener,noreferrer');
+ success('Secure URL Retrieved', 'Signed evidence download link opened.');
+ } else {
+ toastError('Download Error', 'No active secure download link generated.');
+ }
+ } catch (err: any) {
+ toastError('Link Error', err?.message || 'Failed to retrieve evidence secure link.');
+ }
+ };
- return (
-
- {/* Header */}
-
-
-
- M&E, Evidence & Risk Management
-
-
- Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register.
-
-
+ // Indicator thresholds from SRS 9.6:
+ // Green >= 90%, Amber 70-89%, Red < 70%
+ const getIndicatorStatus = (actual: number, target: number) => {
+ if (!target) return { label: 'Pending', badge: 'bg-slate-800/60 text-slate-400 border-slate-700/60' };
+ const pct = (actual / target) * 100;
+ if (pct >= 90) return { label: 'Green (≥90%)', badge: 'bg-emerald-500/10 text-emerald-400 border-emerald-500/20' };
+ if (pct >= 70) return { label: 'Amber (70–89%)', badge: 'bg-amber-500/10 text-amber-400 border-amber-500/20' };
+ return { label: 'Red (<70%)', badge: 'bg-rose-500/10 text-rose-400 border-rose-500/20' };
+ };
-
- {activeTab === 'indicators' && (
-
- )}
- {activeTab === 'risks' && (
-
- )}
-
-
-
- {/* Tabs */}
-
-
-
-
-
-
- {/* Tab 1: Milestone Evidence Submission & Review (Exact Implementation of Figure 10) */}
- {activeTab === 'evidence' && (
-
- {/* Wireframe Container from SRS Figure 10 */}
-
- {/* Header Bar */}
-
-
- RECIPIENT PORTAL • Project XYZ • Milestone 3: Prototype Delivery
-
- Milestone Evidence Submission
-
-
- {/* Workflow Progress Steps */}
-
-
-
Planned
-
In Progress
-
- Submitted
-
-
- Under Review
-
-
- Verified & Paid
-
-
-
-
-
- Current status:
-
- SUBMITTED — awaiting Programme Manager review
-
-
-
-
-
- {/* Upload Evidence & Attached Evidence Panels (Matching Figure 10) */}
-
- {/* Upload Box */}
-
-
-
Upload Evidence
-
-
-
-
-
-
- Drag & drop or browse files. Accepted formats: PDF, JPG, DOCX, XLSX (Max 25MB).
-
-
-
fileInputRef.current?.click()}
- className="border-2 border-dashed border-slate-700/80 hover:border-blue-500/80 bg-slate-900/40 hover:bg-blue-500/5 rounded-2xl p-8 text-center cursor-pointer transition flex flex-col items-center justify-center space-y-3"
- >
-
-
- Click to Browse or Drag Files Here
- Automated SHA-256 hash stamp applied on intake
-
-
-
-
-
-
- {/* Attached Evidence List */}
-
-
-
Attached Evidence (v2)
- {evidenceList.length} files loaded
-
-
-
- {evidenceList.map((ev) => (
-
-
-
-
-
- {ev.fileName || `evidence_file_${ev.id}.pdf`}
-
-
- {ev.fileSize || '3.2 MB'} • v{ev.version} • {getEvidenceTypeLabel(ev.type)}
-
-
-
-
-
-
- Under Review
-
- {ev.documentUrl && (
-
-
-
- )}
-
-
- ))}
-
-
-
-
- {/* Programme Manager Review Section (Figure 10) */}
-
-
Programme Manager Review
-
-
-
- setReviewerComment(e.target.value)}
- placeholder="Mandatory if requesting rework; optional for approvals..."
- className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"
- />
-
-
-
-
-
-
-
-
-
-
- Approval auto-triggers payment eligibility check.
-
-
-
-
-
- )}
-
- {/* Tab 2: Indicators Table (M08) */}
- {activeTab === 'indicators' && (
-
-
-
-
-
- | Indicator Name |
- Project ID |
- Unit of Measure |
- Baseline |
- Target |
- Actual Recorded |
- Status |
- Action |
-
-
-
- {loadingIndicators ? (
-
- |
- Loading indicators...
- |
-
- ) : (
- indicators.map((ind) => {
- const statusObj = getIndicatorStatus(ind.actualAmount, ind.targetAmount);
- return (
-
- | {ind.name} |
- Project #{ind.projectId} |
- {getUnitOfMeasureLabel(ind.unitOfMeasure)} |
- {ind.baselineAmount} |
- {ind.targetAmount} |
-
- {ind.actualAmount}
- |
-
-
- {statusObj.label}
-
- |
-
-
- |
-
- );
- })
- )}
-
-
-
-
- )}
-
- {/* Tab 3: Risk Register (M09) */}
- {activeTab === 'risks' && (
-
-
-
-
-
- | Risk Title & Description |
- Project ID |
- Likelihood (1–5) |
- Impact (1–5) |
- Residual Score |
- Risk Owner |
- Mitigation Control |
-
-
-
- {loadingRisks ? (
-
- |
- Loading risk register...
- |
-
- ) : (
- risks.map((rk) => {
- const score = rk.likelihood * rk.impact;
- const color = getRiskScoreColor(rk.likelihood, rk.impact);
- return (
-
- |
- {rk.name}
- {rk.description}
- |
- Project #{rk.projectId} |
- {rk.likelihood} |
- {rk.impact} |
-
-
- {score} / 25 ({color.level})
-
- |
- {getUserEmail(rk.owner)} |
-
- {rk.mitigationAction || 'Standard internal audit controls applied.'}
- |
-
- );
- })
- )}
-
-
-
-
- )}
-
- {/* Modal: Define Indicator */}
-
setIsIndicatorModalOpen(false)}
- title="Define M&E Indicator"
- subtitle="Logical Framework & Theory of Change metric"
- >
-
-
-
- {/* Modal: Record Actual Amount */}
-
setSelectedIndicatorForActual(null)}
- title="Record Indicator Actual Value"
- subtitle={selectedIndicatorForActual?.name || ''}
- >
-
-
- Baseline: {selectedIndicatorForActual?.baselineAmount}
- Target: {selectedIndicatorForActual?.targetAmount}
-
-
-
-
- setActualInput(Number(e.target.value))}
- className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"
- />
-
-
-
-
-
-
-
-
-
- {/* Modal: Add Risk */}
-
setIsRiskModalOpen(false)}
- title="Add Risk to Risk Register"
- subtitle="Identify and score project delivery risks"
- >
-
-
+ return (
+
+ {/* Header */}
+
+
+
+ M&E, Evidence & Risk Management
+
+
+ Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register.
+
- );
+
+
+ {activeTab === 'indicators' && (
+
+ )}
+ {activeTab === 'risks' && (
+
+ )}
+
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+ {/* Tab 1: Milestone Evidence Submission & Review (Exact Implementation of Figure 10) */}
+ {activeTab === 'evidence' && (
+
+ {/* Wireframe Container from SRS Figure 10 */}
+
+ {/* Header Bar */}
+
+
+ RECIPIENT PORTAL • Project XYZ • Milestone 3: Prototype Delivery
+
+ Milestone Evidence Submission
+
+
+ {/* Workflow Progress Steps */}
+
+
+
Planned
+
In Progress
+
+ Submitted
+
+
+ Under Review
+
+
+ Verified & Paid
+
+
+
+
+
+ Current status:
+
+ SUBMITTED — awaiting Programme Manager review
+
+
+
+
+
+ {/* Upload Evidence & Attached Evidence Panels (Matching Figure 10) */}
+
+ {/* Upload Box */}
+
+
+
Upload Evidence
+
+
+
+
+
+
+ Drag & drop or browse files. Accepted formats: PDF, JPG, DOCX, XLSX (Max 25MB).
+
+
+
fileInputRef.current?.click()}
+ className="border-2 border-dashed border-slate-700/80 hover:border-blue-500/80 bg-slate-900/40 hover:bg-blue-500/5 rounded-2xl p-8 text-center cursor-pointer transition flex flex-col items-center justify-center space-y-3"
+ >
+
+
+ Click to Browse or Drag Files Here
+ Automated SHA-256 hash stamp applied on intake
+
+
+
+
+
+
+ {/* Attached Evidence List */}
+
+
+
+
Attached Evidence (v2)
+ {evidenceList.length} files loaded
+
+
+ Milestone:
+
+
+
+
+
+ {evidenceList.map((ev) => (
+
+
+
+
+
+ {ev.fileName || `evidence_file_${ev.id}.pdf`}
+
+
+ {ev.fileSize || '3.2 MB'} • v{ev.version} • {getEvidenceTypeLabel(ev.type)}
+
+
+
+
+
+
+ Under Review
+
+
+ {ev.documentUrl && (
+
+
+
+ )}
+
+
+ ))}
+
+
+
+
+ {/* Programme Manager Review Section (Figure 10) */}
+
+
Programme Manager Review
+
+
+
+ setReviewerComment(e.target.value)}
+ placeholder="Mandatory if requesting rework; optional for approvals..."
+ className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition"
+ />
+
+
+
+
+
+
+
+
+
+
+ Approval auto-triggers payment eligibility check.
+
+
+
+
+
+ )}
+
+ {/* Tab 2: Indicators Table (M08) */}
+ {activeTab === 'indicators' && (
+
+
+
+
+
+ | Indicator Name |
+ Project ID |
+ Unit of Measure |
+ Baseline |
+ Target |
+ Actual Recorded |
+ Status |
+ Action |
+
+
+
+ {loadingIndicators ? (
+
+ |
+ Loading indicators...
+ |
+
+ ) : (
+ indicators.map((ind) => {
+ const statusObj = getIndicatorStatus(ind.actualAmount, ind.targetAmount);
+ return (
+
+ | {ind.name} |
+ Project #{ind.projectId} |
+ {getUnitOfMeasureLabel(ind.unitOfMeasure)} |
+ {ind.baselineAmount} |
+ {ind.targetAmount} |
+
+ {ind.actualAmount}
+ |
+
+
+ {statusObj.label}
+
+ |
+
+
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Tab 3: Risk Register (M09) */}
+ {activeTab === 'risks' && (
+
+
+
+
+
+ | Risk Title & Description |
+ Project ID |
+ Likelihood (1–5) |
+ Impact (1–5) |
+ Residual Score |
+ Risk Owner |
+ Mitigation Control |
+
+
+
+ {loadingRisks ? (
+
+ |
+ Loading risk register...
+ |
+
+ ) : (
+ risks.map((rk) => {
+ const score = rk.likelihood * rk.impact;
+ const color = getRiskScoreColor(rk.likelihood, rk.impact);
+ return (
+
+ |
+ {rk.name}
+ {rk.description}
+ |
+ Project #{rk.projectId} |
+ {rk.likelihood} |
+ {rk.impact} |
+
+
+ {score} / 25 ({color.level})
+
+ |
+ {getUserEmail(rk.owner)} |
+
+ {rk.mitigationAction || 'Standard internal audit controls applied.'}
+ |
+
+ );
+ })
+ )}
+
+
+
+
+ )}
+
+ {/* Modal: Define Indicator */}
+
setIsIndicatorModalOpen(false)}
+ title="Define M&E Indicator"
+ subtitle="Logical Framework & Theory of Change metric"
+ >
+
+
+
+ {/* Modal: Record Actual Amount */}
+
setSelectedIndicatorForActual(null)}
+ title="Record Indicator Actual Value"
+ subtitle={selectedIndicatorForActual?.name || ''}
+ >
+
+
+ Baseline: {selectedIndicatorForActual?.baselineAmount}
+ Target: {selectedIndicatorForActual?.targetAmount}
+
+
+
+
+ setActualInput(Number(e.target.value))}
+ className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 font-mono transition"
+ />
+
+
+
+
+
+
+
+
+
+ {/* Modal: Add Risk */}
+
setIsRiskModalOpen(false)}
+ title="Add Risk to Risk Register"
+ subtitle="Identify and score project delivery risks"
+ >
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx
index ab8a68b..3b1ac7b 100644
--- a/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx
@@ -4,76 +4,79 @@ import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import {
- FolderKanban,
- Plus,
- CheckCircle2,
- Calendar,
- Layers,
- ChevronRight,
- Clock,
- Briefcase,
- AlertCircle,
- Flag,
- FileCheck,
- TrendingUp,
- X,
+ FolderKanban,
+ Plus,
+ CheckCircle2,
+ Calendar,
+ Layers,
+ ChevronRight,
+ Clock,
+ Briefcase,
+ AlertCircle,
+ Flag,
+ FileCheck,
+ TrendingUp,
+ X,
} from 'lucide-react';
import {
- getProjects,
- createProject,
- updateProjectStatus,
- getActivities,
- createActivity,
- getMilestones,
- createMilestone,
- getAllProgrammes,
- getProjectIndicators,
- getProjectRisks,
+ getProjects,
+ createProject,
+ updateProjectStatus,
+ getActivities,
+ createActivity,
+ getMilestones,
+ createMilestone,
+ getAllProgrammes,
+ getProgrammeProjects,
+ getActivity,
+ getMilestone,
+ getProjectIndicators,
+ getProjectRisks,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import {
- Project,
- IndustrySector,
- ProjectType,
- ApprovalStatus,
- Priority,
- CreateProjectRequest,
- CreateActivityRequest,
- CreateMilestoneRequest,
+ Project,
+ IndustrySector,
+ ProjectType,
+ ApprovalStatus,
+ Priority,
+ CreateProjectRequest,
+ CreateActivityRequest,
+ CreateMilestoneRequest,
} from '../types';
import {
- getIndustrySectorLabel,
- getProjectTypeLabel,
- getApprovalStatusBadge,
- getPriorityBadge,
- formatDate,
- formatZAR,
- INDUSTRY_SECTOR_OPTIONS,
- PROJECT_TYPE_OPTIONS,
- PRIORITY_OPTIONS,
- APPROVAL_STATUS_OPTIONS,
+ getIndustrySectorLabel,
+ getProjectTypeLabel,
+ getApprovalStatusBadge,
+ getPriorityBadge,
+ formatDate,
+ formatZAR,
+ INDUSTRY_SECTOR_OPTIONS,
+ PROJECT_TYPE_OPTIONS,
+ PRIORITY_OPTIONS,
+ APPROVAL_STATUS_OPTIONS,
} from '../lib/formatters';
const projectSchema = z.object({
- name: z.string().min(4, 'Project name must be at least 4 characters'),
- description: z.string().min(10, 'Provide a meaningful implementation description'),
- programmeId: z.coerce.number().min(1, 'Select a programme'),
- sector: z.coerce.number().min(1),
- projectType: z.coerce.number().min(1),
+ name: z.string().min(4, 'Project name must be at least 4 characters'),
+ description: z.string().min(10, 'Provide a meaningful implementation description'),
+ programmeId: z.coerce.number().min(1, 'Select a programme'),
+ sector: z.coerce.number().min(1),
+ projectType: z.coerce.number().min(1),
});
const activitySchema = z.object({
- title: z.string().min(3, 'Activity title required'),
- description: z.string().min(5, 'Description required'),
- category: z.string().min(2, 'Category required'),
- amount: z.coerce.number().min(1, 'Amount must be positive'),
+ title: z.string().min(3, 'Activity title required'),
+ description: z.string().min(5, 'Description required'),
+ category: z.string().min(2, 'Category required'),
+ amount: z.coerce.number().min(1, 'Amount must be positive'),
});
const milestoneSchema = z.object({
- name: z.string().min(4, 'Milestone deliverable required'),
- dueAt: z.string().min(4, 'Due date is mandatory'),
- priority: z.coerce.number().min(1).max(4),
+ name: z.string().min(4, 'Milestone deliverable required'),
+ dueAt: z.string().min(4, 'Due date is mandatory'),
+ priority: z.coerce.number().min(1).max(4),
});
type ProjectFormValues = z.infer
;
@@ -81,709 +84,732 @@ type ActivityFormValues = z.infer;
type MilestoneFormValues = z.infer;
export const ProjectsPage: React.FC = () => {
- const queryClient = useQueryClient();
- const { success, error: toastError } = useToast();
+ const queryClient = useQueryClient();
+ const { success, error: toastError } = useToast();
- const [selectedProject, setSelectedProject] = useState(null);
- const [detailTab, setDetailTab] = useState<'activities' | 'milestones' | 'indicators' | 'risks'>('activities');
+ const [selectedProject, setSelectedProject] = useState(null);
+ const [detailTab, setDetailTab] = useState<'activities' | 'milestones' | 'indicators' | 'risks'>('activities');
+ const [selectedProgrammeFilter, setSelectedProgrammeFilter] = useState('all');
- // Modals
- const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
- const [isActivityModalOpen, setIsActivityModalOpen] = useState(false);
- const [isMilestoneModalOpen, setIsMilestoneModalOpen] = useState(false);
+ // Modals
+ const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
+ const [isActivityModalOpen, setIsActivityModalOpen] = useState(false);
+ const [isMilestoneModalOpen, setIsMilestoneModalOpen] = useState(false);
- // Queries
- const { data: projects = [], isLoading: loadingProjects } = useQuery({
- queryKey: ['projects'],
- queryFn: () => getProjects(),
- });
+ // Queries
+ const { data: programmes = [] } = useQuery({
+ queryKey: ['programmes'],
+ queryFn: () => getAllProgrammes(),
+ });
- const { data: programmes = [] } = useQuery({
- queryKey: ['programmes'],
- queryFn: () => getAllProgrammes(),
- });
+ const { data: projects = [], isLoading: loadingProjects } = useQuery({
+ queryKey: ['projects', selectedProgrammeFilter],
+ queryFn: () =>
+ selectedProgrammeFilter !== 'all'
+ ? getProgrammeProjects(selectedProgrammeFilter)
+ : getProjects(),
+ });
- // Details Queries (for currently selected project)
- const activeProjectId = selectedProject ? selectedProject.id : null;
+ // Details Queries (for currently selected project)
+ const activeProjectId = selectedProject ? selectedProject.id : null;
- const { data: activities = [] } = useQuery({
- queryKey: ['activities', activeProjectId],
- queryFn: () => (activeProjectId ? getActivities(activeProjectId) : Promise.resolve([])),
- enabled: !!activeProjectId,
- });
+ const { data: activities = [] } = useQuery({
+ queryKey: ['activities', activeProjectId],
+ queryFn: () => (activeProjectId ? getActivities(activeProjectId) : Promise.resolve([])),
+ enabled: !!activeProjectId,
+ });
- const { data: milestones = [] } = useQuery({
- queryKey: ['milestones', activeProjectId],
- queryFn: () => (activeProjectId ? getMilestones(activeProjectId) : Promise.resolve([])),
- enabled: !!activeProjectId,
- });
+ const { data: milestones = [] } = useQuery({
+ queryKey: ['milestones', activeProjectId],
+ queryFn: () => (activeProjectId ? getMilestones(activeProjectId) : Promise.resolve([])),
+ enabled: !!activeProjectId,
+ });
- const { data: indicators = [] } = useQuery({
- queryKey: ['indicators', activeProjectId],
- queryFn: () => (activeProjectId ? getProjectIndicators(activeProjectId) : Promise.resolve([])),
- enabled: !!activeProjectId,
- });
+ const { data: indicators = [] } = useQuery({
+ queryKey: ['indicators', activeProjectId],
+ queryFn: () => (activeProjectId ? getProjectIndicators(activeProjectId) : Promise.resolve([])),
+ enabled: !!activeProjectId,
+ });
- const { data: risks = [] } = useQuery({
- queryKey: ['risks', activeProjectId],
- queryFn: () => (activeProjectId ? getProjectRisks(activeProjectId) : Promise.resolve([])),
- enabled: !!activeProjectId,
- });
+ const { data: risks = [] } = useQuery({
+ queryKey: ['risks', activeProjectId],
+ queryFn: () => (activeProjectId ? getProjectRisks(activeProjectId) : Promise.resolve([])),
+ enabled: !!activeProjectId,
+ });
- // Forms
- const projectForm = useForm({
- resolver: zodResolver(projectSchema) as any,
- defaultValues: {
- name: '',
- description: '',
- programmeId: programmes[0]?.id ? Number(programmes[0].id) : 0,
- sector: IndustrySector.AgriTech,
- projectType: ProjectType.Innovation,
- },
- });
+ // Forms
+ const projectForm = useForm({
+ resolver: zodResolver(projectSchema) as any,
+ defaultValues: {
+ name: '',
+ description: '',
+ programmeId: programmes[0]?.id ? Number(programmes[0].id) : 0,
+ sector: IndustrySector.AgriTech,
+ projectType: ProjectType.Innovation,
+ },
+ });
- const activityForm = useForm({
- resolver: zodResolver(activitySchema) as any,
- defaultValues: {
- title: '',
- description: '',
- category: '',
- amount: 0,
- },
- });
+ const activityForm = useForm({
+ resolver: zodResolver(activitySchema) as any,
+ defaultValues: {
+ title: '',
+ description: '',
+ category: '',
+ amount: 0,
+ },
+ });
- const milestoneForm = useForm({
- resolver: zodResolver(milestoneSchema) as any,
- defaultValues: {
- name: '',
- dueAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
- priority: Priority.High,
- },
- });
+ const milestoneForm = useForm({
+ resolver: zodResolver(milestoneSchema) as any,
+ defaultValues: {
+ name: '',
+ dueAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10),
+ priority: Priority.High,
+ },
+ });
- // Mutations
- const projectMutation = useMutation({
- mutationFn: (data: CreateProjectRequest) => createProject(data),
- onSuccess: (newProj) => {
- queryClient.invalidateQueries({ queryKey: ['projects'] });
- success('Project registered', `Project "${newProj.name}" created successfully`);
- setIsProjectModalOpen(false);
- projectForm.reset();
- setSelectedProject(newProj);
- },
- onError: (err: unknown) => {
- toastError('Failed to create project', String(err));
- },
- });
+ // Mutations
+ const projectMutation = useMutation({
+ mutationFn: (data: CreateProjectRequest) => createProject(data),
+ onSuccess: (newProj) => {
+ queryClient.invalidateQueries({ queryKey: ['projects'] });
+ success('Project registered', `Project "${newProj.name}" created successfully`);
+ setIsProjectModalOpen(false);
+ projectForm.reset();
+ setSelectedProject(newProj);
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to create project', String(err));
+ },
+ });
- const statusMutation = useMutation({
- mutationFn: ({ projectId, status }: { projectId: number | string; status: ApprovalStatus }) =>
- updateProjectStatus({ projectId, status }),
- onSuccess: (_, variables) => {
- queryClient.invalidateQueries({ queryKey: ['projects'] });
- if (selectedProject) {
- setSelectedProject({ ...selectedProject, status: variables.status });
- }
- success('Status updated', 'Project governance status changed successfully');
- },
- onError: (err: unknown) => {
- toastError('Failed to update status', String(err));
- },
- });
+ const statusMutation = useMutation({
+ mutationFn: ({ projectId, status }: { projectId: number | string; status: ApprovalStatus }) =>
+ updateProjectStatus({ projectId, status }),
+ onSuccess: (_, variables) => {
+ queryClient.invalidateQueries({ queryKey: ['projects'] });
+ if (selectedProject) {
+ setSelectedProject({ ...selectedProject, status: variables.status });
+ }
+ success('Status updated', 'Project governance status changed successfully');
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to update status', String(err));
+ },
+ });
- const activityMutation = useMutation({
- mutationFn: (data: CreateActivityRequest) => createActivity(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['activities', activeProjectId] });
- success('Activity added', 'Activity registered to project work breakdown');
- setIsActivityModalOpen(false);
- activityForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to add activity', String(err));
- },
- });
+ const activityMutation = useMutation({
+ mutationFn: (data: CreateActivityRequest) => createActivity(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['activities', activeProjectId] });
+ success('Activity added', 'Activity registered to project work breakdown');
+ setIsActivityModalOpen(false);
+ activityForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to add activity', String(err));
+ },
+ });
- const milestoneMutation = useMutation({
- mutationFn: (data: CreateMilestoneRequest) => createMilestone(data),
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['milestones', activeProjectId] });
- success('Milestone created', 'Contractual deliverable added to implementation schedule');
- setIsMilestoneModalOpen(false);
- milestoneForm.reset();
- },
- onError: (err: unknown) => {
- toastError('Failed to add milestone', String(err));
- },
- });
+ const milestoneMutation = useMutation({
+ mutationFn: (data: CreateMilestoneRequest) => createMilestone(data),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['milestones', activeProjectId] });
+ success('Milestone created', 'Contractual deliverable added to implementation schedule');
+ setIsMilestoneModalOpen(false);
+ milestoneForm.reset();
+ },
+ onError: (err: unknown) => {
+ toastError('Failed to add milestone', String(err));
+ },
+ });
- // Calculate project progress based on milestones (FR-M04-018)
- const completedMilestones = milestones.filter((m) => m.status === ApprovalStatus.Approved).length;
- const milestoneProgress = milestones.length > 0 ? Math.round((completedMilestones / milestones.length) * 100) : 0;
+ // Calculate project progress based on milestones (FR-M04-018)
+ const completedMilestones = milestones.filter((m) => m.status === ApprovalStatus.Approved).length;
+ const milestoneProgress = milestones.length > 0 ? Math.round((completedMilestones / milestones.length) * 100) : 0;
- return (
-
- {/* Page Header */}
-
-
-
-
- Project & Milestone Management
-
-
- Work Breakdown
-
-
-
- Work breakdown structure, contractual deliverables, milestone evidence tracking, and progress calculation.
-
-
-
-
-
-
-
- {/* Left Column: Project Directory List */}
-
-
- Projects Directory ({projects.length})
-
-
- {loadingProjects ? (
-
- Loading projects...
-
- ) : (
-
- {projects.map((p) => {
- const isSelected = selectedProject?.id === p.id;
- const statusBadge = getApprovalStatusBadge(p.status);
- return (
-
setSelectedProject(p)}
- className={`p-4 rounded-2xl border transition-all cursor-pointer bg-[#0f1523]/80 backdrop-blur-sm text-left ${isSelected
- ? 'border-blue-500 ring-2 ring-blue-500/20 shadow-lg shadow-blue-500/10'
- : 'border-slate-800/80 hover:border-slate-700 hover:shadow-md'
- }`}
- >
-
- {p.name}
-
- {statusBadge.label.split('/')[0]}
-
-
-
-
{p.description}
-
-
-
- {getIndustrySectorLabel(p.sector)}
-
- •
- {getProjectTypeLabel(p.projectType)}
-
-
- );
- })}
-
- )}
-
-
- {/* Right Column: Detailed Project Workspace */}
-
- {selectedProject ? (
-
- {/* Project Header */}
-
-
-
-
- Project #{selectedProject.id}
-
-
{selectedProject.name}
-
{selectedProject.description}
-
-
- {/* Status Change Selector */}
-
-
-
-
-
-
- {/* Progress bar (FR-M04-018: calculated from approved milestones) */}
-
-
-
-
- Calculated Progress (Milestone-based)
-
- {milestoneProgress}%
-
-
-
- {completedMilestones} of {milestones.length} milestones verified
- Target: 100% completion
-
-
-
-
- {/* Sub-tabs */}
-
-
-
-
-
-
-
- {/* Tab Content */}
-
- {/* Activities Sub-tab */}
- {detailTab === 'activities' && (
-
-
-
Project Work Breakdown
-
-
-
- {activities.length === 0 ? (
-
No activities recorded yet.
- ) : (
-
- {activities.map((a) => (
-
-
- {a.title}
- {formatZAR(a.amount)}
-
-
{a.description}
-
- {a.category}
- Date: {formatDate(a.performedAt)}
-
-
- ))}
-
- )}
-
- )}
-
- {/* Milestones Sub-tab */}
- {detailTab === 'milestones' && (
-
-
-
Contractual Milestones
-
-
-
- {milestones.length === 0 ? (
-
No milestones scheduled.
- ) : (
-
- {milestones.map((m) => {
- const statusBadge = getApprovalStatusBadge(m.status);
- const priBadge = getPriorityBadge(m.priority);
- return (
-
-
- {m.name}
-
- {statusBadge.label}
-
-
-
-
-
- Due: {formatDate(m.dueAt)}
-
-
- {priBadge.label}
-
-
-
- );
- })}
-
- )}
-
- )}
-
- {/* Indicators Sub-tab */}
- {detailTab === 'indicators' && (
-
-
Assigned Project Indicators
- {indicators.length === 0 ? (
-
No indicators attached.
- ) : (
- indicators.map((ind) => {
- const pct = ind.targetAmount > 0 ? Math.round((ind.actualAmount / ind.targetAmount) * 100) : 0;
- return (
-
-
- {ind.name}
- = 90 ? 'text-emerald-400' : 'text-amber-400'}>{pct}%
-
-
- Baseline: {ind.baselineAmount}
- Target: {ind.targetAmount}
- Actual: {ind.actualAmount}
-
-
- );
- })
- )}
-
- )}
-
- {/* Risks Sub-tab */}
- {detailTab === 'risks' && (
-
-
Project Risk Register (M09)
- {risks.length === 0 ? (
-
No project risks logged.
- ) : (
- risks.map((rk) => (
-
-
- {rk.name}
-
- Score: {rk.likelihood * rk.impact} / 25
-
-
-
{rk.description}
- {rk.mitigationAction && (
-
- Mitigation: {rk.mitigationAction}
-
- )}
-
- ))
- )}
-
- )}
-
-
- ) : (
-
-
-
Select a project to inspect implementation
-
View activities, milestones, indicators, and risk controls.
-
- )}
-
-
-
- {/* Modal: Create Project */}
-
setIsProjectModalOpen(false)}
- title="Create Funded Project"
- subtitle="Operational entity under an approved Programme"
- >
-
-
-
- {/* Modal: Add Activity */}
-
setIsActivityModalOpen(false)}
- title="Add Activity to Implementation Plan"
- subtitle={`Registering activity under Project #${activeProjectId}`}
- >
-
-
-
- {/* Modal: Add Milestone */}
-
setIsMilestoneModalOpen(false)}
- title="Define Contractual Milestone"
- subtitle={`Deliverable schedule for Project #${activeProjectId}`}
- >
-
-
+ return (
+
+ {/* Page Header */}
+
+
+
+
+ Project & Milestone Management
+
+
+ Work Breakdown
+
+
+
+ Work breakdown structure, contractual deliverables, milestone evidence tracking, and progress calculation.
+
- );
+
+
+
+
+
+ {/* Left Column: Project Directory List */}
+
+
+
+ Projects Directory ({projects.length})
+
+
+
+
+ {loadingProjects ? (
+
+ Loading projects...
+
+ ) : (
+
+ {projects.map((p) => {
+ const isSelected = selectedProject?.id === p.id;
+ const statusBadge = getApprovalStatusBadge(p.status);
+ return (
+
setSelectedProject(p)}
+ className={`p-4 rounded-2xl border transition-all cursor-pointer bg-[#0f1523]/80 backdrop-blur-sm text-left ${
+ isSelected
+ ? 'border-blue-500 ring-2 ring-blue-500/20 shadow-lg shadow-blue-500/10'
+ : 'border-slate-800/80 hover:border-slate-700 hover:shadow-md'
+ }`}
+ >
+
+ {p.name}
+
+ {statusBadge.label.split('/')[0]}
+
+
+
+
{p.description}
+
+
+
+ {getIndustrySectorLabel(p.sector)}
+
+ •
+ {getProjectTypeLabel(p.projectType)}
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* Right Column: Detailed Project Workspace */}
+
+ {selectedProject ? (
+
+ {/* Project Header */}
+
+
+
+
+ Project #{selectedProject.id}
+
+
{selectedProject.name}
+
{selectedProject.description}
+
+
+ {/* Status Change Selector */}
+
+
+
+
+
+
+ {/* Progress bar (FR-M04-018: calculated from approved milestones) */}
+
+
+
+
+ Calculated Progress (Milestone-based)
+
+ {milestoneProgress}%
+
+
+
+ {completedMilestones} of {milestones.length} milestones verified
+ Target: 100% completion
+
+
+
+
+ {/* Sub-tabs */}
+
+
+
+
+
+
+
+ {/* Tab Content */}
+
+ {/* Activities Sub-tab */}
+ {detailTab === 'activities' && (
+
+
+
Project Work Breakdown
+
+
+
+ {activities.length === 0 ? (
+
No activities recorded yet.
+ ) : (
+
+ {activities.map((a) => (
+
+
+ {a.title}
+ {formatZAR(a.amount)}
+
+
{a.description}
+
+ {a.category}
+ Date: {formatDate(a.performedAt)}
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* Milestones Sub-tab */}
+ {detailTab === 'milestones' && (
+
+
+
Contractual Milestones
+
+
+
+ {milestones.length === 0 ? (
+
No milestones scheduled.
+ ) : (
+
+ {milestones.map((m) => {
+ const statusBadge = getApprovalStatusBadge(m.status);
+ const priBadge = getPriorityBadge(m.priority);
+ return (
+
+
+ {m.name}
+
+ {statusBadge.label}
+
+
+
+
+
+ Due: {formatDate(m.dueAt)}
+
+
+ {priBadge.label}
+
+
+
+ );
+ })}
+
+ )}
+
+ )}
+
+ {/* Indicators Sub-tab */}
+ {detailTab === 'indicators' && (
+
+
Assigned Project Indicators
+ {indicators.length === 0 ? (
+
No indicators attached.
+ ) : (
+ indicators.map((ind) => {
+ const pct = ind.targetAmount > 0 ? Math.round((ind.actualAmount / ind.targetAmount) * 100) : 0;
+ return (
+
+
+ {ind.name}
+ = 90 ? 'text-emerald-400' : 'text-amber-400'}>{pct}%
+
+
+ Baseline: {ind.baselineAmount}
+ Target: {ind.targetAmount}
+ Actual: {ind.actualAmount}
+
+
+ );
+ })
+ )}
+
+ )}
+
+ {/* Risks Sub-tab */}
+ {detailTab === 'risks' && (
+
+
Project Risk Register (M09)
+ {risks.length === 0 ? (
+
No project risks logged.
+ ) : (
+ risks.map((rk) => (
+
+
+ {rk.name}
+
+ Score: {rk.likelihood * rk.impact} / 25
+
+
+
{rk.description}
+ {rk.mitigationAction && (
+
+ Mitigation: {rk.mitigationAction}
+
+ )}
+
+ ))
+ )}
+
+ )}
+
+
+ ) : (
+
+
+
Select a project to inspect implementation
+
View activities, milestones, indicators, and risk controls.
+
+ )}
+
+
+
+ {/* Modal: Create Project */}
+
setIsProjectModalOpen(false)}
+ title="Create Funded Project"
+ subtitle="Operational entity under an approved Programme"
+ >
+
+
+
+ {/* Modal: Add Activity */}
+
setIsActivityModalOpen(false)}
+ title="Add Activity to Implementation Plan"
+ subtitle={`Registering activity under Project #${activeProjectId}`}
+ >
+
+
+
+ {/* Modal: Add Milestone */}
+
setIsMilestoneModalOpen(false)}
+ title="Define Contractual Milestone"
+ subtitle={`Deliverable schedule for Project #${activeProjectId}`}
+ >
+
+
+
+ );
};
diff --git a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx
index 57610ec..311a4b3 100644
--- a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx
+++ b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx
@@ -1,501 +1,979 @@
import React, { useState } from 'react';
-import { useQuery } from '@tanstack/react-query';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
- Shield,
- History,
- CheckCircle,
- Bell,
- Send,
- Radio,
- Filter,
- UserCheck,
+ Shield,
+ History,
+ CheckCircle,
+ Bell,
+ Send,
+ Radio,
+ Filter,
+ UserCheck,
+ Users,
+ RefreshCw,
+ Search,
+ Mail,
+ Eye,
+ ExternalLink,
+ Plus,
+ Building2,
+ Award as AwardIcon,
+ FileText,
+ AlertCircle,
} from 'lucide-react';
import {
- getAuditLogs,
+ getAuditLogs,
+ getUsers,
+ getUser,
+ updateUserStatus,
+ syncIntegration,
+ getInvites,
+ sendInvite,
+ getApplicantInvites,
+ getOrganisations,
+ getAwards,
+ getContracts,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import { useAuth } from '../auth/AuthProvider';
+import { getAppConfig } from '../config';
import {
- EmailStatuses,
- NotificationStatus,
- NotificationPlatform,
+ EmailStatuses,
+ NotificationStatus,
+ NotificationPlatform,
+ ActivityStatus,
+ User,
+ Invite,
+ InviteRequest,
+ UserFilterRequest,
} from '../types';
import {
- formatDate,
- getEmailStatusLabel,
- getEmailStatusBadge,
- getNotificationStatusLabel,
- getNotificationStatusBadge,
- getNotificationPlatformLabel,
- getUserEmail,
- EMAIL_STATUSES_OPTIONS,
- NOTIFICATION_STATUS_OPTIONS,
- NOTIFICATION_PLATFORM_OPTIONS,
+ formatDate,
+ getEmailStatusLabel,
+ getEmailStatusBadge,
+ getNotificationStatusLabel,
+ getNotificationStatusBadge,
+ getNotificationPlatformLabel,
+ getUserEmail,
+ getActivityStatusBadge,
+ EMAIL_STATUSES_OPTIONS,
+ NOTIFICATION_STATUS_OPTIONS,
+ NOTIFICATION_PLATFORM_OPTIONS,
} from '../lib/formatters';
interface NotificationItem {
- id: number;
- recipient: string;
- platform: NotificationPlatform;
- status: NotificationStatus;
- subject: string;
- sentAt: string;
- errorMessage?: string;
+ id: number;
+ recipient: string;
+ platform: NotificationPlatform;
+ status: NotificationStatus;
+ subject: string;
+ sentAt: string;
+ errorMessage?: string;
}
export const SettingsPage: React.FC = () => {
- const { user } = useAuth();
- const { success, error: toastError } = useToast();
+ const queryClient = useQueryClient();
+ const { user } = useAuth();
+ const { success, error: toastError } = useToast();
- const [activeTab, setActiveTab] = useState<'notifications' | 'audit' | 'profile'>('notifications');
- const [isSendNotificationModalOpen, setIsSendNotificationModalOpen] = useState(false);
+ const [activeTab, setActiveTab] = useState<'users' | 'invites' | 'notifications' | 'audit' | 'profile'>('users');
- // Email Gateway & Notification Engine states
- const [emailGatewayStatus, setEmailGatewayStatus] = useState
(EmailStatuses.Connected);
- const [selectedPlatformFilter, setSelectedPlatformFilter] = useState('all');
- const [selectedStatusFilter, setSelectedStatusFilter] = useState('all');
+ // ==========================================
+ // 1. User Directory State
+ // ==========================================
+ const [userEmailSearch, setUserEmailSearch] = useState('');
+ const [userStatusFilter, setUserStatusFilter] = useState('all');
+ const [selectedUserForDetail, setSelectedUserForDetail] = useState(null);
+ const [isUserDetailModalOpen, setIsUserDetailModalOpen] = useState(false);
- // Dispatch Form State
- const [dispatchPlatform, setDispatchPlatform] = useState(NotificationPlatform.Email);
- const [dispatchStatus, setDispatchStatus] = useState(NotificationStatus.Sent);
- const [dispatchRecipient, setDispatchRecipient] = useState('');
- const [dispatchSubject, setDispatchSubject] = useState('');
+ const userFilter: UserFilterRequest = {
+ email: userEmailSearch.trim() || undefined,
+ status: userStatusFilter !== 'all' ? userStatusFilter : undefined,
+ };
- const [notifications, setNotifications] = useState([]);
+ const { data: usersList = [], isLoading: loadingUsers, refetch: refetchUsers } = useQuery({
+ queryKey: ['users', userStatusFilter, userEmailSearch],
+ queryFn: () => getUsers(1, 100, userFilter),
+ });
- // Queries
- const { data: auditLogs = [], isLoading: loadingAudit } = useQuery({
- queryKey: ['auditLogs'],
- queryFn: () => getAuditLogs(),
- });
+ const syncMutation = useMutation({
+ mutationFn: () => syncIntegration(),
+ onSuccess: () => {
+ success('Integration Sync Complete', 'Background user and tenant directory synchronized successfully.');
+ queryClient.invalidateQueries({ queryKey: ['users'] });
+ },
+ onError: (err: any) => {
+ toastError('Sync Failed', err?.message || 'Failed to trigger background integration sync.');
+ },
+ });
- return (
-
- {/* Header */}
-
-
-
- System Settings & Communications
-
-
- Communication dispatch gateways, statutory audit trail, and active session claims.
-
-
+ const updateUserStatusMutation = useMutation({
+ mutationFn: ({ userId, status }: { userId: string; status: ActivityStatus }) =>
+ updateUserStatus(userId, status),
+ onSuccess: (_, vars) => {
+ success('User Status Updated', `User status changed to ${getActivityStatusBadge(vars.status).label}.`);
+ queryClient.invalidateQueries({ queryKey: ['users'] });
+ if (selectedUserForDetail && selectedUserForDetail.id === vars.userId) {
+ setSelectedUserForDetail({ ...selectedUserForDetail, status: vars.status });
+ }
+ },
+ onError: (err: any) => {
+ toastError('Update Failed', err?.message || 'Could not update user status.');
+ },
+ });
- {activeTab === 'notifications' && (
-
- )}
-
+ // ==========================================
+ // 2. Communications & Invites State
+ // ==========================================
+ const [isSendInviteModalOpen, setIsSendInviteModalOpen] = useState(false);
+ const [applicantSearchId, setApplicantSearchId] = useState('');
+ const [applicantInvitesResults, setApplicantInvitesResults] = useState
(null);
+ const [searchingApplicant, setSearchingApplicant] = useState(false);
- {/* Tabs */}
-
-
-
-
-
+ // Invite Form State
+ const [inviteOrgId, setInviteOrgId] = useState('');
+ const [invitedOrgId, setInvitedOrgId] = useState('');
+ const [inviteAwardId, setInviteAwardId] = useState('');
+ const [inviteContractId, setInviteContractId] = useState('');
+ const [inviteRecipientUuid, setInviteRecipientUuid] = useState('');
+ const [inviteMessage, setInviteMessage] = useState('');
+ const [invitePlatform, setInvitePlatform] = useState(NotificationPlatform.Email);
- {/* Tab 1: Notifications & Email Gateways */}
- {activeTab === 'notifications' && (
-
- {/* Status KPI Cards */}
-
-
-
- Email Gateway Status
-
- {getEmailStatusBadge(emailGatewayStatus).label}
-
-
-
-
-
-
-
+ const { data: invitesList = [], isLoading: loadingInvites } = useQuery({
+ queryKey: ['invites'],
+ queryFn: () => getInvites(1, 100),
+ });
-
-
Queued / Active Pipeline
-
- {notifications.filter((n) => n.status === NotificationStatus.Pending || n.status === NotificationStatus.Queued || n.status === NotificationStatus.Processing).length}
-
-
Automated retry scheduler active
-
+ const { data: organisations = [] } = useQuery({
+ queryKey: ['organisations'],
+ queryFn: () => getOrganisations(1, 50),
+ });
-
-
Delivery Success Ratio
-
97.4%
-
Cross-platform SLA target: ≥95.0%
-
-
+ const { data: awards = [] } = useQuery({
+ queryKey: ['awards'],
+ queryFn: () => getAwards(1, 50),
+ });
- {/* Filter Bar */}
-
-
-
- Filter Notification Logs:
-
-
-
-
-
-
+ const { data: contracts = [] } = useQuery({
+ queryKey: ['contracts'],
+ queryFn: () => getContracts(1, 50),
+ });
-
-
-
-
-
-
+ const sendInviteMutation = useMutation({
+ mutationFn: (payload: InviteRequest) => sendInvite(payload),
+ onSuccess: () => {
+ success('Invitation Dispatched', 'Statutory communications invitation sent successfully.');
+ queryClient.invalidateQueries({ queryKey: ['invites'] });
+ setIsSendInviteModalOpen(false);
+ setInviteRecipientUuid('');
+ setInviteMessage('');
+ },
+ onError: (err: any) => {
+ toastError('Dispatch Error', err?.message || 'Failed to dispatch invitation.');
+ },
+ });
- {/* Notifications Table */}
-
-
-
-
-
- | Platform Channel |
- Recipient Destination |
- Subject / Notification Message |
- Dispatch Timestamp |
- Status |
- Action |
-
-
-
- {notifications
- .filter((n) => selectedPlatformFilter === 'all' || n.platform === selectedPlatformFilter)
- .filter((n) => selectedStatusFilter === 'all' || n.status === selectedStatusFilter)
- .map((item) => {
- const statusBadge = getNotificationStatusBadge(item.status);
- return (
-
- |
-
-
- {getNotificationPlatformLabel(item.platform)}
-
- |
-
- {item.recipient}
- |
-
- {item.subject}
- {item.errorMessage && (
- {item.errorMessage}
- )}
- |
-
- {formatDate(item.sentAt)}
- |
-
-
- |
-
-
- |
-
- );
- })}
-
-
-
-
-
- )}
+ const handleSearchApplicantInvites = async (e: React.FormEvent) => {
+ e.preventDefault();
+ if (!applicantSearchId.trim()) return;
+ try {
+ setSearchingApplicant(true);
+ const res = await getApplicantInvites(applicantSearchId.trim());
+ setApplicantInvitesResults(res);
+ success('Lookup Complete', `Retrieved ${res.length} invitation records for applicant.`);
+ } catch (err: any) {
+ toastError('Lookup Error', err?.message || 'Applicant invitation lookup failed.');
+ setApplicantInvitesResults([]);
+ } finally {
+ setSearchingApplicant(false);
+ }
+ };
- {/* Tab 2: Statutory Audit Trail */}
- {activeTab === 'audit' && (
-
-
-
-
-
- | Timestamp |
- Action Type |
- Target Entity |
- Actor Email |
- Origin Details |
-
-
-
- {loadingAudit ? (
-
- |
- Loading audit log entries...
- |
-
- ) : (
- auditLogs.map((log) => (
-
- | {formatDate(log.createdAt)} |
- {log.action} |
-
- {log.entityType} #{log.entityId}
- |
-
- {getUserEmail(log.userEmail)}
- |
-
- {log.details || 'Web Session'}
- |
-
- ))
- )}
-
-
-
-
- )}
+ // ==========================================
+ // 3. Email Gateway & Dispatch Simulation
+ // ==========================================
+ const [emailGatewayStatus, setEmailGatewayStatus] = useState(EmailStatuses.Connected);
+ const [isSendNotificationModalOpen, setIsSendNotificationModalOpen] = useState(false);
+ const [dispatchPlatform, setDispatchPlatform] = useState(NotificationPlatform.Email);
+ const [dispatchStatus, setDispatchStatus] = useState(NotificationStatus.Sent);
+ const [dispatchRecipient, setDispatchRecipient] = useState('');
+ const [dispatchSubject, setDispatchSubject] = useState('');
+ const [notifications, setNotifications] = useState([]);
- {/* Tab 3: Session & Token Claims */}
- {activeTab === 'profile' && (
-
-
-
- {user?.name?.slice(0, 2).toUpperCase() || 'PF'}
-
-
-
{user?.name}
-
{user?.email}
-
-
+ // ==========================================
+ // 4. Statutory Audit Trail
+ // ==========================================
+ const { data: auditLogs = [], isLoading: loadingAudit } = useQuery({
+ queryKey: ['auditLogs'],
+ queryFn: () => getAuditLogs(),
+ });
-
-
- Token Claims Accessible by UI
-
-
-
- User Identifier (sub)
- {user?.id}
-
-
- Email Address (email)
- {user?.email}
-
-
- Full Name (name)
- {user?.name}
-
-
- Assigned Role (role)
- {user?.role}
-
-
-
-
- )}
-
- {/* Modal: Dispatch Multi-Channel Notification */}
- setIsSendNotificationModalOpen(false)}
- title="Dispatch Outbound Notification"
- subtitle="Route message through configured multi-channel notification gateway"
- >
-
-
+ return (
+
+ {/* Header */}
+
+
+
+ System Administration & Communications
+
+
+ User access directory, statutory invitations, background integration sync, and audit ledger.
+
- );
+
+
+ {activeTab === 'users' && (
+
+ )}
+
+ {activeTab === 'invites' && (
+
+ )}
+
+ {activeTab === 'notifications' && (
+
+ )}
+
+
+
+ {/* Navigation Tabs */}
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* ============================================================== */}
+ {/* TAB 1: User Directory & Access */}
+ {/* ============================================================== */}
+ {activeTab === 'users' && (
+
+ {/* Filter and Search Bar */}
+
+
+
+ setUserEmailSearch(e.target.value)}
+ placeholder="Search users by email..."
+ className="w-full pl-9 pr-3 py-1.5 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-blue-500/50"
+ />
+
+
+
+ Filter Status:
+
+
+
+
+
+
+ {/* Users Table */}
+
+
+
+
+
+ | User ID (UUID) |
+ Email |
+ Status |
+ Last Login |
+ Actions |
+
+
+
+ {loadingUsers ? (
+
+ |
+ Loading user directory...
+ |
+
+ ) : usersList.length === 0 ? (
+
+ |
+ No users found matching current filters.
+ |
+
+ ) : (
+ usersList.map((u) => {
+ const badge = getActivityStatusBadge(u.status);
+ return (
+
+ |
+ {u.id}
+ |
+
+ {u.email || No email recorded}
+ |
+
+
+ {badge.label}
+
+ |
+
+ {u.lastLoginAt ? formatDate(u.lastLoginAt) : 'Never'}
+ |
+
+ |