- FIX: DELETE /api/records/:id возвращал 500 (no such column: deleted_by)
- Было: SET deleted_at = CURRENT_TIMESTAMP, deleted_by = ?
- Стало: SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
- Добавлены логи: console.log('[DELETE]...')
- Docker: убран docker-compose.yml из v4.1.21
- Добавлен fix-docker.sh и docker-compose-simple.yml
- Cache version: app.js?v=4.1.22
748 lines
24 KiB
TypeScript
748 lines
24 KiB
TypeScript
import { Hono } from 'hono'
|
|
import { cors } from 'hono/cors'
|
|
import { serveStatic } from 'hono/cloudflare-workers'
|
|
import { authMiddleware, optionalAuthMiddleware } from './middleware/auth'
|
|
import { generateToken, verifyPassword, hashPassword } from './utils/auth'
|
|
import { ORIGINAL_HTML } from './original-html'
|
|
|
|
type Bindings = {
|
|
DB: D1Database;
|
|
}
|
|
|
|
type Variables = {
|
|
userId?: number;
|
|
username?: string;
|
|
role?: string;
|
|
}
|
|
|
|
const app = new Hono<{ Bindings: Bindings; Variables: Variables }>()
|
|
|
|
// Enable CORS
|
|
app.use('/api/*', cors())
|
|
|
|
// Serve static files
|
|
app.use('/static/*', serveStatic({ root: './public' }))
|
|
// Serve favicon (empty response to avoid 404)
|
|
app.get('/favicon.ico', (c) => {
|
|
return new Response(null, { status: 204 })
|
|
})
|
|
|
|
// ==================== AUTH ROUTES ====================
|
|
|
|
// Login endpoint
|
|
app.post('/api/auth/login', async (c) => {
|
|
try {
|
|
const { username, password } = await c.req.json()
|
|
|
|
const user = await c.env.DB.prepare(
|
|
'SELECT id, username, password_hash, full_name, role FROM users WHERE username = ? AND deleted_at IS NULL'
|
|
).bind(username).first()
|
|
|
|
if (!user || !await verifyPassword(password, user.password_hash as string)) {
|
|
return c.json({ error: 'Invalid credentials' }, 401)
|
|
}
|
|
|
|
const token = generateToken(user.id as number, user.username as string)
|
|
|
|
return c.json({
|
|
success: true,
|
|
token,
|
|
user: {
|
|
username: user.username,
|
|
fullName: user.full_name,
|
|
role: user.role
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('Login error:', error)
|
|
return c.json({ error: 'Login failed' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update user profile (password change)
|
|
app.patch('/api/users/profile', authMiddleware, async (c) => {
|
|
try {
|
|
const body = await c.req.json()
|
|
const fullName = body.full_name || body.fullName
|
|
const currentPassword = body.current_password || body.currentPassword
|
|
const newPassword = body.new_password || body.newPassword
|
|
const userId = c.get('userId')
|
|
|
|
console.log('[PROFILE UPDATE]', { userId, fullName, hasCurrentPwd: !!currentPassword, hasNewPwd: !!newPassword })
|
|
|
|
// Get user from database
|
|
const user = await c.env.DB.prepare(
|
|
'SELECT password_hash, full_name FROM users WHERE id = ?'
|
|
).bind(userId).first()
|
|
|
|
if (!user) {
|
|
return c.json({ error: 'Kasutajat ei leitud' }, 404)
|
|
}
|
|
|
|
// If changing password
|
|
if (newPassword) {
|
|
// Verify current password is provided
|
|
if (!currentPassword) {
|
|
return c.json({ error: 'Praegune parool on kohustuslik parooli muutmiseks' }, 400)
|
|
}
|
|
|
|
// Verify current password
|
|
if (!await verifyPassword(currentPassword, user.password_hash as string)) {
|
|
return c.json({ error: 'Vale praegune parool' }, 400)
|
|
}
|
|
|
|
// Update password and full name
|
|
const newHash = await hashPassword(newPassword)
|
|
await c.env.DB.prepare(
|
|
'UPDATE users SET password_hash = ?, full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
).bind(newHash, fullName, userId).run()
|
|
} else {
|
|
// Only update full name (no password change)
|
|
await c.env.DB.prepare(
|
|
'UPDATE users SET full_name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
).bind(fullName, userId).run()
|
|
}
|
|
|
|
return c.json({
|
|
success: true,
|
|
message: 'Profiil uuendatud',
|
|
user: {
|
|
full_name: fullName
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('Profile update error:', error)
|
|
return c.json({ error: 'Profiili uuendamine ebaõnnestus' }, 500)
|
|
}
|
|
})
|
|
|
|
// ==================== DATA ROUTES ====================
|
|
|
|
// Get years for dropdown (with optional auth)
|
|
app.get('/api/years', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const result = await c.env.DB.prepare(
|
|
'SELECT MIN(year) as min_year FROM production_records WHERE deleted_at IS NULL'
|
|
).first()
|
|
|
|
const minYear = result?.min_year || new Date().getFullYear()
|
|
const maxYear = new Date().getFullYear() + 1
|
|
|
|
// Create array of years from minYear to maxYear
|
|
const years = []
|
|
for (let year = minYear; year <= maxYear; year++) {
|
|
years.push(year)
|
|
}
|
|
|
|
return c.json({ years })
|
|
} catch (error) {
|
|
console.error('Error fetching years:', error)
|
|
return c.json({ error: 'Failed to fetch years' }, 500)
|
|
}
|
|
})
|
|
|
|
// Get records (with optional auth for token refresh)
|
|
app.get('/api/records', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const month = c.req.query('month')
|
|
const year = c.req.query('year')
|
|
|
|
if (!month || !year) {
|
|
return c.json({ error: 'Month and year required' }, 400)
|
|
}
|
|
|
|
const records = await c.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(month, year).all()
|
|
|
|
return c.json(records.results || [])
|
|
} catch (error) {
|
|
console.error('Error fetching records:', error)
|
|
return c.json({ error: 'Failed to fetch records' }, 500)
|
|
}
|
|
})
|
|
|
|
// Create new record
|
|
app.post('/api/records', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const data = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
// Validate and convert numeric fields
|
|
const quantity = data.quantity ? parseInt(data.quantity, 10) : 0
|
|
const price = data.price ? parseFloat(data.price) : 0
|
|
|
|
const result = await c.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(
|
|
data.month, data.year, data.client_name, data.type || null,
|
|
data.offer_number, data.work_number, quantity, data.color || null,
|
|
data.notes || null, data.problems || null, data.installer || null,
|
|
price, userId, userId
|
|
).run()
|
|
|
|
// Create status checkboxes entry
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO status_checkboxes (record_id) VALUES (?)
|
|
`).bind(result.meta.last_row_id).run()
|
|
|
|
return c.json({ success: true, id: result.meta.last_row_id })
|
|
} catch (error) {
|
|
console.error('Error creating record:', error)
|
|
return c.json({ error: 'Failed to create record' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update record
|
|
app.put('/api/records/:id', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const id = c.req.param('id')
|
|
const data = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
// Validate and convert numeric fields
|
|
const quantity = data.quantity ? parseInt(data.quantity, 10) : 0
|
|
const price = data.price ? parseFloat(data.price) : 0
|
|
|
|
await c.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(
|
|
data.client_name, data.type || null, data.offer_number, data.work_number,
|
|
quantity, data.color || null, data.notes || null, data.problems || null,
|
|
data.installer || null, price, userId, id
|
|
).run()
|
|
|
|
// Update status_checkboxes dates if provided
|
|
if (data.material_date !== undefined || data.material2_date !== undefined || data.package_date !== undefined) {
|
|
// Convert empty strings and "null" strings to actual NULL
|
|
const materialDate = (data.material_date && data.material_date !== 'null') ? data.material_date : null
|
|
const material2Date = (data.material2_date && data.material2_date !== 'null') ? data.material2_date : null
|
|
const packageDate = (data.package_date && data.package_date !== 'null') ? data.package_date : null
|
|
|
|
await c.env.DB.prepare(`
|
|
UPDATE status_checkboxes
|
|
SET material_date = ?,
|
|
material2_date = ?,
|
|
package_date = ?,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE record_id = ?
|
|
`).bind(materialDate, material2Date, packageDate, id).run()
|
|
}
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating record:', error)
|
|
return c.json({ error: 'Failed to update record' }, 500)
|
|
}
|
|
})
|
|
|
|
// Get single record
|
|
app.get('/api/records/:id', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const id = c.req.param('id')
|
|
|
|
const record = await c.env.DB.prepare(`
|
|
SELECT * FROM production_records WHERE id = ? AND deleted_at IS NULL
|
|
`).bind(id).first()
|
|
|
|
if (!record) {
|
|
return c.json({ error: 'Record not found' }, 404)
|
|
}
|
|
|
|
return c.json(record)
|
|
} catch (error) {
|
|
console.error('Error fetching record:', error)
|
|
return c.json({ error: 'Failed to fetch record' }, 500)
|
|
}
|
|
})
|
|
|
|
// Delete record (soft delete)
|
|
app.delete('/api/records/:id', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const id = c.req.param('id')
|
|
const userId = c.get('userId')
|
|
|
|
console.log('[DELETE] Deleting record:', id, 'by user:', userId)
|
|
|
|
await c.env.DB.prepare(`
|
|
UPDATE production_records
|
|
SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
|
|
WHERE id = ?
|
|
`).bind(id).run()
|
|
|
|
console.log('[DELETE] Record deleted successfully:', id)
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error deleting record:', error)
|
|
return c.json({ error: 'Failed to delete record' }, 500)
|
|
}
|
|
})
|
|
|
|
// ==================== STATUS CHECKBOX ROUTES ====================
|
|
|
|
// Toggle date - simplified endpoint for frontend compatibility
|
|
app.patch('/api/records/:id/status', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const { field, date } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
console.log(`[TOGGLE] recordId=${recordId}, field=${field}, date=${JSON.stringify(date)}`)
|
|
|
|
// Field name with _date suffix for database column
|
|
const dbField = `${field}_date`
|
|
|
|
// Get old date for audit
|
|
const oldRecord = await c.env.DB.prepare(
|
|
`SELECT ${dbField} FROM status_checkboxes WHERE record_id = ?`
|
|
).bind(recordId).first()
|
|
|
|
// Check if ready or issued fields are blocked by error flags
|
|
if (field === 'ready' || field === 'issued') {
|
|
const statusCheckbox = await c.env.DB.prepare(
|
|
'SELECT worksheets_error, cutting_error, glazing_error, ready_error, issued_error FROM status_checkboxes WHERE record_id = ?'
|
|
).bind(recordId).first()
|
|
|
|
const hasErrorFlags = statusCheckbox && (
|
|
statusCheckbox.worksheets_error ||
|
|
statusCheckbox.cutting_error ||
|
|
statusCheckbox.glazing_error ||
|
|
statusCheckbox.ready_error ||
|
|
statusCheckbox.issued_error
|
|
)
|
|
|
|
if (hasErrorFlags) {
|
|
return c.json({
|
|
error: 'blocked',
|
|
message: 'Vigade märked on seatud (punased kolmnurgad)'
|
|
}, 403)
|
|
}
|
|
}
|
|
|
|
// Toggle logic:
|
|
// 1. If date is null/empty → check if cell is empty → add today's date OR clear
|
|
// 2. If date matches current date → toggle off (clear)
|
|
// 3. Otherwise → use provided date
|
|
let newDate: string | null
|
|
if (!date || date === 'null') {
|
|
// null/empty clicked
|
|
if (oldRecord?.[dbField]) {
|
|
// Cell has date → clear it
|
|
newDate = null
|
|
} else {
|
|
// Cell is empty → add today's date
|
|
newDate = new Date().toISOString().split('T')[0]
|
|
}
|
|
} else if (date === oldRecord?.[dbField]) {
|
|
// Same date as current → toggle off (clear)
|
|
newDate = null
|
|
} else {
|
|
// Different date provided → use it
|
|
newDate = date
|
|
}
|
|
|
|
await c.env.DB.prepare(
|
|
`UPDATE status_checkboxes SET ${dbField} = ? WHERE record_id = ?`
|
|
).bind(newDate, recordId).run()
|
|
|
|
// Log to audit
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'toggle_status')
|
|
`).bind(userId || null, recordId, field, oldRecord?.[dbField] || null, newDate).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error toggling status:', error)
|
|
return c.json({ error: 'Failed to toggle status' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update status checkbox date
|
|
app.patch('/api/status/:recordId/:field', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const field = c.req.param('field')
|
|
const { date } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
// Get old date for audit
|
|
const oldRecord = await c.env.DB.prepare(
|
|
`SELECT ${field}_date FROM status_checkboxes WHERE record_id = ?`
|
|
).bind(recordId).first()
|
|
|
|
// Check if ready or issued fields are blocked by error flags
|
|
if (field === 'ready' || field === 'issued') {
|
|
const statusCheckbox = await c.env.DB.prepare(
|
|
'SELECT worksheets_error, cutting_error, glazing_error, ready_error, issued_error FROM status_checkboxes WHERE record_id = ?'
|
|
).bind(recordId).first()
|
|
|
|
const hasErrorFlags = statusCheckbox && (
|
|
statusCheckbox.worksheets_error ||
|
|
statusCheckbox.cutting_error ||
|
|
statusCheckbox.glazing_error ||
|
|
statusCheckbox.ready_error ||
|
|
statusCheckbox.issued_error
|
|
)
|
|
|
|
if (hasErrorFlags) {
|
|
return c.json({
|
|
error: 'Väli blokeeritud',
|
|
blocked: true,
|
|
reason: 'Vigade märked on seatud (punased kolmnurgad)'
|
|
}, 400)
|
|
}
|
|
}
|
|
|
|
// Update the date
|
|
await c.env.DB.prepare(
|
|
`UPDATE status_checkboxes SET ${field}_date = ? WHERE record_id = ?`
|
|
).bind(date, recordId).run()
|
|
|
|
// Log to audit
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_status')
|
|
`).bind(userId || null, recordId, field, oldRecord?.[`${field}_date`] || null, date).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating status:', error)
|
|
return c.json({ error: 'Failed to update status' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update error flag
|
|
app.patch('/api/status/:recordId/:field/error', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const field = c.req.param('field')
|
|
const { value } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
await c.env.DB.prepare(
|
|
`UPDATE status_checkboxes SET ${field}_error = ? WHERE record_id = ?`
|
|
).bind(value ? 1 : 0, recordId).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating error flag:', error)
|
|
return c.json({ error: 'Failed to update error flag' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update confirmation flag
|
|
app.patch('/api/status/:recordId/:field/confirm', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const field = c.req.param('field')
|
|
const { value } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
|
|
await c.env.DB.prepare(
|
|
`UPDATE status_checkboxes SET ${field}_confirmed = ? WHERE record_id = ?`
|
|
).bind(value ? 1 : 0, recordId).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating confirmation flag:', error)
|
|
return c.json({ error: 'Failed to update confirmation flag' }, 500)
|
|
}
|
|
})
|
|
|
|
// ==================== ADDITIONAL RECORD ROUTES ====================
|
|
|
|
// Worksheets cycle (3-step: empty -> confirmed -> with date -> empty)
|
|
app.patch('/api/records/:id/worksheets-cycle', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const userId = c.get('userId')
|
|
|
|
// Get current worksheets state
|
|
const statusRecord = await c.env.DB.prepare(
|
|
'SELECT worksheets_date, worksheets_confirmed FROM status_checkboxes WHERE record_id = ?'
|
|
).bind(recordId).first()
|
|
|
|
let newDate = null
|
|
let newConfirmed = 0
|
|
|
|
// 3-step cycle logic:
|
|
// Step 1: empty (null date, confirmed=0) -> gray with date (date, confirmed=0)
|
|
// Step 2: gray with date (date, confirmed=0) -> green with date (date, confirmed=1)
|
|
// Step 3: green with date (date, confirmed=1) -> empty (null date, confirmed=0)
|
|
if (!statusRecord?.worksheets_date) {
|
|
// Step 1: empty -> gray with date
|
|
newConfirmed = 0
|
|
newDate = new Date().toISOString().split('T')[0]
|
|
} else if (statusRecord.worksheets_confirmed === 0) {
|
|
// Step 2: gray with date -> green with date
|
|
newConfirmed = 1
|
|
newDate = statusRecord.worksheets_date // Keep existing date
|
|
} else {
|
|
// Step 3: green with date -> empty
|
|
newConfirmed = 0
|
|
newDate = null
|
|
}
|
|
|
|
await c.env.DB.prepare(
|
|
'UPDATE status_checkboxes SET worksheets_date = ?, worksheets_confirmed = ? WHERE record_id = ?'
|
|
).bind(newDate, newConfirmed, recordId).run()
|
|
|
|
// Log to audit
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'worksheets_cycle')
|
|
`).bind(userId || null, recordId, 'worksheets', statusRecord?.worksheets_date || '', newDate || '').run()
|
|
|
|
return c.json({ success: true, date: newDate, confirmed: newConfirmed })
|
|
} catch (error) {
|
|
console.error('Error cycling worksheets:', error)
|
|
return c.json({ error: 'Failed to cycle worksheets' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update notes
|
|
app.patch('/api/records/:id/notes', authMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const { notes } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
const userRole = c.get('role')
|
|
|
|
// Only admin can edit notes
|
|
if (userRole !== 'admin') {
|
|
return c.json({ error: 'Permission denied. Only admin can edit notes.' }, 403)
|
|
}
|
|
|
|
await c.env.DB.prepare(
|
|
'UPDATE production_records SET notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
).bind(notes, recordId).run()
|
|
|
|
// Log to audit
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_notes')
|
|
`).bind(userId || null, recordId, 'notes', '', notes).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating notes:', error)
|
|
return c.json({ error: 'Failed to update notes' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update problems and error flags
|
|
app.patch('/api/records/:id/problems', authMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const { problems, errorFlags } = await c.req.json()
|
|
const userId = c.get('userId')
|
|
const userRole = c.get('role')
|
|
|
|
// User and admin can edit problems
|
|
if (userRole !== 'admin' && userRole !== 'user') {
|
|
return c.json({ error: 'Permission denied. Only admin and user can edit problems.' }, 403)
|
|
}
|
|
|
|
// Update problems text
|
|
await c.env.DB.prepare(
|
|
'UPDATE production_records SET problems = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
|
).bind(problems, recordId).run()
|
|
|
|
// Update error flags
|
|
await c.env.DB.prepare(`
|
|
UPDATE status_checkboxes
|
|
SET worksheets_error = ?,
|
|
cutting_error = ?,
|
|
glazing_error = ?,
|
|
ready_error = ?,
|
|
issued_error = ?
|
|
WHERE record_id = ?
|
|
`).bind(
|
|
errorFlags.worksheets ? 1 : 0,
|
|
errorFlags.cutting ? 1 : 0,
|
|
errorFlags.glazing ? 1 : 0,
|
|
errorFlags.ready ? 1 : 0,
|
|
errorFlags.issued ? 1 : 0,
|
|
recordId
|
|
).run()
|
|
|
|
// Log to audit
|
|
await c.env.DB.prepare(`
|
|
INSERT INTO audit_log (user_id, record_id, field, old_value, new_value, action)
|
|
VALUES (?, ?, ?, ?, ?, 'update_problems')
|
|
`).bind(userId || null, recordId, 'problems', '', problems).run()
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating problems:', error)
|
|
return c.json({ error: 'Failed to update problems' }, 500)
|
|
}
|
|
})
|
|
|
|
// Toggle material confirmed
|
|
app.patch('/api/records/:id/material-confirmed', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
console.log('[MAT1] Toggle request for record:', recordId)
|
|
|
|
// Get current value
|
|
const current = await c.env.DB.prepare(
|
|
'SELECT material_confirmed FROM status_checkboxes WHERE record_id = ?'
|
|
).bind(recordId).first()
|
|
|
|
console.log('[MAT1] Current value:', current?.material_confirmed)
|
|
|
|
// Toggle value
|
|
const newValue = current?.material_confirmed === 1 ? 0 : 1
|
|
console.log('[MAT1] New value:', newValue)
|
|
|
|
await c.env.DB.prepare(
|
|
'UPDATE status_checkboxes SET material_confirmed = ? WHERE record_id = ?'
|
|
).bind(newValue, recordId).run()
|
|
|
|
console.log('[MAT1] Update completed successfully')
|
|
return c.json({ success: true, newValue })
|
|
} catch (error) {
|
|
console.error('[MAT1] Error updating material confirmed:', error)
|
|
return c.json({ error: 'Failed to update material confirmed' }, 500)
|
|
}
|
|
})
|
|
|
|
// Toggle material2 confirmed
|
|
app.patch('/api/records/:id/material2-confirmed', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
console.log('[MAT2] Toggle request for record:', recordId)
|
|
|
|
// Get current value
|
|
const current = await c.env.DB.prepare(
|
|
'SELECT material2_confirmed FROM status_checkboxes WHERE record_id = ?'
|
|
).bind(recordId).first()
|
|
|
|
console.log('[MAT2] Current value:', current?.material2_confirmed)
|
|
|
|
// Toggle value
|
|
const newValue = current?.material2_confirmed === 1 ? 0 : 1
|
|
console.log('[MAT2] New value:', newValue)
|
|
|
|
await c.env.DB.prepare(
|
|
'UPDATE status_checkboxes SET material2_confirmed = ? WHERE record_id = ?'
|
|
).bind(newValue, recordId).run()
|
|
|
|
console.log('[MAT2] Update completed successfully')
|
|
return c.json({ success: true, newValue })
|
|
} catch (error) {
|
|
console.error('[MAT2] Error updating material2 confirmed:', error)
|
|
return c.json({ error: 'Failed to update material2 confirmed' }, 500)
|
|
}
|
|
})
|
|
|
|
// Update price paid status (for invoice tracking)
|
|
app.patch('/api/records/:id/price-paid', optionalAuthMiddleware, async (c) => {
|
|
try {
|
|
const recordId = c.req.param('id')
|
|
const { paid } = await c.req.json()
|
|
|
|
// You might want to add a 'paid' field to production_records table
|
|
// For now, we'll just return success
|
|
// TODO: Add paid field to schema if needed
|
|
|
|
return c.json({ success: true })
|
|
} catch (error) {
|
|
console.error('Error updating price paid:', error)
|
|
return c.json({ error: 'Failed to update price paid' }, 500)
|
|
}
|
|
})
|
|
|
|
// ==================== DEFAULT ROUTE ====================
|
|
|
|
|
|
// ==================== MAIN PAGE - ORIGINAL HTML FROM ARCHIVE ====================
|
|
app.get('/', (c) => {
|
|
return c.html(ORIGINAL_HTML)
|
|
})
|
|
|
|
// Test page for debugging clicks
|
|
app.get('/test-click', (c) => {
|
|
return c.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>`)
|
|
})
|
|
|
|
|
|
export default app
|