- FIX: Даты MAT-1, MAT-2, Package теперь сохраняются при создании записи (INSERT INTO status_checkboxes: record_id, material_date, material2_date, package_date) - FIX: Цена принимает и запятую, и точку (1,5 → 1.5) - src/index.tsx: POST /api/records — парсинг material_date, material2_date, package_date - src/utils/auth.ts: минорные исправления - public/static/app.js: улучшения UX - Cache version: app.js?v=4.1.23
1353 lines
131 KiB
JavaScript
1353 lines
131 KiB
JavaScript
var je=Object.defineProperty;var Ft=t=>{throw TypeError(t)};var Se=(t,e,r)=>e in t?je(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var x=(t,e,r)=>Se(t,typeof e!="symbol"?e+"":e,r),Mt=(t,e,r)=>e.has(t)||Ft("Cannot "+r);var d=(t,e,r)=>(Mt(t,e,"read from private field"),r?r.call(t):e.get(t)),h=(t,e,r)=>e.has(t)?Ft("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,r),u=(t,e,r,o)=>(Mt(t,e,"write to private field"),o?o.call(t,r):e.set(t,r),r),y=(t,e,r)=>(Mt(t,e,"access private method"),r);var Ht=(t,e,r,o)=>({set _(s){u(t,e,s,r)},get _(){return d(t,e,o)}});var zt=(t,e,r)=>(o,s)=>{let a=-1;return i(0);async function i(l){if(l<=a)throw new Error("next() called multiple times");a=l;let n,c=!1,p;if(t[l]?(p=t[l][0][0],o.req.routeIndex=l):p=l===t.length&&s||void 0,p)try{n=await p(o,()=>i(l+1))}catch(g){if(g instanceof Error&&e)o.error=g,n=await e(g,o),c=!0;else throw g}else o.finalized===!1&&r&&(n=await r(o));return n&&(o.finalized===!1||c)&&(o.res=n),o}},Re=Symbol(),Ae=async(t,e=Object.create(null))=>{const{all:r=!1,dot:o=!1}=e,a=(t instanceof ie?t.raw.headers:t.headers).get("Content-Type");return a!=null&&a.startsWith("multipart/form-data")||a!=null&&a.startsWith("application/x-www-form-urlencoded")?qe(t,{all:r,dot:o}):{}};async function qe(t,e){const r=await t.formData();return r?Me(r,e):{}}function Me(t,e){const r=Object.create(null);return t.forEach((o,s)=>{e.all||s.endsWith("[]")?De(r,s,o):r[s]=o}),e.dot&&Object.entries(r).forEach(([o,s])=>{o.includes(".")&&(Pe(r,o,s),delete r[o])}),r}var De=(t,e,r)=>{t[e]!==void 0?Array.isArray(t[e])?t[e].push(r):t[e]=[t[e],r]:e.endsWith("[]")?t[e]=[r]:t[e]=r},Pe=(t,e,r)=>{let o=t;const s=e.split(".");s.forEach((a,i)=>{i===s.length-1?o[a]=r:((!o[a]||typeof o[a]!="object"||Array.isArray(o[a])||o[a]instanceof File)&&(o[a]=Object.create(null)),o=o[a])})},ee=t=>{const e=t.split("/");return e[0]===""&&e.shift(),e},Ie=t=>{const{groups:e,path:r}=Oe(t),o=ee(r);return Ne(o,e)},Oe=t=>{const e=[];return t=t.replace(/\{[^}]+\}/g,(r,o)=>{const s=`@${o}`;return e.push([s,r]),s}),{groups:e,path:t}},Ne=(t,e)=>{for(let r=e.length-1;r>=0;r--){const[o]=e[r];for(let s=t.length-1;s>=0;s--)if(t[s].includes(o)){t[s]=t[s].replace(o,e[r][1]);break}}return t},kt={},Ce=(t,e)=>{if(t==="*")return"*";const r=t.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);if(r){const o=`${t}#${e}`;return kt[o]||(r[2]?kt[o]=e&&e[0]!==":"&&e[0]!=="*"?[o,r[1],new RegExp(`^${r[2]}(?=/${e})`)]:[t,r[1],new RegExp(`^${r[2]}$`)]:kt[o]=[t,r[1],!0]),kt[o]}return null},Nt=(t,e)=>{try{return e(t)}catch{return t.replace(/(?:%[0-9A-Fa-f]{2})+/g,r=>{try{return e(r)}catch{return r}})}},Le=t=>Nt(t,decodeURI),re=t=>{const e=t.url,r=e.indexOf("/",e.indexOf(":")+4);let o=r;for(;o<e.length;o++){const s=e.charCodeAt(o);if(s===37){const a=e.indexOf("?",o),i=e.slice(r,a===-1?void 0:a);return Le(i.includes("%25")?i.replace(/%25/g,"%2525"):i)}else if(s===63)break}return e.slice(r,o)},Fe=t=>{const e=re(t);return e.length>1&&e.at(-1)==="/"?e.slice(0,-1):e},ot=(t,e,...r)=>(r.length&&(e=ot(e,...r)),`${(t==null?void 0:t[0])==="/"?"":"/"}${t}${e==="/"?"":`${(t==null?void 0:t.at(-1))==="/"?"":"/"}${(e==null?void 0:e[0])==="/"?e.slice(1):e}`}`),oe=t=>{if(t.charCodeAt(t.length-1)!==63||!t.includes(":"))return null;const e=t.split("/"),r=[];let o="";return e.forEach(s=>{if(s!==""&&!/\:/.test(s))o+="/"+s;else if(/\:/.test(s))if(/\?/.test(s)){r.length===0&&o===""?r.push("/"):r.push(o);const a=s.replace("?","");o+="/"+a,r.push(o)}else o+="/"+s}),r.filter((s,a,i)=>i.indexOf(s)===a)},Dt=t=>/[%+]/.test(t)?(t.indexOf("+")!==-1&&(t=t.replace(/\+/g," ")),t.indexOf("%")!==-1?Nt(t,ae):t):t,se=(t,e,r)=>{let o;if(!r&&e&&!/[%+]/.test(e)){let i=t.indexOf("?",8);if(i===-1)return;for(t.startsWith(e,i+1)||(i=t.indexOf(`&${e}`,i+1));i!==-1;){const l=t.charCodeAt(i+e.length+1);if(l===61){const n=i+e.length+2,c=t.indexOf("&",n);return Dt(t.slice(n,c===-1?void 0:c))}else if(l==38||isNaN(l))return"";i=t.indexOf(`&${e}`,i+1)}if(o=/[%+]/.test(t),!o)return}const s={};o??(o=/[%+]/.test(t));let a=t.indexOf("?",8);for(;a!==-1;){const i=t.indexOf("&",a+1);let l=t.indexOf("=",a);l>i&&i!==-1&&(l=-1);let n=t.slice(a+1,l===-1?i===-1?void 0:i:l);if(o&&(n=Dt(n)),a=i,n==="")continue;let c;l===-1?c="":(c=t.slice(l+1,i===-1?void 0:i),o&&(c=Dt(c))),r?(s[n]&&Array.isArray(s[n])||(s[n]=[]),s[n].push(c)):s[n]??(s[n]=c)}return e?s[e]:s},He=se,ze=(t,e)=>se(t,e,!0),ae=decodeURIComponent,Bt=t=>Nt(t,ae),it,q,H,ne,de,It,B,Kt,ie=(Kt=class{constructor(t,e="/",r=[[]]){h(this,H);x(this,"raw");h(this,it);h(this,q);x(this,"routeIndex",0);x(this,"path");x(this,"bodyCache",{});h(this,B,t=>{const{bodyCache:e,raw:r}=this,o=e[t];if(o)return o;const s=Object.keys(e)[0];return s?e[s].then(a=>(s==="json"&&(a=JSON.stringify(a)),new Response(a)[t]())):e[t]=r[t]()});this.raw=t,this.path=e,u(this,q,r),u(this,it,{})}param(t){return t?y(this,H,ne).call(this,t):y(this,H,de).call(this)}query(t){return He(this.url,t)}queries(t){return ze(this.url,t)}header(t){if(t)return this.raw.headers.get(t)??void 0;const e={};return this.raw.headers.forEach((r,o)=>{e[o]=r}),e}async parseBody(t){var e;return(e=this.bodyCache).parsedBody??(e.parsedBody=await Ae(this,t))}json(){return d(this,B).call(this,"text").then(t=>JSON.parse(t))}text(){return d(this,B).call(this,"text")}arrayBuffer(){return d(this,B).call(this,"arrayBuffer")}blob(){return d(this,B).call(this,"blob")}formData(){return d(this,B).call(this,"formData")}addValidatedData(t,e){d(this,it)[t]=e}valid(t){return d(this,it)[t]}get url(){return this.raw.url}get method(){return this.raw.method}get[Re](){return d(this,q)}get matchedRoutes(){return d(this,q)[0].map(([[,t]])=>t)}get routePath(){return d(this,q)[0].map(([[,t]])=>t)[this.routeIndex].path}},it=new WeakMap,q=new WeakMap,H=new WeakSet,ne=function(t){const e=d(this,q)[0][this.routeIndex][1][t],r=y(this,H,It).call(this,e);return r&&/\%/.test(r)?Bt(r):r},de=function(){const t={},e=Object.keys(d(this,q)[0][this.routeIndex][1]);for(const r of e){const o=y(this,H,It).call(this,d(this,q)[0][this.routeIndex][1][r]);o!==void 0&&(t[r]=/\%/.test(o)?Bt(o):o)}return t},It=function(t){return d(this,q)[1]?d(this,q)[1][t]:t},B=new WeakMap,Kt),Be={Stringify:1},le=async(t,e,r,o,s)=>{typeof t=="object"&&!(t instanceof String)&&(t instanceof Promise||(t=t.toString()),t instanceof Promise&&(t=await t));const a=t.callbacks;return a!=null&&a.length?(s?s[0]+=t:s=[t],Promise.all(a.map(l=>l({phase:e,buffer:s,context:o}))).then(l=>Promise.all(l.filter(Boolean).map(n=>le(n,e,!1,o,s))).then(()=>s[0]))):Promise.resolve(t)},Ue="text/plain; charset=UTF-8",Pt=(t,e)=>({"Content-Type":t,...e}),ht,yt,N,nt,C,S,ft,dt,lt,Y,mt,vt,U,st,Vt,We=(Vt=class{constructor(t,e){h(this,U);h(this,ht);h(this,yt);x(this,"env",{});h(this,N);x(this,"finalized",!1);x(this,"error");h(this,nt);h(this,C);h(this,S);h(this,ft);h(this,dt);h(this,lt);h(this,Y);h(this,mt);h(this,vt);x(this,"render",(...t)=>(d(this,dt)??u(this,dt,e=>this.html(e)),d(this,dt).call(this,...t)));x(this,"setLayout",t=>u(this,ft,t));x(this,"getLayout",()=>d(this,ft));x(this,"setRenderer",t=>{u(this,dt,t)});x(this,"header",(t,e,r)=>{this.finalized&&u(this,S,new Response(d(this,S).body,d(this,S)));const o=d(this,S)?d(this,S).headers:d(this,Y)??u(this,Y,new Headers);e===void 0?o.delete(t):r!=null&&r.append?o.append(t,e):o.set(t,e)});x(this,"status",t=>{u(this,nt,t)});x(this,"set",(t,e)=>{d(this,N)??u(this,N,new Map),d(this,N).set(t,e)});x(this,"get",t=>d(this,N)?d(this,N).get(t):void 0);x(this,"newResponse",(...t)=>y(this,U,st).call(this,...t));x(this,"body",(t,e,r)=>y(this,U,st).call(this,t,e,r));x(this,"text",(t,e,r)=>!d(this,Y)&&!d(this,nt)&&!e&&!r&&!this.finalized?new Response(t):y(this,U,st).call(this,t,e,Pt(Ue,r)));x(this,"json",(t,e,r)=>y(this,U,st).call(this,JSON.stringify(t),e,Pt("application/json",r)));x(this,"html",(t,e,r)=>{const o=s=>y(this,U,st).call(this,s,e,Pt("text/html; charset=UTF-8",r));return typeof t=="object"?le(t,Be.Stringify,!1,{}).then(o):o(t)});x(this,"redirect",(t,e)=>{const r=String(t);return this.header("Location",/[^\x00-\xFF]/.test(r)?encodeURI(r):r),this.newResponse(null,e??302)});x(this,"notFound",()=>(d(this,lt)??u(this,lt,()=>new Response),d(this,lt).call(this,this)));u(this,ht,t),e&&(u(this,C,e.executionCtx),this.env=e.env,u(this,lt,e.notFoundHandler),u(this,vt,e.path),u(this,mt,e.matchResult))}get req(){return d(this,yt)??u(this,yt,new ie(d(this,ht),d(this,vt),d(this,mt))),d(this,yt)}get event(){if(d(this,C)&&"respondWith"in d(this,C))return d(this,C);throw Error("This context has no FetchEvent")}get executionCtx(){if(d(this,C))return d(this,C);throw Error("This context has no ExecutionContext")}get res(){return d(this,S)||u(this,S,new Response(null,{headers:d(this,Y)??u(this,Y,new Headers)}))}set res(t){if(d(this,S)&&t){t=new Response(t.body,t);for(const[e,r]of d(this,S).headers.entries())if(e!=="content-type")if(e==="set-cookie"){const o=d(this,S).headers.getSetCookie();t.headers.delete("set-cookie");for(const s of o)t.headers.append("set-cookie",s)}else t.headers.set(e,r)}u(this,S,t),this.finalized=!0}get var(){return d(this,N)?Object.fromEntries(d(this,N)):{}}},ht=new WeakMap,yt=new WeakMap,N=new WeakMap,nt=new WeakMap,C=new WeakMap,S=new WeakMap,ft=new WeakMap,dt=new WeakMap,lt=new WeakMap,Y=new WeakMap,mt=new WeakMap,vt=new WeakMap,U=new WeakSet,st=function(t,e,r){const o=d(this,S)?new Headers(d(this,S).headers):d(this,Y)??new Headers;if(typeof e=="object"&&"headers"in e){const a=e.headers instanceof Headers?e.headers:new Headers(e.headers);for(const[i,l]of a)i.toLowerCase()==="set-cookie"?o.append(i,l):o.set(i,l)}if(r)for(const[a,i]of Object.entries(r))if(typeof i=="string")o.set(a,i);else{o.delete(a);for(const l of i)o.append(a,l)}const s=typeof e=="number"?e:(e==null?void 0:e.status)??d(this,nt);return new Response(t,{status:s,headers:o})},Vt),k="ALL",$e="all",Ke=["get","post","put","delete","options","patch"],ce="Can not add a route since the matcher is already built.",pe=class extends Error{},Ve="__COMPOSED_HANDLER",Je=t=>t.text("404 Not Found",404),Ut=(t,e)=>{if("getResponse"in t){const r=t.getResponse();return e.newResponse(r.body,r)}return console.error(t),e.text("Internal Server Error",500)},M,E,ge,D,J,Et,_t,Jt,ue=(Jt=class{constructor(e={}){h(this,E);x(this,"get");x(this,"post");x(this,"put");x(this,"delete");x(this,"options");x(this,"patch");x(this,"all");x(this,"on");x(this,"use");x(this,"router");x(this,"getPath");x(this,"_basePath","/");h(this,M,"/");x(this,"routes",[]);h(this,D,Je);x(this,"errorHandler",Ut);x(this,"onError",e=>(this.errorHandler=e,this));x(this,"notFound",e=>(u(this,D,e),this));x(this,"fetch",(e,...r)=>y(this,E,_t).call(this,e,r[1],r[0],e.method));x(this,"request",(e,r,o,s)=>e instanceof Request?this.fetch(r?new Request(e,r):e,o,s):(e=e.toString(),this.fetch(new Request(/^https?:\/\//.test(e)?e:`http://localhost${ot("/",e)}`,r),o,s)));x(this,"fire",()=>{addEventListener("fetch",e=>{e.respondWith(y(this,E,_t).call(this,e.request,e,void 0,e.request.method))})});[...Ke,$e].forEach(a=>{this[a]=(i,...l)=>(typeof i=="string"?u(this,M,i):y(this,E,J).call(this,a,d(this,M),i),l.forEach(n=>{y(this,E,J).call(this,a,d(this,M),n)}),this)}),this.on=(a,i,...l)=>{for(const n of[i].flat()){u(this,M,n);for(const c of[a].flat())l.map(p=>{y(this,E,J).call(this,c.toUpperCase(),d(this,M),p)})}return this},this.use=(a,...i)=>(typeof a=="string"?u(this,M,a):(u(this,M,"*"),i.unshift(a)),i.forEach(l=>{y(this,E,J).call(this,k,d(this,M),l)}),this);const{strict:o,...s}=e;Object.assign(this,s),this.getPath=o??!0?e.getPath??re:Fe}route(e,r){const o=this.basePath(e);return r.routes.map(s=>{var i;let a;r.errorHandler===Ut?a=s.handler:(a=async(l,n)=>(await zt([],r.errorHandler)(l,()=>s.handler(l,n))).res,a[Ve]=s.handler),y(i=o,E,J).call(i,s.method,s.path,a)}),this}basePath(e){const r=y(this,E,ge).call(this);return r._basePath=ot(this._basePath,e),r}mount(e,r,o){let s,a;o&&(typeof o=="function"?a=o:(a=o.optionHandler,o.replaceRequest===!1?s=n=>n:s=o.replaceRequest));const i=a?n=>{const c=a(n);return Array.isArray(c)?c:[c]}:n=>{let c;try{c=n.executionCtx}catch{}return[n.env,c]};s||(s=(()=>{const n=ot(this._basePath,e),c=n==="/"?0:n.length;return p=>{const g=new URL(p.url);return g.pathname=g.pathname.slice(c)||"/",new Request(g,p)}})());const l=async(n,c)=>{const p=await r(s(n.req.raw),...i(n));if(p)return p;await c()};return y(this,E,J).call(this,k,ot(e,"*"),l),this}},M=new WeakMap,E=new WeakSet,ge=function(){const e=new ue({router:this.router,getPath:this.getPath});return e.errorHandler=this.errorHandler,u(e,D,d(this,D)),e.routes=this.routes,e},D=new WeakMap,J=function(e,r,o){e=e.toUpperCase(),r=ot(this._basePath,r);const s={basePath:this._basePath,path:r,method:e,handler:o};this.router.add(e,r,[o,s]),this.routes.push(s)},Et=function(e,r){if(e instanceof Error)return this.errorHandler(e,r);throw e},_t=function(e,r,o,s){if(s==="HEAD")return(async()=>new Response(null,await y(this,E,_t).call(this,e,r,o,"GET")))();const a=this.getPath(e,{env:o}),i=this.router.match(s,a),l=new We(e,{path:a,matchResult:i,env:o,executionCtx:r,notFoundHandler:d(this,D)});if(i[0].length===1){let c;try{c=i[0][0][0][0](l,async()=>{l.res=await d(this,D).call(this,l)})}catch(p){return y(this,E,Et).call(this,p,l)}return c instanceof Promise?c.then(p=>p||(l.finalized?l.res:d(this,D).call(this,l))).catch(p=>y(this,E,Et).call(this,p,l)):c??d(this,D).call(this,l)}const n=zt(i[0],this.errorHandler,d(this,D));return(async()=>{try{const c=await n(l);if(!c.finalized)throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");return c.res}catch(c){return y(this,E,Et).call(this,c,l)}})()},Jt),xe=[];function Ge(t,e){const r=this.buildAllMatchers(),o=(s,a)=>{const i=r[s]||r[k],l=i[2][a];if(l)return l;const n=a.match(i[0]);if(!n)return[[],xe];const c=n.indexOf("",1);return[i[1][c],n]};return this.match=o,o(t,e)}var jt="[^/]+",xt=".*",bt="(?:|/.*)",at=Symbol(),Ye=new Set(".\\+*[^]$()");function Xe(t,e){return t.length===1?e.length===1?t<e?-1:1:-1:e.length===1||t===xt||t===bt?1:e===xt||e===bt?-1:t===jt?1:e===jt?-1:t.length===e.length?t<e?-1:1:e.length-t.length}var X,Q,P,Gt,Ot=(Gt=class{constructor(){h(this,X);h(this,Q);h(this,P,Object.create(null))}insert(e,r,o,s,a){if(e.length===0){if(d(this,X)!==void 0)throw at;if(a)return;u(this,X,r);return}const[i,...l]=e,n=i==="*"?l.length===0?["","",xt]:["","",jt]:i==="/*"?["","",bt]:i.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);let c;if(n){const p=n[1];let g=n[2]||jt;if(p&&n[2]&&(g===".*"||(g=g.replace(/^\((?!\?:)(?=[^)]+\)$)/,"(?:"),/\((?!\?:)/.test(g))))throw at;if(c=d(this,P)[g],!c){if(Object.keys(d(this,P)).some(b=>b!==xt&&b!==bt))throw at;if(a)return;c=d(this,P)[g]=new Ot,p!==""&&u(c,Q,s.varIndex++)}!a&&p!==""&&o.push([p,d(c,Q)])}else if(c=d(this,P)[i],!c){if(Object.keys(d(this,P)).some(p=>p.length>1&&p!==xt&&p!==bt))throw at;if(a)return;c=d(this,P)[i]=new Ot}c.insert(l,r,o,s,a)}buildRegExpStr(){const r=Object.keys(d(this,P)).sort(Xe).map(o=>{const s=d(this,P)[o];return(typeof d(s,Q)=="number"?`(${o})@${d(s,Q)}`:Ye.has(o)?`\\${o}`:o)+s.buildRegExpStr()});return typeof d(this,X)=="number"&&r.unshift(`#${d(this,X)}`),r.length===0?"":r.length===1?r[0]:"(?:"+r.join("|")+")"}},X=new WeakMap,Q=new WeakMap,P=new WeakMap,Gt),St,wt,Yt,Qe=(Yt=class{constructor(){h(this,St,{varIndex:0});h(this,wt,new Ot)}insert(t,e,r){const o=[],s=[];for(let i=0;;){let l=!1;if(t=t.replace(/\{[^}]+\}/g,n=>{const c=`@\\${i}`;return s[i]=[c,n],i++,l=!0,c}),!l)break}const a=t.match(/(?::[^\/]+)|(?:\/\*$)|./g)||[];for(let i=s.length-1;i>=0;i--){const[l]=s[i];for(let n=a.length-1;n>=0;n--)if(a[n].indexOf(l)!==-1){a[n]=a[n].replace(l,s[i][1]);break}}return d(this,wt).insert(a,e,o,d(this,St),r),o}buildRegExp(){let t=d(this,wt).buildRegExpStr();if(t==="")return[/^$/,[],[]];let e=0;const r=[],o=[];return t=t.replace(/#(\d+)|@(\d+)|\.\*\$/g,(s,a,i)=>a!==void 0?(r[++e]=Number(a),"$()"):(i!==void 0&&(o[Number(i)]=++e),"")),[new RegExp(`^${t}`),r,o]}},St=new WeakMap,wt=new WeakMap,Yt),Ze=[/^$/,[],Object.create(null)],Tt=Object.create(null);function be(t){return Tt[t]??(Tt[t]=new RegExp(t==="*"?"":`^${t.replace(/\/\*$|([.\\+*[^\]$()])/g,(e,r)=>r?`\\${r}`:"(?:|/.*)")}$`))}function tr(){Tt=Object.create(null)}function er(t){var c;const e=new Qe,r=[];if(t.length===0)return Ze;const o=t.map(p=>[!/\*|\/:/.test(p[0]),...p]).sort(([p,g],[b,w])=>p?1:b?-1:g.length-w.length),s=Object.create(null);for(let p=0,g=-1,b=o.length;p<b;p++){const[w,R,m]=o[p];w?s[R]=[m.map(([j])=>[j,Object.create(null)]),xe]:g++;let v;try{v=e.insert(R,g,w)}catch(j){throw j===at?new pe(R):j}w||(r[g]=m.map(([j,et])=>{const pt=Object.create(null);for(et-=1;et>=0;et--){const[I,At]=v[et];pt[I]=At}return[j,pt]}))}const[a,i,l]=e.buildRegExp();for(let p=0,g=r.length;p<g;p++)for(let b=0,w=r[p].length;b<w;b++){const R=(c=r[p][b])==null?void 0:c[1];if(!R)continue;const m=Object.keys(R);for(let v=0,j=m.length;v<j;v++)R[m[v]]=l[R[m[v]]]}const n=[];for(const p in i)n[p]=r[i[p]];return[a,n,s]}function rt(t,e){if(t){for(const r of Object.keys(t).sort((o,s)=>s.length-o.length))if(be(r).test(e))return[...t[r]]}}var W,$,Rt,he,Xt,rr=(Xt=class{constructor(){h(this,Rt);x(this,"name","RegExpRouter");h(this,W);h(this,$);x(this,"match",Ge);u(this,W,{[k]:Object.create(null)}),u(this,$,{[k]:Object.create(null)})}add(t,e,r){var l;const o=d(this,W),s=d(this,$);if(!o||!s)throw new Error(ce);o[t]||[o,s].forEach(n=>{n[t]=Object.create(null),Object.keys(n[k]).forEach(c=>{n[t][c]=[...n[k][c]]})}),e==="/*"&&(e="*");const a=(e.match(/\/:/g)||[]).length;if(/\*$/.test(e)){const n=be(e);t===k?Object.keys(o).forEach(c=>{var p;(p=o[c])[e]||(p[e]=rt(o[c],e)||rt(o[k],e)||[])}):(l=o[t])[e]||(l[e]=rt(o[t],e)||rt(o[k],e)||[]),Object.keys(o).forEach(c=>{(t===k||t===c)&&Object.keys(o[c]).forEach(p=>{n.test(p)&&o[c][p].push([r,a])})}),Object.keys(s).forEach(c=>{(t===k||t===c)&&Object.keys(s[c]).forEach(p=>n.test(p)&&s[c][p].push([r,a]))});return}const i=oe(e)||[e];for(let n=0,c=i.length;n<c;n++){const p=i[n];Object.keys(s).forEach(g=>{var b;(t===k||t===g)&&((b=s[g])[p]||(b[p]=[...rt(o[g],p)||rt(o[k],p)||[]]),s[g][p].push([r,a-c+n+1]))})}}buildAllMatchers(){const t=Object.create(null);return Object.keys(d(this,$)).concat(Object.keys(d(this,W))).forEach(e=>{t[e]||(t[e]=y(this,Rt,he).call(this,e))}),u(this,W,u(this,$,void 0)),tr(),t}},W=new WeakMap,$=new WeakMap,Rt=new WeakSet,he=function(t){const e=[];let r=t===k;return[d(this,W),d(this,$)].forEach(o=>{const s=o[t]?Object.keys(o[t]).map(a=>[a,o[t][a]]):[];s.length!==0?(r||(r=!0),e.push(...s)):t!==k&&e.push(...Object.keys(o[k]).map(a=>[a,o[k][a]]))}),r?er(e):null},Xt),K,L,Qt,or=(Qt=class{constructor(t){x(this,"name","SmartRouter");h(this,K,[]);h(this,L,[]);u(this,K,t.routers)}add(t,e,r){if(!d(this,L))throw new Error(ce);d(this,L).push([t,e,r])}match(t,e){if(!d(this,L))throw new Error("Fatal error");const r=d(this,K),o=d(this,L),s=r.length;let a=0,i;for(;a<s;a++){const l=r[a];try{for(let n=0,c=o.length;n<c;n++)l.add(...o[n]);i=l.match(t,e)}catch(n){if(n instanceof pe)continue;throw n}this.match=l.match.bind(l),u(this,K,[l]),u(this,L,void 0);break}if(a===s)throw new Error("Fatal error");return this.name=`SmartRouter + ${this.activeRouter.name}`,i}get activeRouter(){if(d(this,L)||d(this,K).length!==1)throw new Error("No active router has been determined yet.");return d(this,K)[0]}},K=new WeakMap,L=new WeakMap,Qt),gt=Object.create(null),V,T,Z,ct,_,F,G,Zt,ye=(Zt=class{constructor(t,e,r){h(this,F);h(this,V);h(this,T);h(this,Z);h(this,ct,0);h(this,_,gt);if(u(this,T,r||Object.create(null)),u(this,V,[]),t&&e){const o=Object.create(null);o[t]={handler:e,possibleKeys:[],score:0},u(this,V,[o])}u(this,Z,[])}insert(t,e,r){u(this,ct,++Ht(this,ct)._);let o=this;const s=Ie(e),a=[];for(let i=0,l=s.length;i<l;i++){const n=s[i],c=s[i+1],p=Ce(n,c),g=Array.isArray(p)?p[0]:n;if(g in d(o,T)){o=d(o,T)[g],p&&a.push(p[1]);continue}d(o,T)[g]=new ye,p&&(d(o,Z).push(p),a.push(p[1])),o=d(o,T)[g]}return d(o,V).push({[t]:{handler:r,possibleKeys:a.filter((i,l,n)=>n.indexOf(i)===l),score:d(this,ct)}}),o}search(t,e){var l;const r=[];u(this,_,gt);let s=[this];const a=ee(e),i=[];for(let n=0,c=a.length;n<c;n++){const p=a[n],g=n===c-1,b=[];for(let w=0,R=s.length;w<R;w++){const m=s[w],v=d(m,T)[p];v&&(u(v,_,d(m,_)),g?(d(v,T)["*"]&&r.push(...y(this,F,G).call(this,d(v,T)["*"],t,d(m,_))),r.push(...y(this,F,G).call(this,v,t,d(m,_)))):b.push(v));for(let j=0,et=d(m,Z).length;j<et;j++){const pt=d(m,Z)[j],I=d(m,_)===gt?{}:{...d(m,_)};if(pt==="*"){const z=d(m,T)["*"];z&&(r.push(...y(this,F,G).call(this,z,t,d(m,_))),u(z,_,I),b.push(z));continue}const[At,Lt,ut]=pt;if(!p&&!(ut instanceof RegExp))continue;const O=d(m,T)[At],Te=a.slice(n).join("/");if(ut instanceof RegExp){const z=ut.exec(Te);if(z){if(I[Lt]=z[0],r.push(...y(this,F,G).call(this,O,t,d(m,_),I)),Object.keys(d(O,T)).length){u(O,_,I);const qt=((l=z[0].match(/\//))==null?void 0:l.length)??0;(i[qt]||(i[qt]=[])).push(O)}continue}}(ut===!0||ut.test(p))&&(I[Lt]=p,g?(r.push(...y(this,F,G).call(this,O,t,I,d(m,_))),d(O,T)["*"]&&r.push(...y(this,F,G).call(this,d(O,T)["*"],t,I,d(m,_)))):(u(O,_,I),b.push(O)))}}s=b.concat(i.shift()??[])}return r.length>1&&r.sort((n,c)=>n.score-c.score),[r.map(({handler:n,params:c})=>[n,c])]}},V=new WeakMap,T=new WeakMap,Z=new WeakMap,ct=new WeakMap,_=new WeakMap,F=new WeakSet,G=function(t,e,r,o){const s=[];for(let a=0,i=d(t,V).length;a<i;a++){const l=d(t,V)[a],n=l[e]||l[k],c={};if(n!==void 0&&(n.params=Object.create(null),s.push(n),r!==gt||o&&o!==gt))for(let p=0,g=n.possibleKeys.length;p<g;p++){const b=n.possibleKeys[p],w=c[n.score];n.params[b]=o!=null&&o[b]&&!w?o[b]:r[b]??(o==null?void 0:o[b]),c[n.score]=!0}}return s},Zt),tt,te,sr=(te=class{constructor(){x(this,"name","TrieRouter");h(this,tt);u(this,tt,new ye)}add(t,e,r){const o=oe(e);if(o){for(let s=0,a=o.length;s<a;s++)d(this,tt).insert(t,o[s],r);return}d(this,tt).insert(t,e,r)}match(t,e){return d(this,tt).search(t,e)}},tt=new WeakMap,te),fe=class extends ue{constructor(t={}){super(t),this.router=t.router??new or({routers:[new rr,new sr]})}},ar=t=>{const r={...{origin:"*",allowMethods:["GET","HEAD","PUT","POST","DELETE","PATCH"],allowHeaders:[],exposeHeaders:[]},...t},o=(a=>typeof a=="string"?a==="*"?()=>a:i=>a===i?i:null:typeof a=="function"?a:i=>a.includes(i)?i:null)(r.origin),s=(a=>typeof a=="function"?a:Array.isArray(a)?()=>a:()=>[])(r.allowMethods);return async function(i,l){var p;function n(g,b){i.res.headers.set(g,b)}const c=await o(i.req.header("origin")||"",i);if(c&&n("Access-Control-Allow-Origin",c),r.credentials&&n("Access-Control-Allow-Credentials","true"),(p=r.exposeHeaders)!=null&&p.length&&n("Access-Control-Expose-Headers",r.exposeHeaders.join(",")),i.req.method==="OPTIONS"){r.origin!=="*"&&n("Vary","Origin"),r.maxAge!=null&&n("Access-Control-Max-Age",r.maxAge.toString());const g=await s(i.req.header("origin")||"",i);g.length&&n("Access-Control-Allow-Methods",g.join(","));let b=r.allowHeaders;if(!(b!=null&&b.length)){const w=i.req.header("Access-Control-Request-Headers");w&&(b=w.split(/\s*,\s*/))}return b!=null&&b.length&&(n("Access-Control-Allow-Headers",b.join(",")),i.res.headers.append("Vary","Access-Control-Request-Headers")),i.res.headers.delete("Content-Length"),i.res.headers.delete("Content-Type"),new Response(null,{headers:i.res.headers,status:204,statusText:"No Content"})}await l(),r.origin!=="*"&&i.header("Vary","Origin",{append:!0})}},ir=/^\s*(?:text\/(?!event-stream(?:[;\s]|$))[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i,Wt=(t,e=dr)=>{const r=/\.([a-zA-Z0-9]+?)$/,o=t.match(r);if(!o)return;let s=e[o[1]];return s&&s.startsWith("text")&&(s+="; charset=utf-8"),s},nr={aac:"audio/aac",avi:"video/x-msvideo",avif:"image/avif",av1:"video/av1",bin:"application/octet-stream",bmp:"image/bmp",css:"text/css",csv:"text/csv",eot:"application/vnd.ms-fontobject",epub:"application/epub+zip",gif:"image/gif",gz:"application/gzip",htm:"text/html",html:"text/html",ico:"image/x-icon",ics:"text/calendar",jpeg:"image/jpeg",jpg:"image/jpeg",js:"text/javascript",json:"application/json",jsonld:"application/ld+json",map:"application/json",mid:"audio/x-midi",midi:"audio/x-midi",mjs:"text/javascript",mp3:"audio/mpeg",mp4:"video/mp4",mpeg:"video/mpeg",oga:"audio/ogg",ogv:"video/ogg",ogx:"application/ogg",opus:"audio/opus",otf:"font/otf",pdf:"application/pdf",png:"image/png",rtf:"application/rtf",svg:"image/svg+xml",tif:"image/tiff",tiff:"image/tiff",ts:"video/mp2t",ttf:"font/ttf",txt:"text/plain",wasm:"application/wasm",webm:"video/webm",weba:"audio/webm",webmanifest:"application/manifest+json",webp:"image/webp",woff:"font/woff",woff2:"font/woff2",xhtml:"application/xhtml+xml",xml:"application/xml",zip:"application/zip","3gp":"video/3gpp","3g2":"video/3gpp2",gltf:"model/gltf+json",glb:"model/gltf-binary"},dr=nr,lr=(...t)=>{let e=t.filter(s=>s!=="").join("/");e=e.replace(new RegExp("(?<=\\/)\\/+","g"),"");const r=e.split("/"),o=[];for(const s of r)s===".."&&o.length>0&&o.at(-1)!==".."?o.pop():s!=="."&&o.push(s);return o.join("/")||"."},me={br:".br",zstd:".zst",gzip:".gz"},cr=Object.keys(me),pr="index.html",ur=t=>{const e=t.root??"./",r=t.path,o=t.join??lr;return async(s,a)=>{var p,g,b,w;if(s.finalized)return a();let i;if(t.path)i=t.path;else try{if(i=decodeURIComponent(s.req.path),/(?:^|[\/\\])\.\.(?:$|[\/\\])/.test(i))throw new Error}catch{return await((p=t.onNotFound)==null?void 0:p.call(t,s.req.path,s)),a()}let l=o(e,!r&&t.rewriteRequestPath?t.rewriteRequestPath(i):i);t.isDir&&await t.isDir(l)&&(l=o(l,pr));const n=t.getContent;let c=await n(l,s);if(c instanceof Response)return s.newResponse(c.body,c);if(c){const R=t.mimes&&Wt(l,t.mimes)||Wt(l);if(s.header("Content-Type",R||"application/octet-stream"),t.precompressed&&(!R||ir.test(R))){const m=new Set((g=s.req.header("Accept-Encoding"))==null?void 0:g.split(",").map(v=>v.trim()));for(const v of cr){if(!m.has(v))continue;const j=await n(l+me[v],s);if(j){c=j,s.header("Content-Encoding",v),s.header("Vary","Accept-Encoding",{append:!0});break}}}return await((b=t.onFound)==null?void 0:b.call(t,l,s)),s.body(c)}await((w=t.onNotFound)==null?void 0:w.call(t,l,s)),await a()}},gr=async(t,e)=>{let r;e&&e.manifest?typeof e.manifest=="string"?r=JSON.parse(e.manifest):r=e.manifest:typeof __STATIC_CONTENT_MANIFEST=="string"?r=JSON.parse(__STATIC_CONTENT_MANIFEST):r=__STATIC_CONTENT_MANIFEST;let o;e&&e.namespace?o=e.namespace:o=__STATIC_CONTENT;const s=r[t]||t;if(!s)return null;const a=await o.get(s,{type:"stream"});return a||null},xr=t=>async function(r,o){return ur({...t,getContent:async a=>gr(a,{manifest:t.manifest,namespace:t.namespace?t.namespace:r.env?r.env.__STATIC_CONTENT:void 0})})(r,o)},br=t=>xr(t);async function ve(t){const e=new TextEncoder().encode(t),r=await crypto.subtle.digest("SHA-256",e);return Array.from(new Uint8Array(r)).map(a=>a.toString(16).padStart(2,"0")).join("")}async function we(t,e){return e?await ve(t)===e:t==="demo123"}function hr(t,e){const r=Date.now()+144e5;return btoa(JSON.stringify({userId:t,username:e,exp:r}))}function ke(t){try{const e=JSON.parse(atob(t)),r=Date.now()+14400*1e3,o={...e,exp:r};return btoa(JSON.stringify(o))}catch{return null}}function Ee(t){try{const e=JSON.parse(atob(t));return e.exp<Date.now()?null:{userId:e.userId,username:e.username}}catch{return null}}async function Ct(t,e){const r=t.req.header("Authorization");if(!r||!r.startsWith("Bearer "))return t.json({error:"Unauthorized"},401);const o=r.substring(7),s=Ee(o);if(!s)return t.json({error:"Invalid or expired token"},401);t.set("userId",s.userId),t.set("username",s.username);const a=await t.env.DB.prepare("SELECT role FROM users WHERE id = ?").bind(s.userId).first();a&&t.set("role",a.role);const i=ke(o);i&&t.header("X-Refreshed-Token",i),await e()}async function A(t,e){const r=t.req.header("Authorization");if(r&&r.startsWith("Bearer ")){const o=r.substring(7),s=Ee(o);if(s){t.set("userId",s.userId),t.set("username",s.username);const a=await t.env.DB.prepare("SELECT role FROM users WHERE id = ?").bind(s.userId).first();a&&t.set("role",a.role);const i=ke(o);i&&t.header("X-Refreshed-Token",i)}}else t.set("username","Public");await e()}const yr=`
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="et"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>AKNAPROFF Tootmine</title>
|
|
<script src="https://cdn.tailwindcss.com"><\/script>
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
|
<style>
|
|
.checkbox-cell { cursor: pointer; transition: all 0.2s; }
|
|
.checkbox-cell:hover { opacity: 0.8; transform: scale(1.05); }
|
|
.checkbox-checked { background-color: #10b981 !important; color: white; }
|
|
.checkbox-unchecked { background-color: white; border: 2px solid #d1d5db; }
|
|
.modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); }
|
|
.modal.active { display: flex; align-items: center; justify-content: center; }
|
|
.modal-content { background-color: white; padding: 2rem; border-radius: 0.5rem; max-width: 600px; width: 90%; max-height: 90vh; overflow-y: auto; overflow-x: hidden; }
|
|
.login-container { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
|
|
.admin-only { display: none; }
|
|
.admin-only-block { display: none; }
|
|
.admin-only-inline-block { display: none; }
|
|
body.role-admin .admin-only { display: table-cell; }
|
|
body.role-admin .admin-only-block { display: block; }
|
|
body.role-admin .admin-only-inline-block { display: inline-block; }
|
|
/* Fixed padding for container on all screen sizes */
|
|
.app-container { max-width: 100%; margin-left: auto; margin-right: auto; padding-left: 1rem; padding-right: 1rem; }
|
|
/* Report modal steps */
|
|
.report-step.hidden { display: none; }
|
|
/* Print styles */
|
|
@media print {
|
|
body * { visibility: hidden; }
|
|
#printArea, #printArea * { visibility: visible; }
|
|
#printArea { position: absolute; left: 0; top: 0; width: 100%; }
|
|
@page { margin: 1cm; }
|
|
}
|
|
/* Remove body scroll */
|
|
body { overflow: hidden; height: 100vh; }
|
|
/* Sticky footer with shadow */
|
|
tfoot.sticky { box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15); }
|
|
</style>
|
|
<style>*, ::before, ::after{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/* ! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com */*,::after,::before{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}::after,::before{--tw-content:''}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.sticky{position:sticky}.bottom-0{bottom:0px}.top-0{top:0px}.z-10{z-index:10}.col-span-3{grid-column:span 3 / span 3}.mx-2{margin-left:0.5rem;margin-right:0.5rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:0.25rem}.mb-2{margin-bottom:0.5rem}.mb-3{margin-bottom:0.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:0.25rem}.ml-2{margin-left:0.5rem}.mr-1{margin-right:0.25rem}.mr-2{margin-right:0.5rem}.mr-3{margin-right:0.75rem}.mt-2{margin-top:0.5rem}.mt-4{margin-top:1rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.grid{display:grid}.hidden{display:none}.h-1{height:0.25rem}.h-10{height:2.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.max-h-96{max-height:24rem}.w-10{width:2.5rem}.w-16{width:4rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-64{width:16rem}.w-full{width:100%}.min-w-\\[150px\\]{min-width:150px}.min-w-\\[200px\\]{min-width:200px}.max-w-2xl{max-width:42rem}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2, minmax(0, 1fr))}.grid-cols-3{grid-template-columns:repeat(3, minmax(0, 1fr))}.grid-cols-4{grid-template-columns:repeat(4, minmax(0, 1fr))}.grid-cols-5{grid-template-columns:repeat(5, minmax(0, 1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-3{gap:0.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.space-x-2 > :not([hidden]) ~ :not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.5rem * var(--tw-space-x-reverse));margin-left:calc(0.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-3 > :not([hidden]) ~ :not([hidden]){--tw-space-x-reverse:0;margin-right:calc(0.75rem * var(--tw-space-x-reverse));margin-left:calc(0.75rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-4 > :not([hidden]) ~ :not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem * var(--tw-space-x-reverse));margin-left:calc(1rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-4 > :not([hidden]) ~ :not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.divide-y > :not([hidden]) ~ :not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-200 > :not([hidden]) ~ :not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235 / var(--tw-divide-opacity, 1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:0.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:0.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-l-4{border-left-width:4px}.border-t{border-top-width:1px}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219 / var(--tw-border-opacity, 1))}.border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99 / var(--tw-border-opacity, 1))}.border-red-500{--tw-border-opacity:1;border-color:rgb(239 68 68 / var(--tw-border-opacity, 1))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175 / var(--tw-border-opacity, 1))}.border-red-300{--tw-border-opacity:1;border-color:rgb(252 165 165 / var(--tw-border-opacity, 1))}.border-red-600{--tw-border-opacity:1;border-color:rgb(220 38 38 / var(--tw-border-opacity, 1))}.border-green-600{--tw-border-opacity:1;border-color:rgb(22 163 74 / var(--tw-border-opacity, 1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246 / var(--tw-bg-opacity, 1))}.bg-blue-600{--tw-bg-opacity:1;background-color:rgb(37 99 235 / var(--tw-bg-opacity, 1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.bg-gray-300{--tw-bg-opacity:1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81 / var(--tw-bg-opacity, 1))}.bg-green-100{--tw-bg-opacity:1;background-color:rgb(220 252 231 / var(--tw-bg-opacity, 1))}.bg-green-600{--tw-bg-opacity:1;background-color:rgb(22 163 74 / var(--tw-bg-opacity, 1))}.bg-green-700{--tw-bg-opacity:1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229 / var(--tw-bg-opacity, 1))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226 / var(--tw-bg-opacity, 1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity:1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity:1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.p-3{padding:0.75rem}.p-4{padding:1rem}.p-8{padding:2rem}.px-1{padding-left:0.25rem;padding-right:0.25rem}.px-2{padding-left:0.5rem;padding-right:0.5rem}.px-3{padding-left:0.75rem;padding-right:0.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:0.25rem;padding-bottom:0.25rem}.py-2{padding-top:0.5rem;padding-bottom:0.5rem}.py-3{padding-top:0.75rem;padding-bottom:0.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:0.25rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-2xl{font-size:1.5rem;line-height:2rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:0.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:0.75rem;line-height:1rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219 / var(--tw-text-opacity, 1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128 / var(--tw-text-opacity, 1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99 / var(--tw-text-opacity, 1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55 / var(--tw-text-opacity, 1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74 / var(--tw-text-opacity, 1))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61 / var(--tw-text-opacity, 1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity:1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39 / var(--tw-text-opacity, 1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.shadow-md{--tw-shadow:0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgb(0 0 0 / 0.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)}.transition{transition-property:color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter;transition-property:color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;transition-property:color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter;transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1);transition-duration:150ms}.hover\\:border-green-600:hover{--tw-border-opacity:1;border-color:rgb(22 163 74 / var(--tw-border-opacity, 1))}.hover\\:border-indigo-600:hover{--tw-border-opacity:1;border-color:rgb(79 70 229 / var(--tw-border-opacity, 1))}.hover\\:bg-blue-700:hover{--tw-bg-opacity:1;background-color:rgb(29 78 216 / var(--tw-bg-opacity, 1))}.hover\\:bg-gray-300:hover{--tw-bg-opacity:1;background-color:rgb(209 213 219 / var(--tw-bg-opacity, 1))}.hover\\:bg-gray-400:hover{--tw-bg-opacity:1;background-color:rgb(156 163 175 / var(--tw-bg-opacity, 1))}.hover\\:bg-gray-600:hover{--tw-bg-opacity:1;background-color:rgb(75 85 99 / var(--tw-bg-opacity, 1))}.hover\\:bg-green-50:hover{--tw-bg-opacity:1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.hover\\:bg-green-700:hover{--tw-bg-opacity:1;background-color:rgb(21 128 61 / var(--tw-bg-opacity, 1))}.hover\\:bg-indigo-50:hover{--tw-bg-opacity:1;background-color:rgb(238 242 255 / var(--tw-bg-opacity, 1))}.hover\\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202 / var(--tw-bg-opacity, 1))}.hover\\:bg-red-600:hover{--tw-bg-opacity:1;background-color:rgb(220 38 38 / var(--tw-bg-opacity, 1))}.hover\\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235 / var(--tw-bg-opacity, 1))}.hover\\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251 / var(--tw-bg-opacity, 1))}.hover\\:bg-red-50:hover{--tw-bg-opacity:1;background-color:rgb(254 242 242 / var(--tw-bg-opacity, 1))}.hover\\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81 / var(--tw-text-opacity, 1))}.hover\\:text-indigo-600:hover{--tw-text-opacity:1;color:rgb(79 70 229 / var(--tw-text-opacity, 1))}.hover\\:text-indigo-700:hover{--tw-text-opacity:1;color:rgb(67 56 202 / var(--tw-text-opacity, 1))}.hover\\:text-indigo-800:hover{--tw-text-opacity:1;color:rgb(55 48 163 / var(--tw-text-opacity, 1))}.hover\\:text-red-800:hover{--tw-text-opacity:1;color:rgb(153 27 27 / var(--tw-text-opacity, 1))}.hover\\:opacity-80:hover{opacity:0.8}.focus\\:border-transparent:focus{border-color:transparent}.focus\\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.focus\\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)}.focus\\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241 / var(--tw-ring-opacity, 1))}.focus\\:ring-red-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(239 68 68 / var(--tw-ring-opacity, 1))}.group:hover .group-hover\\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}</style><script type="text/javascript" data-name="TokenSigning" data-by="Web-eID extension" src="chrome-extension://ncibgoaomkmdpilpocfeponihegamlic/token-signing-page-script.js"><\/script></head>
|
|
<body class="bg-gray-100">
|
|
<!-- Login Modal (for admin) -->
|
|
<div id="loginModal" class="modal">
|
|
<div class="modal-content max-w-md">
|
|
<div class="text-center mb-6">
|
|
<i class="fas fa-lock text-4xl text-indigo-600 mb-3"></i>
|
|
<h2 class="text-2xl font-bold text-gray-800">Login</h2>
|
|
<p class="text-gray-600 mt-2">Sisesta kasutajaandmed</p>
|
|
</div>
|
|
<form id="loginForm" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kasutajanimi</label>
|
|
<input type="text" id="username" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Parool</label>
|
|
<input type="password" id="password" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required="">
|
|
</div>
|
|
<div class="flex space-x-3">
|
|
<button type="button" onclick="continueAsGuest()" class="flex-1 bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
<i class="fas fa-eye mr-2"></i>Vaata ainult
|
|
</button>
|
|
<button type="submit" class="flex-1 bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-sign-in-alt mr-2"></i>Logi sisse
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div id="loginError" class="mt-4 p-3 bg-red-100 text-red-700 rounded-lg hidden"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main App (visible by default as public user view) -->
|
|
<div id="mainApp">
|
|
<!-- Header -->
|
|
<div class="bg-white shadow-md mb-6">
|
|
<div class="app-container py-4">
|
|
<div class="flex justify-between items-center">
|
|
<!-- Left: Title -->
|
|
<h1 class="text-2xl font-bold text-gray-800">
|
|
<i class="fas fa-industry mr-2 text-indigo-600"></i>
|
|
AKNAPROFF Tootmine
|
|
</h1>
|
|
|
|
<!-- Right: User info, admin buttons, and logout/login -->
|
|
<div class="flex items-center space-x-4">
|
|
<!-- Show user info and admin buttons for logged in users -->
|
|
<span id="userInfo" class="text-gray-600 hidden">
|
|
<i class="fas fa-user mr-2"></i>
|
|
<span id="userName"></span>
|
|
</span>
|
|
|
|
<!-- Session timer (shown only in last minute) -->
|
|
<span id="sessionTimer" class="hidden text-red-600 font-semibold animate-pulse">
|
|
<i class="fas fa-clock mr-1"></i>
|
|
<span id="sessionTimeLeft"></span>
|
|
</span>
|
|
|
|
<!-- Admin buttons (Report & Settings) -->
|
|
<button id="reportBtn" onclick="openReportModal()" class="admin-only-inline-block text-indigo-600 hover:text-indigo-700 transition mx-2" title="Aruanne">
|
|
<i class="fas fa-chart-bar text-xl"></i>
|
|
</button>
|
|
<button id="settingsBtn" onclick="openSettingsModal()" class="admin-only-inline-block text-gray-600 hover:text-indigo-600 transition mx-2 hidden" title="Seaded">
|
|
<i class="fas fa-cog text-xl"></i>
|
|
</button>
|
|
|
|
<button id="logoutBtn" onclick="logout()" class="hidden bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600 transition">
|
|
<i class="fas fa-sign-out-alt mr-2"></i>Välja
|
|
</button>
|
|
<!-- Show login button for public users -->
|
|
<button id="loginBtn" onclick="openLoginModal()" class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-sign-in-alt mr-2"></i>Admin Login
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="app-container">
|
|
<!-- Filters -->
|
|
<div class="bg-white rounded shadow-sm p-3 mb-3">
|
|
<div class="flex flex-wrap gap-3 items-end">
|
|
<div class="flex-1 min-w-[150px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-calendar mr-1 text-xs"></i>Kuu
|
|
</label>
|
|
<select id="monthFilter" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500">
|
|
<option value="1">Jaanuar</option>
|
|
<option value="2">Veebruar</option>
|
|
<option value="3">Märts</option>
|
|
<option value="4">Aprill</option>
|
|
<option value="5">Mai</option>
|
|
<option value="6">Juuni</option>
|
|
<option value="7">Juuli</option>
|
|
<option value="8">August</option>
|
|
<option value="9">September</option>
|
|
<option value="10">Oktoober</option>
|
|
<option value="11">November</option>
|
|
<option value="12">Detsember</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex-1 min-w-[150px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-calendar-alt mr-1 text-xs"></i>Aasta
|
|
</label>
|
|
<select id="yearFilter" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500"><option value="2025">2025</option><option value="2026">2026</option></select>
|
|
</div>
|
|
<div id="addNewRowBtn">
|
|
<button onclick="openModal()" class="bg-indigo-600 text-white px-4 py-1 text-sm rounded hover:bg-indigo-700 transition">
|
|
<i class="fas fa-plus mr-1 text-xs"></i>Lisa uus rida
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Search -->
|
|
<div class="bg-white rounded shadow-sm p-3 mb-3">
|
|
<h3 class="text-sm font-semibold text-gray-800 mb-2">
|
|
<i class="fas fa-search mr-1 text-indigo-600 text-xs"></i>Kiir otsing
|
|
</h3>
|
|
<div class="flex flex-wrap gap-3 items-end">
|
|
<!-- Sort by ID button -->
|
|
<div class="flex items-end">
|
|
<button id="sortByIdBtn" onclick="toggleSortById()" class="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50 transition flex items-center justify-between min-w-[100px]">
|
|
<span>ID</span>
|
|
<i id="sortByIdIcon" class="fas fa-sort text-gray-400"></i>
|
|
</button>
|
|
</div>
|
|
<div class="flex-1 min-w-[200px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-user mr-1 text-xs"></i>Klient
|
|
</label>
|
|
<input type="text" id="searchClient" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500" placeholder="Otsi kliendi järgi...">
|
|
</div>
|
|
<div class="flex-1 min-w-[150px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-tag mr-1 text-xs"></i>Tüüp
|
|
</label>
|
|
<input type="text" id="searchType" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500" placeholder="Otsi tüübi järgi...">
|
|
</div>
|
|
<div class="flex-1 min-w-[150px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-file-invoice mr-1 text-xs"></i>Pakkum. Nr
|
|
</label>
|
|
<input type="text" id="searchOffer" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500" placeholder="Otsi pakkumise nr järgi...">
|
|
</div>
|
|
<div class="flex-1 min-w-[150px]">
|
|
<label class="block text-xs font-medium text-gray-700 mb-1">
|
|
<i class="fas fa-briefcase mr-1 text-xs"></i>Töö Nr
|
|
</label>
|
|
<input type="text" id="searchWork" class="w-full px-2 py-1 text-sm border border-gray-300 rounded focus:ring-1 focus:ring-indigo-500" placeholder="Otsi töö nr järgi...">
|
|
</div>
|
|
<div>
|
|
<label class="flex items-center space-x-2 cursor-pointer pb-1">
|
|
<input type="checkbox" id="searchByYear" class="w-4 h-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
|
|
<span class="text-xs font-medium text-gray-700 whitespace-nowrap">
|
|
<i class="fas fa-calendar-alt mr-1 text-xs"></i>Aasta järgi
|
|
</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<button onclick="clearAllFilters()" class="px-3 py-1 text-xs bg-gray-200 text-gray-700 rounded hover:bg-gray-300 transition whitespace-nowrap" title="Tühjenda kõik filtrid">
|
|
<i class="fas fa-times-circle mr-1 text-xs"></i>Tühjenda filtrid
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="bg-white rounded shadow-sm overflow-hidden">
|
|
<div class="overflow-x-auto overflow-y-auto" style="max-height: calc(100vh - 320px);">
|
|
<table class="w-full text-xs">
|
|
<thead class="bg-gray-700 sticky top-0 z-10">
|
|
<tr>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('client')">
|
|
<div class="flex items-center justify-between">
|
|
<span>Klient</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-client"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('type')">
|
|
<div class="flex items-center justify-center">
|
|
<span>Tüüp</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-type"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('offer')">
|
|
<div class="flex items-center justify-between">
|
|
<span>Pakkum. Nr</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-offer"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('work')">
|
|
<div class="flex items-center justify-between">
|
|
<span>Töö Nr</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-work"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Kogus</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Värv</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('material')">
|
|
<div class="flex items-center justify-center">
|
|
<span>MAT-1</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-material"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">MAT-2</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase cursor-pointer hover:bg-gray-600 transition" onclick="sortRecords('package')">
|
|
<div class="flex items-center justify-center">
|
|
<span>PAKETT</span>
|
|
<i class="fas fa-sort text-gray-300 ml-1 text-xs" id="sort-icon-package"></i>
|
|
</div>
|
|
</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Töölehti</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">LÕIKUS</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">KLAAS</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">VALMIS</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">VÄLJAS</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Märkused</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Probleemid</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Paigaldaja</th>
|
|
<th class="admin-only px-2 py-2 text-right text-xs font-medium text-gray-200 uppercase">Hind (€)</th>
|
|
<th class="admin-only px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Tegevused</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="recordsTable" class="divide-y divide-gray-200">
|
|
<tr class="hover:bg-gray-50 transition">
|
|
<td class="px-2 py-1 text-xs text-gray-900">AS Okna Service</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-600">-</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">P-2025-001</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">T-2025-001</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-900 font-medium">12</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600" title="">-</td>
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
08.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
11.11.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
09.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 border border-green-600 text-white text-xs font-semibold cursor-pointer hover:opacity-80 transition" onclick="toggleWorksheetsStep(1)" title="Klõps 3: Tühjenda">
|
|
26.11.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(1, 'cutting', '2025-01-10')">
|
|
10.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(1, 'glazing', '2025-01-12')">
|
|
12.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(1, 'ready', '2025-01-14')">
|
|
14.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(1, 'issued', '2025-01-15')">
|
|
15.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openNotesModal(1, "")">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openProblemsModal(1, "", {"worksheets_error":0,"cutting_error":0,"glazing_error":0,"ready_error":0,"issued_error":0})">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-2 py-1 text-xs text-gray-600">Jüri Tamm</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-sm text-right">
|
|
<div class="inline-block px-2 py-1 rounded text-gray-900 font-medium" title="Arve puudub">
|
|
2500.00
|
|
</div>
|
|
</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-center">
|
|
<button onclick="editRecord(1)" class="text-indigo-600 hover:text-indigo-800 transition mr-2">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="confirmDelete(1)" class="delete-btn text-red-600 hover:text-red-800 transition" style="display: inline-block;">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="hover:bg-gray-50 transition">
|
|
<td class="px-2 py-1 text-xs text-gray-900">OÜ Aken ja Uks</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-600">-</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">P-2025-002</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">T-2025-002</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-900 font-medium">8</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600" title="">-</td>
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
07.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
07.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-red-100 border border-red-300 text-red-800 text-xs cursor-pointer hover:opacity-80 transition" onclick="toggleWorksheetsStep(2)" title="qqqq">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(2, 'cutting', '2025-01-08')">
|
|
08.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-red-100 border border-red-300 text-red-800 text-xs font-semibold cursor-pointer hover:bg-red-50 transition" title="qqqq" onclick="toggleDate(2, 'glazing', '2025-01-10')">
|
|
10.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-300 text-gray-600 border border-gray-400 cursor-pointer hover:bg-gray-400 text-xs" title="qqqq" onclick="openBlockedFieldModal("qqqq")">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-300 text-gray-600 border border-gray-400 cursor-pointer hover:bg-gray-400 text-xs" title="qqqq" onclick="openBlockedFieldModal("qqqq")">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openNotesModal(2, "Срочный заказ до 20.01")">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-red-500 text-white border-2 border-red-600 text-xs font-semibold cursor-pointer hover:bg-red-600 transition" title="qqqq" onclick="openProblemsModal(2, "qqqq", {"worksheets_error":1,"cutting_error":0,"glazing_error":1,"ready_error":0,"issued_error":0})">
|
|
27.11.2025
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-2 py-1 text-xs text-gray-600">Mari Kask</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-sm text-right">
|
|
<div class="inline-block px-2 py-1 rounded text-gray-900 font-medium" title="Arve puudub">
|
|
1800.00
|
|
</div>
|
|
</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-center">
|
|
<button onclick="editRecord(2)" class="text-indigo-600 hover:text-indigo-800 transition mr-2">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="confirmDelete(2)" class="delete-btn text-red-600 hover:text-red-800 transition" style="display: inline-block;">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="hover:bg-gray-50 transition">
|
|
<td class="px-2 py-1 text-xs text-gray-900">Koduleht OÜ</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-600">-</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">P-2025-003</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">T-2025-003</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-900 font-medium">15</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600" title="">-</td>
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
10.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-red-100 border border-red-300 text-red-800 text-xs cursor-pointer hover:opacity-80 transition" onclick="toggleWorksheetsStep(3)" title="Klõps 1: Lisa kuupäev">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-green-500 text-white text-xs font-semibold cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(3, 'cutting', '2025-01-11')">
|
|
11.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(3, 'glazing', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-300 text-gray-600 border border-gray-400 cursor-pointer hover:bg-gray-400 text-xs" title="Klõpsake probleemi vaatamiseks" onclick="openBlockedFieldModal("")">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-300 text-gray-600 border border-gray-400 cursor-pointer hover:bg-gray-400 text-xs" title="Klõpsake probleemi vaatamiseks" onclick="openBlockedFieldModal("")">
|
|
<i class="fas fa-lock"></i>
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openNotesModal(3, "")">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-red-500 text-white border-2 border-red-600 text-xs font-semibold cursor-pointer hover:bg-red-600 transition" title="" onclick="openProblemsModal(3, "", {"worksheets_error":1,"cutting_error":0,"glazing_error":0,"ready_error":0,"issued_error":0})">
|
|
26.11.2025
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-2 py-1 text-xs text-gray-600">Peeter Sepp</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-sm text-right">
|
|
<div class="inline-block px-2 py-1 rounded text-gray-900 font-medium" title="Arve puudub">
|
|
3200.00
|
|
</div>
|
|
</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-center">
|
|
<button onclick="editRecord(3)" class="text-indigo-600 hover:text-indigo-800 transition mr-2">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="confirmDelete(3)" class="delete-btn text-red-600 hover:text-red-800 transition" style="display: inline-block;">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="hover:bg-gray-50 transition">
|
|
<td class="px-2 py-1 text-xs text-gray-900">Test Client AS</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-600">-</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">P-2025-004</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">T-2025-004</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-900 font-medium">5</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600" title="">-</td>
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs font-semibold">
|
|
05.01.2025
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-400 text-xs cursor-pointer hover:opacity-80 transition" onclick="toggleWorksheetsStep(4)" title="Klõps 1: Lisa kuupäev">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(4, 'cutting', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(4, 'glazing', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(4, 'ready', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(4, 'issued', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openNotesModal(4, "Ждём подтверждения")">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openProblemsModal(4, "", {"worksheets_error":0,"cutting_error":0,"glazing_error":0,"ready_error":0,"issued_error":0})">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-2 py-1 text-xs text-gray-600">Peeter Sepp</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-sm text-right">
|
|
<div class="inline-block px-2 py-1 rounded text-gray-900 font-medium" title="Arve puudub">
|
|
1000.00
|
|
</div>
|
|
</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-center">
|
|
<button onclick="editRecord(4)" class="text-indigo-600 hover:text-indigo-800 transition mr-2">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="confirmDelete(4)" class="delete-btn text-red-600 hover:text-red-800 transition" style="display: inline-block;">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
|
|
<tr class="hover:bg-gray-50 transition">
|
|
<td class="px-2 py-1 text-xs text-gray-900">Demo Company</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-600">-</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">P-2025-005</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600">T-2025-005</td>
|
|
<td class="px-2 py-1 text-xs text-center text-gray-900 font-medium">20</td>
|
|
<td class="px-2 py-1 text-xs text-gray-600" title="">-</td>
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-400 text-xs cursor-pointer hover:opacity-80 transition" onclick="toggleWorksheetsStep(5)" title="Klõps 1: Lisa kuupäev">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(5, 'cutting', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(5, 'glazing', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(5, 'ready', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-white border border-gray-300 text-gray-900 text-xs cursor-pointer hover:bg-red-50 transition" onclick="toggleDate(5, 'issued', null)">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openNotesModal(5, "")">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
|
|
<td class="px-2 py-1 text-center">
|
|
<div class="inline-block px-2 py-1 rounded bg-gray-100 border border-gray-300 text-gray-400 text-xs cursor-pointer hover:bg-gray-200 transition" onclick="openProblemsModal(5, "", {"worksheets_error":0,"cutting_error":0,"glazing_error":0,"ready_error":0,"issued_error":0})">
|
|
-
|
|
</div>
|
|
</td>
|
|
|
|
<td class="px-2 py-1 text-xs text-gray-600">Jüri Tamm</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-sm text-right">
|
|
<div class="inline-block px-2 py-1 rounded text-gray-900 font-medium" title="Arve puudub">
|
|
4500.00
|
|
</div>
|
|
</td>
|
|
|
|
<td class="admin-only px-2 py-1 text-center">
|
|
<button onclick="editRecord(5)" class="text-indigo-600 hover:text-indigo-800 transition mr-2">
|
|
<i class="fas fa-edit"></i>
|
|
</button>
|
|
<button onclick="confirmDelete(5)" class="delete-btn text-red-600 hover:text-red-800 transition" style="display: inline-block;">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
<tfoot id="recordsTableFooter" class="bg-gray-700 border-t border-gray-600 sticky bottom-0 z-10">
|
|
<tr class="font-semibold">
|
|
<td class="px-2 py-1 text-xs text-gray-200" colspan="4">
|
|
<i class="fas fa-calculator mr-1 text-xs"></i>Summa:
|
|
</td>
|
|
<td class="px-2 py-1 text-xs text-center text-white bg-indigo-600 font-bold">
|
|
60
|
|
</td>
|
|
<td colspan="12"></td>
|
|
<td class="admin-only px-2 py-1 text-xs text-right text-white bg-indigo-600 font-bold">
|
|
13000.00
|
|
</td>
|
|
<td class="admin-only"></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal -->
|
|
<div id="recordModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800" id="modalTitle">Lisa uus kirje</h2>
|
|
<button onclick="closeModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
<form id="recordForm">
|
|
<input type="hidden" id="recordId">
|
|
<div class="space-y-4">
|
|
<div class="grid grid-cols-4 gap-4">
|
|
<div class="col-span-3">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Klient *</label>
|
|
<input type="text" id="clientName" class="w-full px-4 py-2 border border-gray-300 rounded-lg" required="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Tüüp</label>
|
|
<input type="text" id="type" class="w-full px-4 py-2 border border-gray-300 rounded-lg" maxlength="3">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Pakkum. Nr *</label>
|
|
<input type="text" id="offerNumber" class="w-full px-4 py-2 border border-gray-300 rounded-lg" required="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Töö Nr *</label>
|
|
<input type="text" id="workNumber" class="w-full px-4 py-2 border border-gray-300 rounded-lg" required="">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kogus</label>
|
|
<input type="number" id="quantity" class="w-full px-4 py-2 border border-gray-300 rounded-lg" min="0">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Värv</label>
|
|
<input type="text" id="color" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Märkused</label>
|
|
<textarea id="notes" class="w-full px-4 py-2 border border-gray-300 rounded-lg" rows="3"></textarea>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Paigaldaja</label>
|
|
<input type="text" id="installer" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Hind (€)</label>
|
|
<input type="number" id="price" class="w-full px-4 py-2 border border-gray-300 rounded-lg" step="0.01" min="0">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Arve Nr</label>
|
|
<input type="text" id="arveMakstud" class="w-full px-4 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
<div class="flex items-center justify-center" style="padding-top: 28px;">
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="arveChecked" class="w-5 h-5 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500 cursor-pointer">
|
|
<span class="text-sm font-medium text-gray-700">Arve makstud</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<!-- Date fields (MAT-1, MAT-2 and PAK only) -->
|
|
<div class="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">MATERJAL-1</label>
|
|
<input type="date" id="materialDate" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">MATERJAL-2</label>
|
|
<input type="date" id="material2Date" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">PAKETT</label>
|
|
<input type="date" id="packageDate" class="w-full px-3 py-2 border border-gray-300 rounded-lg">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end space-x-3 mt-6">
|
|
<button type="button" onclick="closeModal()" class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
Tühista
|
|
</button>
|
|
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-save mr-2"></i>Salvesta
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes Modal -->
|
|
<div id="notesModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">Märkused</h2>
|
|
<button onclick="closeNotesModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
<form id="notesForm" onsubmit="saveNotes(event)">
|
|
<input type="hidden" id="notesRecordId">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Märkused</label>
|
|
<textarea id="notesText" class="w-full px-4 py-2 border border-gray-300 rounded-lg" rows="8" placeholder="Sisesta märkused..."></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end space-x-3 mt-6">
|
|
<button type="button" onclick="closeNotesModal()" class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
Tühista
|
|
</button>
|
|
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-save mr-2"></i>Salvesta
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Problems Modal -->
|
|
<div id="problemsModal" class="modal">
|
|
<div class="modal-content">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">Probleemid</h2>
|
|
<button onclick="closeProblemsModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
<form id="problemsForm" onsubmit="saveProblems(event)">
|
|
<input type="hidden" id="problemsRecordId" value="">
|
|
<div class="space-y-4">
|
|
<div>
|
|
<textarea id="problemsText" class="w-full px-4 py-2 border border-gray-300 rounded-lg" rows="8" placeholder="Sisesta probleemid..."></textarea>
|
|
<p class="mt-2 text-sm text-gray-600">
|
|
<i class="fas fa-info-circle mr-1"></i>
|
|
Kui see väli on täidetud või vigade märked on seatud, siis VALMIS ja VÄLJAS väljad on blokeeritud
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Märgi vead (esiletoodud punasega tabelis):</label>
|
|
<div class="grid grid-cols-5 gap-4">
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="errorWorksheets" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">Töölehti</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="errorCutting" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">Lõikus</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="errorGlazing" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">Klaas</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="errorReady" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">Valmis</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="errorIssued" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500">
|
|
<span class="text-sm text-gray-700">Väljas</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-end space-x-3 mt-6">
|
|
<button type="button" onclick="closeProblemsModal()" class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
Tühista
|
|
</button>
|
|
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-save mr-2"></i>Salvesta
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Blocked Field Alert Modal -->
|
|
<div id="blockedFieldModal" class="modal">
|
|
<div class="modal-content max-w-md">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-red-600">
|
|
<i class="fas fa-exclamation-triangle mr-2"></i>
|
|
Väli blokeeritud
|
|
</h2>
|
|
<button onclick="closeBlockedFieldModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
<div class="space-y-4">
|
|
<div class="bg-red-50 border-l-4 border-red-500 p-4 rounded">
|
|
<p class="text-sm text-gray-700 mb-2 font-semibold">
|
|
<i class="fas fa-lock mr-2"></i>
|
|
See väli on blokeeritud probleemi tõttu:
|
|
</p>
|
|
<p id="blockedFieldMessage" class="text-sm text-gray-800 whitespace-pre-wrap"></p>
|
|
</div>
|
|
<p class="text-xs text-gray-600">
|
|
<i class="fas fa-info-circle mr-1"></i>
|
|
Lahendage probleem Probleemid veerus, et väljad vabastada
|
|
</p>
|
|
</div>
|
|
<div class="flex justify-end mt-6">
|
|
<button onclick="closeBlockedFieldModal()" class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
Sulge
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Modal (for admin) -->
|
|
<div id="settingsModal" class="modal">
|
|
<div class="modal-content max-w-md">
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">
|
|
<i class="fas fa-cog mr-2 text-indigo-600"></i>
|
|
Seaded
|
|
</h2>
|
|
<button onclick="closeSettingsModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
<form id="settingsForm" class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kasutajanimi</label>
|
|
<input type="text" id="settingsUsername" class="w-full px-4 py-2 border border-gray-300 rounded-lg bg-gray-100 text-gray-600" readonly="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Nimi (Allkiri)</label>
|
|
<input type="text" id="settingsFullName" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Praegune parool</label>
|
|
<input type="password" id="settingsCurrentPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" required="">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Uus parool (jäta tühjaks, kui ei muuda)</label>
|
|
<input type="password" id="settingsNewPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kinnita uus parool</label>
|
|
<input type="password" id="settingsConfirmPassword" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent">
|
|
</div>
|
|
<div class="pt-4 border-t">
|
|
<label class="flex items-center space-x-2 cursor-pointer">
|
|
<input type="checkbox" id="allowDeleteCheckbox" class="w-4 h-4 text-red-600 border-gray-300 rounded focus:ring-red-500 cursor-pointer">
|
|
<span class="text-sm font-medium text-gray-700">Luba kustutamine (kuva prügikasti nupp)</span>
|
|
</label>
|
|
</div>
|
|
<div class="flex space-x-3 pt-4">
|
|
<button type="button" onclick="closeSettingsModal()" class="flex-1 bg-gray-300 text-gray-700 px-6 py-2 rounded-lg hover:bg-gray-400 transition">
|
|
Tühista
|
|
</button>
|
|
<button type="submit" class="flex-1 bg-indigo-600 text-white px-6 py-2 rounded-lg hover:bg-indigo-700 transition">
|
|
<i class="fas fa-save mr-2"></i>Salvesta
|
|
</button>
|
|
</div>
|
|
</form>
|
|
<div id="settingsError" class="mt-4 p-3 bg-red-100 text-red-700 rounded-lg hidden"></div>
|
|
<div id="settingsSuccess" class="mt-4 p-3 bg-green-100 text-green-700 rounded-lg hidden"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Report Modal (3 steps) -->
|
|
<div id="reportModal" class="modal">
|
|
<div class="modal-content" style="max-width: 700px; width: 95%;">
|
|
|
|
<div class="flex justify-between items-center mb-6">
|
|
<h2 class="text-2xl font-bold text-gray-800">
|
|
<i class="fas fa-chart-bar mr-2 text-indigo-600"></i>
|
|
Aruanne
|
|
</h2>
|
|
<button onclick="closeReportModal()" class="text-gray-500 hover:text-gray-700">
|
|
<i class="fas fa-times text-2xl"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Step indicator -->
|
|
<div class="flex items-center justify-center mb-8">
|
|
<div class="flex items-center">
|
|
<div id="step0-indicator" class="flex items-center">
|
|
<div class="w-10 h-10 rounded-full bg-indigo-600 text-white flex items-center justify-center font-bold">1</div>
|
|
<span class="ml-2 text-sm font-medium text-indigo-600">Vali tüüp</span>
|
|
</div>
|
|
<div class="w-16 h-1 bg-gray-300 mx-4"></div>
|
|
<div id="step1-indicator" class="flex items-center">
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 text-gray-600 flex items-center justify-center font-bold">2</div>
|
|
<span class="ml-2 text-sm font-medium text-gray-600">Periood</span>
|
|
</div>
|
|
<div class="w-16 h-1 bg-gray-300 mx-4"></div>
|
|
<div id="step2-indicator" class="flex items-center">
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 text-gray-600 flex items-center justify-center font-bold">3</div>
|
|
<span class="ml-2 text-sm font-medium text-gray-600">Statistika</span>
|
|
</div>
|
|
<div class="w-16 h-1 bg-gray-300 mx-4"></div>
|
|
<div id="step3-indicator" class="flex items-center">
|
|
<div class="w-10 h-10 rounded-full bg-gray-300 text-gray-600 flex items-center justify-center font-bold">4</div>
|
|
<span class="ml-2 text-sm font-medium text-gray-600">Lae alla</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 0: Report Type Selection -->
|
|
<div id="reportStep0" class="report-step">
|
|
<div class="text-center py-8">
|
|
<h3 class="text-xl font-semibold text-gray-800 mb-8">Vali aruande tüüp</h3>
|
|
<div class="grid grid-cols-2 gap-6 max-w-2xl mx-auto">
|
|
<!-- Master Report Button -->
|
|
<button onclick="selectReportType('master')" class="flex flex-col items-center justify-center p-8 border-2 border-gray-300 rounded-lg hover:border-indigo-600 hover:bg-indigo-50 transition group" title="Meistri aruanne: aasta statistika akende kohta">
|
|
<i class="fas fa-tools text-5xl text-indigo-600 mb-4 group-hover:scale-110 transition"></i>
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-2">Meistri aruanne</h4>
|
|
<p class="text-xs text-gray-600 text-center">Aasta statistika<br>koguste ja summade kohta</p>
|
|
</button>
|
|
|
|
<!-- Accountant Report Button -->
|
|
<button onclick="selectReportType('accountant')" class="flex flex-col items-center justify-center p-8 border-2 border-gray-300 rounded-lg hover:border-green-600 hover:bg-green-50 transition group" title="Raamatupidaja aruanne: detailne klientide ja arvete loetelu">
|
|
<i class="fas fa-file-invoice-dollar text-5xl text-green-600 mb-4 group-hover:scale-110 transition"></i>
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-2">Raamatupidaja aruanne</h4>
|
|
<p class="text-xs text-gray-600 text-center">Detailne loetelu<br>klientide ja arvete kohta</p>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 1: Period Selection (Year for Master, Month+Year for Accountant) -->
|
|
<div id="reportStep1" class="report-step hidden">
|
|
<div class="text-center py-8">
|
|
<i class="fas fa-calendar-alt text-6xl text-indigo-600 mb-6"></i>
|
|
<h3 id="reportStep1Title" class="text-xl font-semibold text-gray-800 mb-6">Vali periood</h3>
|
|
|
|
<!-- Month selector (only for accountant) -->
|
|
<div id="reportMonthSelector" class="mb-4 hidden">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Kuu</label>
|
|
<select id="reportMonth" class="w-64 px-4 py-3 text-lg border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent mx-auto">
|
|
<option value="1">Jaanuar</option>
|
|
<option value="2">Veebruar</option>
|
|
<option value="3">Märts</option>
|
|
<option value="4">Aprill</option>
|
|
<option value="5">Mai</option>
|
|
<option value="6">Juuni</option>
|
|
<option value="7">Juuli</option>
|
|
<option value="8">August</option>
|
|
<option value="9">September</option>
|
|
<option value="10">Oktoober</option>
|
|
<option value="11">November</option>
|
|
<option value="12">Detsember</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Year selector (for both) -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Aasta</label>
|
|
<select id="reportYear" class="w-64 px-4 py-3 text-lg border-2 border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent mx-auto"><option value="2025">2025</option><option value="2026">2026</option></select>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-between mt-8">
|
|
<button onclick="goBackToReportStep0()" class="bg-gray-300 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-400 transition">
|
|
<i class="fas fa-arrow-left mr-2"></i>Tagasi
|
|
</button>
|
|
<button onclick="goToReportStep2()" class="bg-indigo-600 text-white px-8 py-3 rounded-lg hover:bg-indigo-700 transition text-lg font-semibold">
|
|
Järgmine <i class="fas fa-arrow-right ml-2"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Statistics Table (Master or Accountant) -->
|
|
<div id="reportStep2" class="report-step hidden">
|
|
<!-- Master Report Table -->
|
|
<div id="masterReportTable">
|
|
<table class="w-full text-xs">
|
|
<thead class="bg-gray-700">
|
|
<tr>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Kuu</th>
|
|
<th class="px-1 py-2 text-center text-xs font-medium text-gray-200 uppercase">Tööp.</th>
|
|
<th class="px-1 py-2 text-center text-xs font-medium text-gray-200 uppercase">Kogus</th>
|
|
<th class="px-1 py-2 text-right text-xs font-medium text-gray-200 uppercase">Summa</th>
|
|
<th class="px-1 py-2 text-center text-xs font-medium text-gray-200 uppercase">Kesk/p</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="reportTableBody" class="bg-white divide-y divide-gray-200">
|
|
<!-- Will be populated by JavaScript -->
|
|
</tbody>
|
|
<tfoot class="bg-gray-700">
|
|
<tr class="font-bold">
|
|
<td class="px-2 py-1 text-xs text-gray-200">KOKKU:</td>
|
|
<td class="px-1 py-1 text-center text-xs text-white bg-indigo-600" id="totalWorkDays">-</td>
|
|
<td class="px-1 py-1 text-center text-xs text-white bg-indigo-600" id="totalWindows">0</td>
|
|
<td class="px-1 py-1 text-right text-xs text-white bg-indigo-600" id="totalPrice">0.00</td>
|
|
<td class="px-1 py-1 text-center text-xs text-white bg-indigo-600" id="avgPerDay">-</td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Accountant Report Table -->
|
|
<div id="accountantReportTable" class="hidden">
|
|
<div class="overflow-x-auto max-h-96 overflow-y-auto">
|
|
<table class="w-full text-xs">
|
|
<thead class="bg-green-700 sticky top-0">
|
|
<tr>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Klient</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Pakkum. Nr</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Töö Nr</th>
|
|
<th class="px-2 py-2 text-center text-xs font-medium text-gray-200 uppercase">Kogus</th>
|
|
<th class="px-2 py-2 text-right text-xs font-medium text-gray-200 uppercase">Hind (€)</th>
|
|
<th class="px-2 py-2 text-left text-xs font-medium text-gray-200 uppercase">Arve Nr</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="accountantReportTableBody" class="bg-white divide-y divide-gray-200">
|
|
<!-- Will be populated by JavaScript -->
|
|
</tbody>
|
|
<tfoot class="bg-green-700 sticky bottom-0">
|
|
<tr class="font-bold">
|
|
<td class="px-2 py-1 text-xs text-gray-200" colspan="3">KOKKU:</td>
|
|
<td class="px-2 py-1 text-center text-xs text-white bg-green-600" id="accTotalQuantity">0</td>
|
|
<td class="px-2 py-1 text-right text-xs text-white bg-green-600" id="accTotalPrice">0.00</td>
|
|
<td class="px-2 py-1 text-xs text-gray-200"></td>
|
|
</tr>
|
|
</tfoot>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-between mt-8">
|
|
<button onclick="goToReportStep1()" class="bg-gray-300 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-400 transition">
|
|
<i class="fas fa-arrow-left mr-2"></i>Tagasi
|
|
</button>
|
|
<button onclick="goToReportStep3()" class="bg-indigo-600 text-white px-8 py-3 rounded-lg hover:bg-indigo-700 transition text-lg font-semibold">
|
|
Genereeri aruanne <i class="fas fa-arrow-right ml-2"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Generate CSV -->
|
|
<div id="reportStep3" class="report-step hidden">
|
|
<div class="text-center py-8">
|
|
<i class="fas fa-file-csv text-6xl text-green-600 mb-6"></i>
|
|
<h3 class="text-xl font-semibold text-gray-800 mb-4">Aruanne on valmis!</h3>
|
|
<p class="text-gray-600 mb-8">CSV fail on genereeritud ja valmis allalaadimiseks või printimiseks</p>
|
|
<div class="flex justify-center gap-4">
|
|
<button onclick="downloadCSV()" class="bg-green-600 text-white px-8 py-4 rounded-lg hover:bg-green-700 transition text-lg font-semibold inline-flex items-center">
|
|
<i class="fas fa-download mr-3"></i>
|
|
Lae alla CSV
|
|
</button>
|
|
<button onclick="printReport()" class="bg-blue-600 text-white px-8 py-4 rounded-lg hover:bg-blue-700 transition text-lg font-semibold inline-flex items-center">
|
|
<i class="fas fa-print mr-3"></i>
|
|
Prindi nüüd
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-between mt-8">
|
|
<button onclick="goToReportStep2()" class="bg-gray-300 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-400 transition">
|
|
<i class="fas fa-arrow-left mr-2"></i>Tagasi
|
|
</button>
|
|
<button onclick="closeReportModal()" class="bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700 transition">
|
|
Sulge
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"><\/script>
|
|
<script src="/static/app.js?v=4.1.9"><\/script>
|
|
|
|
|
|
</body></html>`,f=new fe;f.use("/api/*",ar());f.use("/static/*",br({root:"./public"}));f.get("/favicon.ico",t=>new Response(null,{status:204}));f.post("/api/auth/login",async t=>{try{const{username:e,password:r}=await t.req.json(),o=await t.env.DB.prepare("SELECT id, username, password_hash, full_name, role FROM users WHERE username = ? AND deleted_at IS NULL").bind(e).first();if(!o||!await we(r,o.password_hash))return t.json({error:"Invalid credentials"},401);const s=hr(o.id,o.username);return t.json({success:!0,token:s,user:{username:o.username,fullName:o.full_name,role:o.role}})}catch(e){return console.error("Login error:",e),t.json({error:"Login failed"},500)}});f.patch("/api/users/profile",Ct,async t=>{try{const e=await t.req.json(),r=e.full_name||e.fullName,o=e.current_password||e.currentPassword,s=e.new_password||e.newPassword,a=t.get("userId");console.log("[PROFILE UPDATE]",{userId:a,fullName:r,hasCurrentPwd:!!o,hasNewPwd:!!s});const i=await t.env.DB.prepare("SELECT password_hash, full_name FROM users WHERE id = ?").bind(a).first();if(!i)return t.json({error:"Kasutajat ei leitud"},404);if(s){if(!o)return t.json({error:"Praegune parool on kohustuslik parooli muutmiseks"},400);if(!await we(o,i.password_hash))return t.json({error:"Vale praegune parool"},400);const l=await ve(s);await t.env.DB.prepare("UPDATE users SET password_hash = ?, full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").bind(l,r,a).run()}else await t.env.DB.prepare("UPDATE users SET full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").bind(r,a).run();return t.json({success:!0,message:"Profiil uuendatud",user:{full_name:r}})}catch(e){return console.error("Profile update error:",e),t.json({error:"Profiili uuendamine ebaõnnestus"},500)}});f.get("/api/years",A,async t=>{try{const e=await t.env.DB.prepare("SELECT MIN(year) as min_year FROM production_records WHERE deleted_at IS NULL").first(),r=(e==null?void 0:e.min_year)||new Date().getFullYear(),o=new Date().getFullYear()+1,s=[];for(let a=r;a<=o;a++)s.push(a);return t.json({years:s})}catch(e){return console.error("Error fetching years:",e),t.json({error:"Failed to fetch years"},500)}});f.get("/api/records",A,async t=>{try{const e=t.req.query("month"),r=t.req.query("year");if(!e||!r)return t.json({error:"Month and year required"},400);const o=await t.env.DB.prepare(`
|
|
SELECT
|
|
pr.*,
|
|
sc.material_date,
|
|
sc.material2_date,
|
|
sc.package_date,
|
|
sc.worksheets_date,
|
|
sc.cutting_date,
|
|
sc.glazing_date,
|
|
sc.ready_date,
|
|
sc.issued_date,
|
|
sc.worksheets_error,
|
|
sc.cutting_error,
|
|
sc.glazing_error,
|
|
sc.ready_error,
|
|
sc.issued_error,
|
|
sc.material_confirmed,
|
|
sc.material2_confirmed,
|
|
sc.worksheets_confirmed
|
|
FROM production_records pr
|
|
LEFT JOIN status_checkboxes sc ON pr.id = sc.record_id
|
|
WHERE pr.month = ? AND pr.year = ? AND pr.deleted_at IS NULL
|
|
ORDER BY pr.created_at DESC
|
|
`).bind(e,r).all();return t.json(o.results||[])}catch(e){return console.error("Error fetching records:",e),t.json({error:"Failed to fetch records"},500)}});f.post("/api/records",A,async t=>{try{const e=await t.req.json(),r=t.get("userId"),o=e.quantity?parseInt(e.quantity,10):0,s=e.price?parseFloat(e.price):0,a=await t.env.DB.prepare(`
|
|
INSERT INTO production_records (
|
|
month, year, client_name, type, offer_number, work_number,
|
|
quantity, color, notes, problems, installer, price,
|
|
created_by, updated_by
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`).bind(e.month,e.year,e.client_name,e.type||null,e.offer_number,e.work_number,o,e.color||null,e.notes||null,e.problems||null,e.installer||null,s,r,r).run(),i=e.material_date&&e.material_date!=="null"?e.material_date:null,l=e.material2_date&&e.material2_date!=="null"?e.material2_date:null,n=e.package_date&&e.package_date!=="null"?e.package_date:null;return await t.env.DB.prepare(`
|
|
INSERT INTO status_checkboxes (
|
|
record_id, material_date, material2_date, package_date
|
|
) VALUES (?, ?, ?, ?)
|
|
`).bind(a.meta.last_row_id,i,l,n).run(),t.json({success:!0,id:a.meta.last_row_id})}catch(e){return console.error("Error creating record:",e),t.json({error:"Failed to create record"},500)}});f.put("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.req.json(),o=t.get("userId"),s=r.quantity?parseInt(r.quantity,10):0,a=r.price?parseFloat(r.price):0;if(await t.env.DB.prepare(`
|
|
UPDATE production_records
|
|
SET client_name = ?, type = ?, offer_number = ?, work_number = ?,
|
|
quantity = ?, color = ?, notes = ?, problems = ?, installer = ?, price = ?,
|
|
updated_by = ?, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ? AND deleted_at IS NULL
|
|
`).bind(r.client_name,r.type||null,r.offer_number,r.work_number,s,r.color||null,r.notes||null,r.problems||null,r.installer||null,a,o,e).run(),r.material_date!==void 0||r.material2_date!==void 0||r.package_date!==void 0){const i=r.material_date&&r.material_date!=="null"?r.material_date:null,l=r.material2_date&&r.material2_date!=="null"?r.material2_date:null,n=r.package_date&&r.package_date!=="null"?r.package_date:null;await t.env.DB.prepare(`
|
|
UPDATE status_checkboxes
|
|
SET material_date = ?,
|
|
material2_date = ?,
|
|
package_date = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE record_id = ?
|
|
`).bind(i,l,n,e).run()}return t.json({success:!0})}catch(e){return console.error("Error updating record:",e),t.json({error:"Failed to update record"},500)}});f.get("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=await t.env.DB.prepare(`
|
|
SELECT * FROM production_records WHERE id = ? AND deleted_at IS NULL
|
|
`).bind(e).first();return r?t.json(r):t.json({error:"Record not found"},404)}catch(e){return console.error("Error fetching record:",e),t.json({error:"Failed to fetch record"},500)}});f.delete("/api/records/:id",A,async t=>{try{const e=t.req.param("id"),r=t.get("userId");return console.log("[DELETE] Deleting record:",e,"by user:",r),await t.env.DB.prepare(`
|
|
UPDATE production_records
|
|
SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
|
|
WHERE id = ?
|
|
`).bind(e).run(),console.log("[DELETE] Record deleted successfully:",e),t.json({success:!0})}catch(e){return console.error("Error deleting record:",e),t.json({error:"Failed to delete record"},500)}});f.patch("/api/records/:id/status",A,async t=>{try{const e=t.req.param("id"),{field:r,date:o}=await t.req.json(),s=t.get("userId");console.log(`[TOGGLE] recordId=${e}, field=${r}, date=${JSON.stringify(o)}`);const a=`${r}_date`,i=await t.env.DB.prepare(`SELECT ${a} FROM status_checkboxes WHERE record_id = ?`).bind(e).first();if(r==="ready"||r==="issued"){const n=await t.env.DB.prepare("SELECT worksheets_error, cutting_error, glazing_error, ready_error, issued_error FROM status_checkboxes WHERE record_id = ?").bind(e).first();if(n&&(n.worksheets_error||n.cutting_error||n.glazing_error||n.ready_error||n.issued_error))return t.json({error:"blocked",message:"Vigade märked on seatud (punased kolmnurgad)"},403)}let l;return!o||o==="null"?i!=null&&i[a]?l=null:l=new Date().toISOString().split("T")[0]:o===(i==null?void 0:i[a])?l=null:l=o,await t.env.DB.prepare(`UPDATE status_checkboxes SET ${a} = ? WHERE record_id = ?`).bind(l,e).run(),await t.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'toggle_status')
|
|
`).bind(s||null,e,r,(i==null?void 0:i[a])||null,l).run(),t.json({success:!0})}catch(e){return console.error("Error toggling status:",e),t.json({error:"Failed to toggle status"},500)}});f.patch("/api/status/:recordId/:field",A,async t=>{try{const e=t.req.param("id"),r=t.req.param("field"),{date:o}=await t.req.json(),s=t.get("userId"),a=await t.env.DB.prepare(`SELECT ${r}_date FROM status_checkboxes WHERE record_id = ?`).bind(e).first();if(r==="ready"||r==="issued"){const i=await t.env.DB.prepare("SELECT worksheets_error, cutting_error, glazing_error, ready_error, issued_error FROM status_checkboxes WHERE record_id = ?").bind(e).first();if(i&&(i.worksheets_error||i.cutting_error||i.glazing_error||i.ready_error||i.issued_error))return t.json({error:"Väli blokeeritud",blocked:!0,reason:"Vigade märked on seatud (punased kolmnurgad)"},400)}return await t.env.DB.prepare(`UPDATE status_checkboxes SET ${r}_date = ? WHERE record_id = ?`).bind(o,e).run(),await t.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_status')
|
|
`).bind(s||null,e,r,(a==null?void 0:a[`${r}_date`])||null,o).run(),t.json({success:!0})}catch(e){return console.error("Error updating status:",e),t.json({error:"Failed to update status"},500)}});f.patch("/api/status/:recordId/:field/error",A,async t=>{try{const e=t.req.param("id"),r=t.req.param("field"),{value:o}=await t.req.json(),s=t.get("userId");return await t.env.DB.prepare(`UPDATE status_checkboxes SET ${r}_error = ? WHERE record_id = ?`).bind(o?1:0,e).run(),t.json({success:!0})}catch(e){return console.error("Error updating error flag:",e),t.json({error:"Failed to update error flag"},500)}});f.patch("/api/status/:recordId/:field/confirm",A,async t=>{try{const e=t.req.param("id"),r=t.req.param("field"),{value:o}=await t.req.json(),s=t.get("userId");return await t.env.DB.prepare(`UPDATE status_checkboxes SET ${r}_confirmed = ? WHERE record_id = ?`).bind(o?1:0,e).run(),t.json({success:!0})}catch(e){return console.error("Error updating confirmation flag:",e),t.json({error:"Failed to update confirmation flag"},500)}});f.patch("/api/records/:id/worksheets-cycle",A,async t=>{try{const e=t.req.param("id"),r=t.get("userId"),o=await t.env.DB.prepare("SELECT worksheets_date, worksheets_confirmed FROM status_checkboxes WHERE record_id = ?").bind(e).first();let s=null,a=0;return o!=null&&o.worksheets_date?o.worksheets_confirmed===0?(a=1,s=o.worksheets_date):(a=0,s=null):(a=0,s=new Date().toISOString().split("T")[0]),await t.env.DB.prepare("UPDATE status_checkboxes SET worksheets_date = ?, worksheets_confirmed = ? WHERE record_id = ?").bind(s,a,e).run(),await t.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'worksheets_cycle')
|
|
`).bind(r||null,e,"worksheets",(o==null?void 0:o.worksheets_date)||"",s||"").run(),t.json({success:!0,date:s,confirmed:a})}catch(e){return console.error("Error cycling worksheets:",e),t.json({error:"Failed to cycle worksheets"},500)}});f.patch("/api/records/:id/notes",Ct,async t=>{try{const e=t.req.param("id"),{notes:r}=await t.req.json(),o=t.get("userId");return t.get("role")!=="admin"?t.json({error:"Permission denied. Only admin can edit notes."},403):(await t.env.DB.prepare("UPDATE production_records SET notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").bind(r,e).run(),await t.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_notes')
|
|
`).bind(o||null,e,"notes","",r).run(),t.json({success:!0}))}catch(e){return console.error("Error updating notes:",e),t.json({error:"Failed to update notes"},500)}});f.patch("/api/records/:id/problems",Ct,async t=>{try{const e=t.req.param("id"),{problems:r,errorFlags:o}=await t.req.json(),s=t.get("userId"),a=t.get("role");return a!=="admin"&&a!=="user"?t.json({error:"Permission denied. Only admin and user can edit problems."},403):(await t.env.DB.prepare("UPDATE production_records SET problems = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?").bind(r,e).run(),await t.env.DB.prepare(`
|
|
UPDATE status_checkboxes
|
|
SET worksheets_error = ?,
|
|
cutting_error = ?,
|
|
glazing_error = ?,
|
|
ready_error = ?,
|
|
issued_error = ?
|
|
WHERE record_id = ?
|
|
`).bind(o.worksheets?1:0,o.cutting?1:0,o.glazing?1:0,o.ready?1:0,o.issued?1:0,e).run(),await t.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_problems')
|
|
`).bind(s||null,e,"problems","",r).run(),t.json({success:!0}))}catch(e){return console.error("Error updating problems:",e),t.json({error:"Failed to update problems"},500)}});f.patch("/api/records/:id/material-confirmed",A,async t=>{try{const e=t.req.param("id");console.log("[MAT1] Toggle request for record:",e);const r=await t.env.DB.prepare("SELECT material_confirmed FROM status_checkboxes WHERE record_id = ?").bind(e).first();console.log("[MAT1] Current value:",r==null?void 0:r.material_confirmed);const o=(r==null?void 0:r.material_confirmed)===1?0:1;return console.log("[MAT1] New value:",o),await t.env.DB.prepare("UPDATE status_checkboxes SET material_confirmed = ? WHERE record_id = ?").bind(o,e).run(),console.log("[MAT1] Update completed successfully"),t.json({success:!0,newValue:o})}catch(e){return console.error("[MAT1] Error updating material confirmed:",e),t.json({error:"Failed to update material confirmed"},500)}});f.patch("/api/records/:id/material2-confirmed",A,async t=>{try{const e=t.req.param("id");console.log("[MAT2] Toggle request for record:",e);const r=await t.env.DB.prepare("SELECT material2_confirmed FROM status_checkboxes WHERE record_id = ?").bind(e).first();console.log("[MAT2] Current value:",r==null?void 0:r.material2_confirmed);const o=(r==null?void 0:r.material2_confirmed)===1?0:1;return console.log("[MAT2] New value:",o),await t.env.DB.prepare("UPDATE status_checkboxes SET material2_confirmed = ? WHERE record_id = ?").bind(o,e).run(),console.log("[MAT2] Update completed successfully"),t.json({success:!0,newValue:o})}catch(e){return console.error("[MAT2] Error updating material2 confirmed:",e),t.json({error:"Failed to update material2 confirmed"},500)}});f.patch("/api/records/:id/price-paid",A,async t=>{try{const e=t.req.param("id"),{paid:r}=await t.req.json();return t.json({success:!0})}catch(e){return console.error("Error updating price paid:",e),t.json({error:"Failed to update price paid"},500)}});f.get("/",t=>t.html(yr));f.get("/test-click",t=>t.html(`<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Click Test</title>
|
|
<style>
|
|
body { font-family: Arial; padding: 50px; }
|
|
.box {
|
|
width: 200px;
|
|
height: 100px;
|
|
background: #4F46E5;
|
|
color: white;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
margin: 20px 0;
|
|
}
|
|
#result {
|
|
margin-top: 20px;
|
|
padding: 20px;
|
|
background: #f0f0f0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Click Test Page</h1>
|
|
<div class="box" onclick="handleClick(1)">Click Me (onclick)</div>
|
|
<div class="box" id="box2">Click Me (addEventListener)</div>
|
|
<div id="result">Waiting for click...</div>
|
|
|
|
<script>
|
|
// Test 1: inline onclick
|
|
function handleClick(num) {
|
|
document.getElementById('result').innerHTML = '✅ Test ' + num + ': onclick works!';
|
|
}
|
|
|
|
// Test 2: addEventListener
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
document.getElementById('box2').addEventListener('click', () => {
|
|
document.getElementById('result').innerHTML = '✅ Test 2: addEventListener works!';
|
|
});
|
|
console.log('✅ DOMContentLoaded fired and event listener attached');
|
|
});
|
|
<\/script>
|
|
</body>
|
|
</html>`));const $t=new fe,fr=Object.assign({"/src/index.tsx":f});let _e=!1;for(const[,t]of Object.entries(fr))t&&($t.all("*",e=>{let r;try{r=e.executionCtx}catch{}return t.fetch(e.req.raw,e.env,r)}),$t.notFound(e=>{let r;try{r=e.executionCtx}catch{}return t.fetch(e.req.raw,e.env,r)}),_e=!0);if(!_e)throw new Error("Can't import modules from ['/src/index.ts','/src/index.tsx','/app/server.ts']");export{$t as default};
|