Categories - the categories of each kind: expense, income, * referral and ticket, and the interface, asset status, software, rack and * note types. * * Each shows as it looks on records, with its description and what uses it * (opening the list of them, where there is one). A category can be * changed, merged into another of its kind, archived - it stays on its * records but isn't offered for new ones - or, while nothing uses it, * deleted. The kinds pickers list in an order set here are dragged into it. * The work is in functions/categories.php; the handlers are * admin/post/category.php. */ require_once "includes/inc_all_admin.php"; $kinds = categoryKinds(); $kind = isset($kinds[$_GET['category'] ?? '']) ? (string) $_GET['category'] : 'Expense'; $config = categoryKind($kind); $show_archived = isset($_GET['archived']); $search = trim((string) ($_GET['q'] ?? '')); // How the list is sorted - the order pickers use first, for the kinds that keep one $sorts = ($config['ordered'] ? ['order' => ['Order', 'fa-sort-numeric-down', 'The order they are offered in']] : []) + ['name' => ['Name', 'fa-sort-alpha-down', 'A to Z'], 'use' => ['Most used', 'fa-sort-amount-down', 'The ones on the most records first']]; $sort_by = isset($sorts[$_GET['sort'] ?? '']) ? (string) $_GET['sort'] : array_key_first($sorts); $default_sort = array_key_first($sorts); $usage = categoryUsage($kind); $empty_use = ['all' => 0, 'archived' => 0, 'uses' => []]; $categories = []; foreach (categoryRows($kind) as $category_id => $row) { if ((bool) $row['category_archived_at'] !== $show_archived) { continue; } if ($search !== '' && mb_stripos($row['category_name'], $search) === false && mb_stripos((string) $row['category_description'], $search) === false) { continue; } $row['used'] = $usage[$category_id] ?? $empty_use; $categories[$category_id] = $row; } if ($sort_by === 'name') { uasort($categories, function ($a, $b) { return strnatcasecmp($a['category_name'], $b['category_name']); }); } elseif ($sort_by === 'use') { uasort($categories, function ($a, $b) { return $b['used']['all'] <=> $a['used']['all'] ?: strnatcasecmp($a['category_name'], $b['category_name']); }); } // Names records have that aren't categories - a kind held by name, in the plain current view $loose = !$show_archived && $search === '' ? categoryLooseNames($kind) : []; // Dragged into order - in the order view, with the whole current list showing $can_reorder = $config['ordered'] && $sort_by === 'order' && !$show_archived && $search === '' && count($categories) > 1; // Current categories of each kind for the nav, and this kind's archived ones for the tab $kind_counts = []; $archived_count = 0; $sql = mysqli_query($mysqli, "SELECT category_type, SUM(category_archived_at IS NULL), SUM(category_archived_at IS NOT NULL) FROM categories GROUP BY category_type"); while ($row = mysqli_fetch_row($sql)) { $kind_counts[$row[0]] = intval($row[1]); if ($row[0] === $kind) { $archived_count = intval($row[2]); } } // This page with some of its query changed - null drops a key $page_url = function (array $change = []) use ($kind, $show_archived, $sort_by, $default_sort, $search) { $query = array_merge(['category' => $kind, 'q' => $search !== '' ? $search : null, 'sort' => $sort_by !== $default_sort ? $sort_by : null, 'archived' => $show_archived ? 1 : null], $change); return 'categories.php?' . http_build_query(array_filter($query, function ($v) { return $v !== null; })); }; $new_label = 'New ' . ucwords($config['one']); $add_url = 'modals/category/category_add.php?category=' . urlencode($kind); $csrf = $_SESSION['csrf_token']; ?>

Categories

[$sort_label, $sort_icon, $sort_title]) { ?>
> $row) { $name = escapeHtml($row['category_name']); $used = $row['used']; $edit_url = "modals/category/category_edit.php?id=$category_id"; $used_html = $used['all'] ? categoryUsageHtml($row, $used) : '' . ($row['category_archived_at'] ? 'Nothing uses it' : 'Nothing yet') . ''; $blocker = categoryDeleteBlocker($row, $used); $position = array_search($category_id, $positions, true); ?>
Order Used on Action
Archived
Drag them into the order they're offered in.
Clear the search to drag them into order.
$config['many'], 'title' => $show_archived ? 'No archived ' . $config['many'] : 'No ' . $config['many'] . ' yet', 'text' => $show_archived ? 'An archived category stays on its records, but isn\'t offered for new ones.' : $config['about'], 'icon' => $config['icon'], 'filtered' => $search !== '', 'clear_url' => $page_url(['q' => null]), 'actions' => $show_archived ? [] : [['label' => $new_label, 'modal' => $add_url]], ]) ?>
In use, but not set up here

Records have these, but they aren't - so they can't be picked again, and have no color or icon. Add one to make it ; to fold it into another, add it, then merge it.

    $loose_used) { ?>
  • 0, 'category_name' => $loose_name, 'category_type' => $kind], $loose_used) ?>