feat: dochazka Excel export + auto-reload + 162 filter
- Add dochazka Excel export using direct ZIP/XML manipulation of template (preserves styles.xml byte-for-byte to avoid Excel "repaired styles" warning) - Calculate per-person stravné doplatek, transport (AUV), and indiv1 (closure count + Janouš internet) per sichtovnice.py logic - Filter exported people to TEMPLATE_NAMES (12 fixed template rows) - Add server version polling + auto-reload on deploy - Add FPD check modal for monthly hour validation - Add "162" filter button to hide first 5 TKB people from view Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,8 @@ const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
const app = express()
|
||||
const PORT = 3080
|
||||
const PORT = process.env.PORT || 3090
|
||||
const SERVER_VERSION = Date.now().toString()
|
||||
|
||||
const SCHEDULES_DIR = join(__dirname, 'schedules')
|
||||
const SAVED_SCHEDULE_PATH = join(__dirname, 'saved_schedule.json')
|
||||
@@ -198,6 +199,55 @@ app.get('/api/files/:id/export-excel', (_req, res) => {
|
||||
res.status(501).json({ error: 'Excel export not yet implemented for TKB format' })
|
||||
})
|
||||
|
||||
// Export docházka Excel for a given month
|
||||
app.get('/api/files/:id/export-dochazka', 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_dochazka.py')
|
||||
|
||||
try {
|
||||
const { spawn } = await import('child_process')
|
||||
await new Promise((resolve, reject) => {
|
||||
const chunks = []
|
||||
const errChunks = []
|
||||
const proc = spawn('python3', [scriptPath])
|
||||
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_dochazka 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 = `OP2416101755_TKB_${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: 'Export failed', details: err.message })
|
||||
}
|
||||
})
|
||||
|
||||
// Create new file
|
||||
app.post('/api/files', (req, res) => {
|
||||
try {
|
||||
@@ -372,6 +422,11 @@ app.get('/api/export-excel', (_req, res) => {
|
||||
res.status(501).json({ error: 'Excel export not yet implemented for TKB format' })
|
||||
})
|
||||
|
||||
// Version endpoint — clients poll this and reload when version changes
|
||||
app.get('/api/version', (_req, res) => {
|
||||
res.json({ version: SERVER_VERSION })
|
||||
})
|
||||
|
||||
// Serve static files from dist/
|
||||
app.use(express.static(join(__dirname, 'dist'), {
|
||||
setHeaders: (res) => {
|
||||
|
||||
Reference in New Issue
Block a user