fix: stahování souborů ve filemanageru - chybějící auth token

window.open() neposílá JWT header, přepsáno na apiFetch() + blob download.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 13:31:32 +01:00
parent 45fd930f76
commit 019a8355dc
37 changed files with 58 additions and 39 deletions

View File

@@ -1 +1 @@
{"window_start":1773403455,"count":8} {"window_start":1773405014,"count":4}

View File

@@ -0,0 +1 @@
{"window_start":1773404939,"count":1}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{j as e,m as f}from"./vendor-animation-0s3FMHwK.js";import{r as m}from"./vendor-react-BVs3cwbi.js";import{a9 as T}from"./vendor-utils-Dyr8OjFr.js";import{a as C,u as A,c as O,F as B,A as H}from"./index-BrM8fzBu.js";import{F as I}from"./Forbidden-D25jV3Oq.js";import{c as W,b as k,g as w,d as z,e as S,a as v,h as E,i as y,f as b}from"./attendanceHelpers-D6sLEw0q.js";const L="/api/admin",R=s=>s.break_start&&s.break_end?`${b(s.break_start)} - ${b(s.break_end)}`:s.break_start?`${b(s.break_start)} - ?`:"—",Z=s=>s.project_logs&&s.project_logs.length>0?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"0.125rem"},children:s.project_logs.map((n,g)=>{let d,c,o=!1;if(n.hours!==null&&n.hours!==void 0)d=parseInt(n.hours)||0,c=parseInt(n.minutes)||0;else{o=!n.ended_at;const x=n.ended_at?new Date(n.ended_at):new Date,p=Math.floor((x-new Date(n.started_at))/6e4);d=Math.floor(p/60),c=p%60}return e.jsxs("span",{className:"admin-badge",style:{fontSize:"0.7rem",display:"inline-block",background:o?"var(--accent-light)":void 0},children:[n.project_name||`#${n.project_id}`," (",d,":",String(c).padStart(2,"0"),"h",o?" ▸":"",")"]},n.id||g)})}):s.project_name?e.jsx("span",{className:"admin-badge admin-badge-wrap",style:{fontSize:"0.75rem"},children:s.project_name}):"—",Y=s=>s.overtime>0?e.jsxs("span",{className:"leave-badge badge-overtime",children:["+",s.overtime,"h přesčas"]}):s.remaining>0?e.jsxs("span",{style:{color:"#dc2626"},children:["",s.remaining,"h"]}):e.jsx("span",{style:{color:"#16a34a"},children:"splněno"});function Q(){const s=C(),{user:n,hasPermission:g}=A(),[d,c]=m.useState(!0),o=m.useRef(null),[x,p]=m.useState(()=>{const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}),[t,D]=m.useState({records:[],month_name:"",year:new Date().getFullYear(),total_minutes:0,vacation_hours:0,sick_hours:0,holiday_hours:0,unpaid_hours:0,leave_balance:null,monthly_fund:null}),_=m.useCallback(async()=>{c(!0);try{const a=await O(`${L}/attendance.php?action=history&month=${x}`);if(a.status===401)return;const i=await a.json();i.success&&D(i.data)}catch{s.error("Nepodařilo se načíst data")}finally{c(!1)}},[x,s]);if(m.useEffect(()=>{_()},[_]),!g("attendance.history"))return e.jsx(I,{});const $=()=>{if(!o.current)return;const a=window.open("","_blank");a.document.write(` import{j as e,m as f}from"./vendor-animation-0s3FMHwK.js";import{r as m}from"./vendor-react-BVs3cwbi.js";import{a9 as T}from"./vendor-utils-Dyr8OjFr.js";import{a as C,u as A,c as O,F as B,A as H}from"./index-Bay45BGf.js";import{F as I}from"./Forbidden-D25jV3Oq.js";import{c as W,b as k,g as w,d as z,e as S,a as v,h as E,i as y,f as b}from"./attendanceHelpers-D6sLEw0q.js";const L="/api/admin",R=s=>s.break_start&&s.break_end?`${b(s.break_start)} - ${b(s.break_end)}`:s.break_start?`${b(s.break_start)} - ?`:"—",Z=s=>s.project_logs&&s.project_logs.length>0?e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:"0.125rem"},children:s.project_logs.map((n,g)=>{let d,c,o=!1;if(n.hours!==null&&n.hours!==void 0)d=parseInt(n.hours)||0,c=parseInt(n.minutes)||0;else{o=!n.ended_at;const x=n.ended_at?new Date(n.ended_at):new Date,p=Math.floor((x-new Date(n.started_at))/6e4);d=Math.floor(p/60),c=p%60}return e.jsxs("span",{className:"admin-badge",style:{fontSize:"0.7rem",display:"inline-block",background:o?"var(--accent-light)":void 0},children:[n.project_name||`#${n.project_id}`," (",d,":",String(c).padStart(2,"0"),"h",o?" ▸":"",")"]},n.id||g)})}):s.project_name?e.jsx("span",{className:"admin-badge admin-badge-wrap",style:{fontSize:"0.75rem"},children:s.project_name}):"—",Y=s=>s.overtime>0?e.jsxs("span",{className:"leave-badge badge-overtime",children:["+",s.overtime,"h přesčas"]}):s.remaining>0?e.jsxs("span",{style:{color:"#dc2626"},children:["",s.remaining,"h"]}):e.jsx("span",{style:{color:"#16a34a"},children:"splněno"});function Q(){const s=C(),{user:n,hasPermission:g}=A(),[d,c]=m.useState(!0),o=m.useRef(null),[x,p]=m.useState(()=>{const a=new Date;return`${a.getFullYear()}-${String(a.getMonth()+1).padStart(2,"0")}`}),[t,D]=m.useState({records:[],month_name:"",year:new Date().getFullYear(),total_minutes:0,vacation_hours:0,sick_hours:0,holiday_hours:0,unpaid_hours:0,leave_balance:null,monthly_fund:null}),_=m.useCallback(async()=>{c(!0);try{const a=await O(`${L}/attendance.php?action=history&month=${x}`);if(a.status===401)return;const i=await a.json();i.success&&D(i.data)}catch{s.error("Nepodařilo se načíst data")}finally{c(!1)}},[x,s]);if(m.useEffect(()=>{_()},[_]),!g("attendance.history"))return e.jsx(I,{});const $=()=>{if(!o.current)return;const a=window.open("","_blank");a.document.write(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="cs"> <html lang="cs">
<head> <head>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
dist/assets/ProjectDetail-B_QNS7Ph.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
import{j as e,m as p,A as Z}from"./vendor-animation-0s3FMHwK.js";import{r as i,L as J}from"./vendor-react-BVs3cwbi.js";import{a9 as G}from"./vendor-utils-Dyr8OjFr.js";import{a as q,u as Q,c as b,b as X,F as r,A as C,f as l,C as ee}from"./index-BrM8fzBu.js";import{F as se}from"./Forbidden-D25jV3Oq.js";import{b as $}from"./attendanceHelpers-D6sLEw0q.js";const N="/api/admin";function de(){const d=q(),{hasPermission:L}=Q(),[k,D]=i.useState(!0),[j,V]=i.useState(()=>{const s=new Date;return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-01`}),[g,A]=i.useState(()=>{const s=new Date,t=new Date(s.getFullYear(),s.getMonth()+1,0).getDate();return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(t).padStart(2,"0")}`}),[m,F]=i.useState(""),[h,E]=i.useState(""),[P,B]=i.useState({trips:[],vehicles:[],users:[],totals:{total:0,business:0,count:0}}),[n,I]=i.useState(null),w=i.useRef(null),[T,v]=i.useState(!1),[_,U]=i.useState(null),[a,o]=i.useState({vehicle_id:"",trip_date:"",start_km:"",end_km:"",route_from:"",route_to:"",is_business:1,notes:""}),[u,z]=i.useState({show:!1,trip:null}),y=i.useCallback(async(s=!0)=>{s&&D(!0);try{let t=`${N}/trips.php?action=admin&date_from=${j}&date_to=${g}`;m&&(t+=`&vehicle_id=${m}`),h&&(t+=`&user_id=${h}`);const c=await(await b(t)).json();c.success&&B(c.data)}catch{d.error("Nepodařilo se načíst data")}finally{s&&D(!1)}},[j,g,m,h,d]);if(i.useEffect(()=>{y()},[y]),X(T),!L("trips.admin"))return e.jsx(se,{});const H=s=>{U(s),o({vehicle_id:s.vehicle_id,trip_date:s.trip_date,start_km:s.start_km,end_km:s.end_km,route_from:s.route_from,route_to:s.route_to,is_business:s.is_business,notes:s.notes||""}),v(!0)},O=async()=>{if(parseInt(a.end_km)<=parseInt(a.start_km)){d.error("Konečný stav km musí být větší než počáteční");return}try{const t=await(await b(`${N}/trips.php?id=${_.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();t.success?(v(!1),await y(!1),await new Promise(x=>setTimeout(x,300)),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},W=async()=>{if(u.trip)try{const t=await(await b(`${N}/trips.php?id=${u.trip.id}`,{method:"DELETE"})).json();t.success?(z({show:!1,trip:null}),await y(!1),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},K=async()=>{try{let s=`${N}/trips.php?action=print&date_from=${j}&date_to=${g}`;m&&(s+=`&vehicle_id=${m}`),h&&(s+=`&user_id=${h}`);const x=await(await b(s)).json();x.success&&(I(x.data),setTimeout(()=>{if(w.current){const c=window.open("","_blank");c.document.write(` import{j as e,m as p,A as Z}from"./vendor-animation-0s3FMHwK.js";import{r as i,L as J}from"./vendor-react-BVs3cwbi.js";import{a9 as G}from"./vendor-utils-Dyr8OjFr.js";import{a as q,u as Q,c as b,b as X,F as r,A as C,f as l,C as ee}from"./index-Bay45BGf.js";import{F as se}from"./Forbidden-D25jV3Oq.js";import{b as $}from"./attendanceHelpers-D6sLEw0q.js";const N="/api/admin";function de(){const d=q(),{hasPermission:L}=Q(),[k,D]=i.useState(!0),[j,V]=i.useState(()=>{const s=new Date;return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-01`}),[g,A]=i.useState(()=>{const s=new Date,t=new Date(s.getFullYear(),s.getMonth()+1,0).getDate();return`${s.getFullYear()}-${String(s.getMonth()+1).padStart(2,"0")}-${String(t).padStart(2,"0")}`}),[m,F]=i.useState(""),[h,E]=i.useState(""),[P,B]=i.useState({trips:[],vehicles:[],users:[],totals:{total:0,business:0,count:0}}),[n,I]=i.useState(null),w=i.useRef(null),[T,v]=i.useState(!1),[_,U]=i.useState(null),[a,o]=i.useState({vehicle_id:"",trip_date:"",start_km:"",end_km:"",route_from:"",route_to:"",is_business:1,notes:""}),[u,z]=i.useState({show:!1,trip:null}),y=i.useCallback(async(s=!0)=>{s&&D(!0);try{let t=`${N}/trips.php?action=admin&date_from=${j}&date_to=${g}`;m&&(t+=`&vehicle_id=${m}`),h&&(t+=`&user_id=${h}`);const c=await(await b(t)).json();c.success&&B(c.data)}catch{d.error("Nepodařilo se načíst data")}finally{s&&D(!1)}},[j,g,m,h,d]);if(i.useEffect(()=>{y()},[y]),X(T),!L("trips.admin"))return e.jsx(se,{});const H=s=>{U(s),o({vehicle_id:s.vehicle_id,trip_date:s.trip_date,start_km:s.start_km,end_km:s.end_km,route_from:s.route_from,route_to:s.route_to,is_business:s.is_business,notes:s.notes||""}),v(!0)},O=async()=>{if(parseInt(a.end_km)<=parseInt(a.start_km)){d.error("Konečný stav km musí být větší než počáteční");return}try{const t=await(await b(`${N}/trips.php?id=${_.id}`,{method:"PUT",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)})).json();t.success?(v(!1),await y(!1),await new Promise(x=>setTimeout(x,300)),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},W=async()=>{if(u.trip)try{const t=await(await b(`${N}/trips.php?id=${u.trip.id}`,{method:"DELETE"})).json();t.success?(z({show:!1,trip:null}),await y(!1),d.success(t.message)):d.error(t.error)}catch{d.error("Chyba připojení")}},K=async()=>{try{let s=`${N}/trips.php?action=print&date_from=${j}&date_to=${g}`;m&&(s+=`&vehicle_id=${m}`),h&&(s+=`&user_id=${h}`);const x=await(await b(s)).json();x.success&&(I(x.data),setTimeout(()=>{if(w.current){const c=window.open("","_blank");c.document.write(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="cs"> <html lang="cs">
<head> <head>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-BrM8fzBu.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u}; import{j as x}from"./vendor-animation-0s3FMHwK.js";import{r as t}from"./vendor-react-BVs3cwbi.js";import{a as L,c as O}from"./index-Bay45BGf.js";function J({column:e,sort:r,order:n}){return r!==e?null:x.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",style:{marginLeft:4,verticalAlign:"middle"},children:x.jsx("path",{d:n==="ASC"?"M18 15l-6-6-6 6":"M6 9l6 6 6-6"})})}function V(e,r="DESC"){const[n,a]=t.useState(e),[o,c]=t.useState(r),i=t.useRef(!1),S=t.useCallback(u=>{i.current=!0,a(m=>m===u?(c(h=>h==="ASC"?"DESC":"ASC"),m):(c("DESC"),u))},[]),d=i.current?n:null;return{sort:n,order:o,handleSort:S,activeSort:d}}function I(e,r=300){const[n,a]=t.useState(e);return t.useEffect(()=>{const o=setTimeout(()=>a(e),r);return()=>clearTimeout(o)},[e,r]),n}const N="/api/admin";function _(e,{dataKey:r,search:n,sort:a,order:o,page:c,perPage:i,extraParams:S,errorMsg:d="Nepodařilo se načíst data"}={}){const u=L(),[m,h]=t.useState([]),[j,D]=t.useState(!0),[w,k]=t.useState(null),l=t.useRef(null),p=S?JSON.stringify(S):"",b=I(n,300),C=t.useCallback(async()=>{l.current&&l.current.abort();const g=new AbortController;l.current=g;try{const s=new URLSearchParams;if(b&&s.set("search",b),a&&s.set("sort",a),o&&s.set("order",o),c&&s.set("page",c),i&&s.set("per_page",i),p){const R=JSON.parse(p);Object.entries(R).forEach(([y,A])=>{A&&s.set(y,A)})}const E=await O(`${N}/${e}?${s}`,{signal:g.signal});if(E.status===401)return;const f=await E.json();f.success?(h(f.data[r]||[]),f.data.pagination&&k(f.data.pagination)):u.error(f.error||d)}catch(s){if(s.name==="AbortError")return;u.error("Chyba připojení")}finally{D(!1)}},[u,e,r,b,a,o,c,i,p,d]);return t.useEffect(()=>(C(),()=>{l.current&&l.current.abort()}),[C]),{items:m,setItems:h,loading:j,pagination:w,refetch:C}}export{J as S,_ as a,V as u};

2
dist/index.html vendored
View File

@@ -29,7 +29,7 @@
<link <link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Urbanist:wght@400;500;600;700;800&display=swap" href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Urbanist:wght@400;500;600;700;800&display=swap"
rel="stylesheet" /> rel="stylesheet" />
<script type="module" crossorigin src="/assets/index-BrM8fzBu.js"></script> <script type="module" crossorigin src="/assets/index-Bay45BGf.js"></script>
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BVs3cwbi.js"> <link rel="modulepreload" crossorigin href="/assets/vendor-react-BVs3cwbi.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-animation-0s3FMHwK.js"> <link rel="modulepreload" crossorigin href="/assets/vendor-animation-0s3FMHwK.js">
<link rel="modulepreload" crossorigin href="/assets/vendor-utils-Dyr8OjFr.js"> <link rel="modulepreload" crossorigin href="/assets/vendor-utils-Dyr8OjFr.js">

View File

@@ -3,7 +3,7 @@
'name' => 'boha/website', 'name' => 'boha/website',
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => '9e3c95e5764f4ec5167c09b9f9cf05a58891a0aa', 'reference' => '45fd930f76debb906f6a51b10aab2cb0937ac200',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),
@@ -13,7 +13,7 @@
'boha/website' => array( 'boha/website' => array(
'pretty_version' => 'dev-master', 'pretty_version' => 'dev-master',
'version' => 'dev-master', 'version' => 'dev-master',
'reference' => '9e3c95e5764f4ec5167c09b9f9cf05a58891a0aa', 'reference' => '45fd930f76debb906f6a51b10aab2cb0937ac200',
'type' => 'project', 'type' => 'project',
'install_path' => __DIR__ . '/../../', 'install_path' => __DIR__ . '/../../',
'aliases' => array(), 'aliases' => array(),

View File

@@ -250,14 +250,32 @@ export default function ProjectFileManager({ projectId, projectNumber, hasPermis
} }
} }
const handleDownload = (item) => { const handleDownload = async (item) => {
const filePath = currentPath ? `${currentPath}/${item.name}` : item.name const filePath = currentPath ? `${currentPath}/${item.name}` : item.name
const params = new URLSearchParams({ const params = new URLSearchParams({
action: 'download', action: 'download',
project_id: projectId, project_id: projectId,
path: filePath, path: filePath,
}) })
window.open(`${API_BASE}/project-files.php?${params}`, '_blank') try {
const res = await apiFetch(`${API_BASE}/project-files.php?${params}`)
if (!res.ok) {
const err = await res.json().catch(() => null)
alert.error(err?.error || 'Chyba při stahování')
return
}
const blob = await res.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = item.name
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
} catch {
alert.error('Chyba připojení')
}
} }
const handleDelete = async () => { const handleDelete = async () => {