feat: add Kropáček Ondřej + May–Jul feature work (half-cell marks, copy/paste, exports, roles)
New worker: Kropáček Ondřej (TKB, part-time student) inserted between Dvořák Václav and Vondrák Pavel in data.json, the full-month Excel template (new row 13, everything below shifted) and the docházka template (13 person rows). Not added to the harmonogram/CIS roster — he has no CIS personal number. Also checkpoints the uncommitted work deployed to production between May and July 2026: - half-cell day/night preference marks (color + text per half) - cell copy/paste (Ctrl+C/V, context menu), incl. from info rows - light/dark theme toggle, TKB people filter, 162 filter - full-month Excel export (template) and harmonogram export for CIS - docházka export: half-time workers, retired flag, contractor handling - third login role "cis" (viewer + export access) - retired flag hides a person from UI/FPD while keeping past data Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
100
web/server.js
100
web/server.js
@@ -194,9 +194,103 @@ app.get('/api/files/:id', (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// Export file as Excel (not yet implemented for TKB format)
|
||||
app.get('/api/files/:id/export-excel', (_req, res) => {
|
||||
res.status(501).json({ error: 'Excel export not yet implemented for TKB format' })
|
||||
// Export full schedule Excel for a given month
|
||||
app.get('/api/files/:id/export-excel', async (req, res) => {
|
||||
const { id } = req.params
|
||||
const month = parseInt(req.query.month) || (new Date().getMonth() + 1)
|
||||
const year = parseInt(req.query.year) || new Date().getFullYear()
|
||||
|
||||
const filePath = join(SCHEDULES_DIR, `${id}.json`)
|
||||
if (!existsSync(filePath)) return res.status(404).json({ error: 'File not found' })
|
||||
|
||||
let fileData
|
||||
try { fileData = JSON.parse(readFileSync(filePath, 'utf8')) }
|
||||
catch { return res.status(500).json({ error: 'Failed to read file' }) }
|
||||
|
||||
const input = JSON.stringify({ schedule: fileData.data, month, year })
|
||||
const scriptPath = join(__dirname, 'export_full_excel.py')
|
||||
|
||||
try {
|
||||
const { spawn } = await import('child_process')
|
||||
await new Promise((resolve, reject) => {
|
||||
const chunks = []
|
||||
const errChunks = []
|
||||
const proc = spawn('python3', [scriptPath], { cwd: __dirname })
|
||||
proc.stdout.on('data', d => chunks.push(d))
|
||||
proc.stderr.on('data', d => errChunks.push(d))
|
||||
proc.on('close', code => {
|
||||
if (code !== 0) {
|
||||
const errMsg = Buffer.concat(errChunks).toString()
|
||||
console.error('export_full_excel error:', errMsg)
|
||||
res.status(500).json({ error: errMsg })
|
||||
reject(new Error(errMsg))
|
||||
} else {
|
||||
const xlsx = Buffer.concat(chunks)
|
||||
const MONTH_NAMES = ['','Leden','Únor','Březen','Duben','Květen','Červen','Červenec','Srpen','Září','Říjen','Listopad','Prosinec']
|
||||
const mm = String(month).padStart(2, '0')
|
||||
const fname = `Pohotovost_TKB+IT_smeny_${year}_${mm}_${MONTH_NAMES[month]}.xlsx`
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(fname)}`)
|
||||
res.send(xlsx)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
proc.on('error', reject)
|
||||
proc.stdin.write(input)
|
||||
proc.stdin.end()
|
||||
})
|
||||
} catch (err) {
|
||||
if (!res.headersSent) res.status(500).json({ error: String(err) })
|
||||
}
|
||||
})
|
||||
|
||||
// Export harmonogram Excel for a given month (import into attendance system)
|
||||
app.get('/api/files/:id/export-harmonogram', async (req, res) => {
|
||||
const { id } = req.params
|
||||
const month = parseInt(req.query.month) || (new Date().getMonth() + 1)
|
||||
const year = parseInt(req.query.year) || new Date().getFullYear()
|
||||
|
||||
const filePath = join(SCHEDULES_DIR, `${id}.json`)
|
||||
if (!existsSync(filePath)) return res.status(404).json({ error: 'File not found' })
|
||||
|
||||
let fileData
|
||||
try { fileData = JSON.parse(readFileSync(filePath, 'utf8')) }
|
||||
catch { return res.status(500).json({ error: 'Failed to read file' }) }
|
||||
|
||||
const input = JSON.stringify({ schedule: fileData.data, month, year })
|
||||
const scriptPath = join(__dirname, 'export_harmonogram.py')
|
||||
|
||||
try {
|
||||
const { spawn } = await import('child_process')
|
||||
await new Promise((resolve, reject) => {
|
||||
const chunks = []
|
||||
const errChunks = []
|
||||
const proc = spawn('python3', [scriptPath], { cwd: __dirname })
|
||||
proc.stdout.on('data', d => chunks.push(d))
|
||||
proc.stderr.on('data', d => errChunks.push(d))
|
||||
proc.on('close', code => {
|
||||
if (code !== 0) {
|
||||
const errMsg = Buffer.concat(errChunks).toString()
|
||||
console.error('export_harmonogram error:', errMsg)
|
||||
if (!res.headersSent) res.status(500).json({ error: errMsg })
|
||||
reject(new Error(errMsg))
|
||||
} else {
|
||||
const xlsx = Buffer.concat(chunks)
|
||||
const mm = String(month).padStart(2, '0')
|
||||
const fname = `import_harmonogram_${year}_${mm}.xlsx`
|
||||
res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
|
||||
res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(fname)}`)
|
||||
res.send(xlsx)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
proc.on('error', reject)
|
||||
proc.stdin.write(input)
|
||||
proc.stdin.end()
|
||||
})
|
||||
} catch (err) {
|
||||
if (!res.headersSent) res.status(500).json({ error: 'Export failed', details: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
// Export docházka Excel for a given month
|
||||
|
||||
Reference in New Issue
Block a user