0"; // The views - Over budget only once someone uses budgets $budgets_in_use = (bool) mysqli_num_rows(mysqli_query($mysqli, "SELECT 1 FROM projects WHERE project_budget_hours > 0 LIMIT 1")); $shows = projectViews($budgets_in_use); $show = isset($shows[$_GET['show'] ?? '']) ? ($_GET['show'] ?? '') : ''; $show_query = $shows[$show]['where'] ? 'AND ' . $shows[$show]['where'] : ''; // Only these sort - an unknown column would take the page down. Completed ones, newest done first. if (!isset($_GET['sort']) && in_array($show, ['completed', 'archived'], true)) { $sort = 'project_completed_at'; $order = 'DESC'; $disp = 'ASC'; $order_icon = ""; } $sortable_columns = [ 'project_due' => "projects.project_due IS NULL, projects.project_due $order, project_name ASC", 'project_name' => "project_name $order", 'project_number' => "project_number $order", 'client_name' => "project_client_id = 0 $order, client_name $order, project_name ASC", 'user_name' => "project_manager = 0 $order, user_name $order, project_name ASC", 'project_created_at' => "project_created_at $order", 'project_completed_at' => "project_completed_at IS NULL, project_completed_at $order", 'activity' => "COALESCE(project_last_activity, project_created_at) $order", ]; if (!isset($sortable_columns[$sort])) { $sort = 'project_due'; } $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS projects.*, client_name, user_name, user_avatar, contact_name, contact_photo, " . projectLastActivitySql() . " AS project_last_activity FROM projects $joins WHERE $filters $show_query ORDER BY {$sortable_columns[$sort]}, project_id DESC LIMIT $record_from, $record_to"); $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $projects = []; while ($row = mysqli_fetch_assoc($sql)) { $projects[intval($row['project_id'])] = $row; } $stats = projectStats(array_keys($projects)); // How many of each, for the pills - over what the filters leave $count_sql = []; foreach ($shows as $key => $s) { $count_sql[] = $s['where'] === '' ? 'COUNT(*)' : "COALESCE(SUM({$s['where']}), 0)"; } $row = mysqli_fetch_row(mysqli_query($mysqli, "SELECT " . implode(', ', $count_sql) . " FROM projects $joins WHERE $filters")); $counts = []; foreach (array_keys($shows) as $i => $key) { $counts[$key] = intval($row[$i]); } $filter_url = function ($changes) { $params = array_merge($_GET, $changes); unset($params['page']); return '?' . http_build_query(array_filter($params, function ($v) { return $v !== '' && $v !== null; })); }; $sort_link = function ($column, $label) use ($url_query_strings_sort, $sort, $disp, $order_icon) { return '' . $label . ($sort === $column ? " $order_icon" : '') . ''; }; $hidden_filter_count = $date_filter_active ? 1 : 0; ?>

Projects

Created:
text-nowrap table-light"> $row) { $row_client_id = intval($row['project_client_id']); $s = $stats[$project_id]; $is_open = projectIsOpen($row); $state = projectState($row); $flags = projectFlags($row, $s); $flag_keys = array_column($flags, 'key'); $edge = array_intersect($flag_keys, ['overdue', 'over_budget']) ? 'danger' : (array_intersect($flag_keys, ['stalled', 'soon']) ? 'warning' : ''); $url = escapeHtml(projectUrl($project_id, $client_url ? $client_id : $row_client_id)); $reference = escapeHtml(projectReference($row)); $manager = formatAssignee($row['project_manager'], $row['user_name'], $row['user_avatar'], false, 'No manager'); $description = trim(strtok((string) $row['project_description'], "\n")); $last = $row['project_last_activity']; ?> ">
Progress
Ready to close Stalled
/ tickets done
Internal
-
unbilled
-' ?>
Starts
Was due
open
No tickets yet
'projects', 'icon' => 'fa-project-diagram', 'filtered' => $q !== '' || $manager_filter !== '' || $client_filter !== '' || $date_filter_active, 'archived' => $show === 'archived', 'views' => ['show'], 'view' => emptyStatePill($shows, $show), 'text' => 'Onboarding a new client or a server refresh is a good first one.', 'actions' => $can_edit ? [['label' => 'New Project', 'modal' => "modals/project/project_add.php?$client_url", 'size' => 'lg']] : [], ]; require_once "../includes/filter_footer.php"; ?>