mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-07 21:33:18 +01:00
move project task count into its own sortable column
Also removes the ring around the project color dot, shrinks it slightly and tightens its spacing to the name.
This commit is contained in:
@@ -10,13 +10,21 @@ import Pagination from '@/packages/ui/src/Pagination.vue';
|
||||
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
|
||||
|
||||
export type SortColumn =
|
||||
'name' | 'client_name' | 'spent_time' | 'progress' | 'billable_rate' | 'status' | 'visibility';
|
||||
| 'name'
|
||||
| 'tasks'
|
||||
| 'client_name'
|
||||
| 'spent_time'
|
||||
| 'progress'
|
||||
| 'billable_rate'
|
||||
| 'status'
|
||||
| 'visibility';
|
||||
export type { SortDirection } from '@/utils/useSortableTable';
|
||||
import { canCreateProjects } from '@/utils/permissions';
|
||||
import type { CreateProjectBody, Project, Client, CreateClientBody } from '@/packages/api/src';
|
||||
import { useProjectsStore } from '@/utils/useProjects';
|
||||
import { useClientsStore } from '@/utils/useClients';
|
||||
import { useClientsQuery } from '@/utils/useClientsQuery';
|
||||
import { useTasksQuery } from '@/utils/useTasksQuery';
|
||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||
@@ -56,6 +64,16 @@ const clientNameMap = computed(() => {
|
||||
return map;
|
||||
});
|
||||
|
||||
const { tasks } = useTasksQuery();
|
||||
|
||||
const taskCountMap = computed(() => {
|
||||
const map = new Map<string, number>();
|
||||
tasks.value.forEach((task) => {
|
||||
map.set(task.project_id, (map.get(task.project_id) ?? 0) + 1);
|
||||
});
|
||||
return map;
|
||||
});
|
||||
|
||||
// Define column accessors for sorting.
|
||||
// Numeric columns use sortDescFirst so that the first click (chevron down) sorts highest-first,
|
||||
// while text columns default to ascending (A-Z) on first click (chevron down).
|
||||
@@ -64,6 +82,11 @@ const columns = computed<SortableColumnDef<Project, SortColumn>[]>(() => [
|
||||
id: 'name',
|
||||
accessorFn: (row: Project) => row.name.toLowerCase(),
|
||||
},
|
||||
{
|
||||
id: 'tasks',
|
||||
sortDescFirst: true,
|
||||
accessorFn: (row: Project) => taskCountMap.value.get(row.id) ?? 0,
|
||||
},
|
||||
{
|
||||
id: 'client_name',
|
||||
accessorFn: (row: Project) => {
|
||||
@@ -161,7 +184,7 @@ async function createClient(client: CreateClientBody): Promise<Client | undefine
|
||||
}
|
||||
|
||||
const gridTemplate = computed(() => {
|
||||
return `grid-template-columns: minmax(300px, 1fr) minmax(150px, auto) minmax(140px, auto) minmax(130px, auto) ${props.showBillableRate ? 'minmax(130px, auto)' : ''} minmax(120px, auto) minmax(120px, auto) 80px;`;
|
||||
return `grid-template-columns: minmax(300px, 1fr) minmax(100px, auto) minmax(150px, auto) minmax(140px, auto) minmax(130px, auto) ${props.showBillableRate ? 'minmax(130px, auto)' : ''} minmax(120px, auto) minmax(120px, auto) 80px;`;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ function handleSort(column: SortColumn) {
|
||||
@sort="handleSort">
|
||||
Name
|
||||
</SortableTableHeaderCell>
|
||||
<SortableTableHeaderCell column="tasks" v-bind="sortState" @sort="handleSort">
|
||||
Tasks
|
||||
</SortableTableHeaderCell>
|
||||
<SortableTableHeaderCell column="client_name" v-bind="sortState" @sort="handleSort">
|
||||
Client
|
||||
</SortableTableHeaderCell>
|
||||
|
||||
@@ -88,17 +88,19 @@ const showEditProjectModal = ref(false);
|
||||
<ContextMenuTrigger as-child>
|
||||
<TableRow :href="route('projects.show', { project: project.id })">
|
||||
<div
|
||||
class="whitespace-nowrap min-w-0 flex items-center space-x-5 py-4 pr-3 text-sm font-medium text-text-primary pl-2 sm:pl-4 lg:pl-6">
|
||||
class="whitespace-nowrap min-w-0 flex items-center space-x-3 py-4 pr-3 text-sm font-medium text-text-primary pl-2 sm:pl-4 lg:pl-6">
|
||||
<div
|
||||
:style="{
|
||||
backgroundColor: project.color,
|
||||
boxShadow: `var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) ${project.color}30`,
|
||||
}"
|
||||
class="w-3 h-3 ml-1 rounded-full"></div>
|
||||
:style="{ backgroundColor: project.color }"
|
||||
class="w-2.5 h-2.5 ml-1 rounded-full"></div>
|
||||
<span class="overflow-ellipsis overflow-hidden">
|
||||
{{ project.name }}
|
||||
</span>
|
||||
<span class="text-text-secondary"> {{ projectTasksCount }} Tasks </span>
|
||||
</div>
|
||||
<div class="whitespace-nowrap px-3 py-4 text-sm text-text-primary">
|
||||
<span v-if="projectTasksCount">
|
||||
{{ projectTasksCount }} {{ projectTasksCount === 1 ? 'Task' : 'Tasks' }}
|
||||
</span>
|
||||
<span v-else class="text-text-tertiary">--</span>
|
||||
</div>
|
||||
<div class="whitespace-nowrap min-w-0 px-3 py-4 text-sm text-text-primary">
|
||||
<div v-if="project.client_id" class="overflow-ellipsis overflow-hidden">
|
||||
|
||||
Reference in New Issue
Block a user