false, 'ERROR' => 'missing_params', 'MESSAGE' => 'BusinessID is required']); } try { $categoryID = (int) ($data['TaskCategoryID'] ?? 0); $categoryName = trim($data['Name'] ?? ''); $categoryColor = trim($data['Color'] ?? '#6366f1'); if (empty($categoryName)) { apiAbort(['OK' => false, 'ERROR' => 'missing_params', 'MESSAGE' => 'Name is required']); } // Validate color format - accept #RRGGBB or RRGGBB if (!preg_match('/^#[0-9A-Fa-f]{6}$/', $categoryColor)) { if (preg_match('/^[0-9A-Fa-f]{6}$/', $categoryColor)) { $categoryColor = '#' . $categoryColor; } else { $categoryColor = '#6366f1'; } } if ($categoryID > 0) { // UPDATE $qCheck = queryOne(" SELECT ID FROM TaskCategories WHERE ID = ? AND BusinessID = ? ", [$categoryID, $bizID]); if (!$qCheck) { apiAbort(['OK' => false, 'ERROR' => 'not_found', 'MESSAGE' => 'Category not found']); } queryTimed(" UPDATE TaskCategories SET Name = ?, Color = ? WHERE ID = ? ", [$categoryName, $categoryColor, $categoryID]); jsonResponse(['OK' => true, 'CATEGORY_ID' => $categoryID, 'MESSAGE' => 'Category updated']); } else { // INSERT queryTimed(" INSERT INTO TaskCategories (BusinessID, Name, Color) VALUES (?, ?, ?) ", [$bizID, $categoryName, $categoryColor]); $newID = (int) lastInsertId(); jsonResponse(['OK' => true, 'CATEGORY_ID' => $newID, 'MESSAGE' => 'Category created']); } } catch (Exception $e) { jsonResponse(['OK' => false, 'ERROR' => 'server_error', 'MESSAGE' => $e->getMessage()]); }