mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-16 17:53: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';
|
import LoadingSpinner from '@/packages/ui/src/LoadingSpinner.vue';
|
||||||
|
|
||||||
export type SortColumn =
|
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';
|
export type { SortDirection } from '@/utils/useSortableTable';
|
||||||
import { canCreateProjects } from '@/utils/permissions';
|
import { canCreateProjects } from '@/utils/permissions';
|
||||||
import type { CreateProjectBody, Project, Client, CreateClientBody } from '@/packages/api/src';
|
import type { CreateProjectBody, Project, Client, CreateClientBody } from '@/packages/api/src';
|
||||||
import { useProjectsStore } from '@/utils/useProjects';
|
import { useProjectsStore } from '@/utils/useProjects';
|
||||||
import { useClientsStore } from '@/utils/useClients';
|
import { useClientsStore } from '@/utils/useClients';
|
||||||
import { useClientsQuery } from '@/utils/useClientsQuery';
|
import { useClientsQuery } from '@/utils/useClientsQuery';
|
||||||
|
import { useTasksQuery } from '@/utils/useTasksQuery';
|
||||||
import { getOrganizationCurrencyString } from '@/utils/money';
|
import { getOrganizationCurrencyString } from '@/utils/money';
|
||||||
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
|
||||||
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
|
||||||
@@ -56,6 +64,16 @@ const clientNameMap = computed(() => {
|
|||||||
return map;
|
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.
|
// Define column accessors for sorting.
|
||||||
// Numeric columns use sortDescFirst so that the first click (chevron down) sorts highest-first,
|
// 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).
|
// 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',
|
id: 'name',
|
||||||
accessorFn: (row: Project) => row.name.toLowerCase(),
|
accessorFn: (row: Project) => row.name.toLowerCase(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'tasks',
|
||||||
|
sortDescFirst: true,
|
||||||
|
accessorFn: (row: Project) => taskCountMap.value.get(row.id) ?? 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'client_name',
|
id: 'client_name',
|
||||||
accessorFn: (row: Project) => {
|
accessorFn: (row: Project) => {
|
||||||
@@ -161,7 +184,7 @@ async function createClient(client: CreateClientBody): Promise<Client | undefine
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gridTemplate = computed(() => {
|
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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ function handleSort(column: SortColumn) {
|
|||||||
@sort="handleSort">
|
@sort="handleSort">
|
||||||
Name
|
Name
|
||||||
</SortableTableHeaderCell>
|
</SortableTableHeaderCell>
|
||||||
|
<SortableTableHeaderCell column="tasks" v-bind="sortState" @sort="handleSort">
|
||||||
|
Tasks
|
||||||
|
</SortableTableHeaderCell>
|
||||||
<SortableTableHeaderCell column="client_name" v-bind="sortState" @sort="handleSort">
|
<SortableTableHeaderCell column="client_name" v-bind="sortState" @sort="handleSort">
|
||||||
Client
|
Client
|
||||||
</SortableTableHeaderCell>
|
</SortableTableHeaderCell>
|
||||||
|
|||||||
@@ -88,17 +88,19 @@ const showEditProjectModal = ref(false);
|
|||||||
<ContextMenuTrigger as-child>
|
<ContextMenuTrigger as-child>
|
||||||
<TableRow :href="route('projects.show', { project: project.id })">
|
<TableRow :href="route('projects.show', { project: project.id })">
|
||||||
<div
|
<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
|
<div
|
||||||
:style="{
|
:style="{ backgroundColor: project.color }"
|
||||||
backgroundColor: project.color,
|
class="w-2.5 h-2.5 ml-1 rounded-full"></div>
|
||||||
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>
|
|
||||||
<span class="overflow-ellipsis overflow-hidden">
|
<span class="overflow-ellipsis overflow-hidden">
|
||||||
{{ project.name }}
|
{{ project.name }}
|
||||||
</span>
|
</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>
|
||||||
<div class="whitespace-nowrap min-w-0 px-3 py-4 text-sm text-text-primary">
|
<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">
|
<div v-if="project.client_id" class="overflow-ellipsis overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user