mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 19:57:46 +01:00
Add number of records for each board column (#122)
This commit is contained in:
@@ -19,10 +19,8 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
public async handleQuery(
|
public async handleQuery(
|
||||||
input: RecordFieldValueCountQueryInput
|
input: RecordFieldValueCountQueryInput
|
||||||
): Promise<RecordFieldValueCountQueryOutput> {
|
): Promise<RecordFieldValueCountQueryOutput> {
|
||||||
const counts = await this.fetchFieldValueCounts(input);
|
const result = await this.fetchFieldValueCounts(input);
|
||||||
return {
|
return result;
|
||||||
items: counts,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkForChanges(
|
public async checkForChanges(
|
||||||
@@ -37,7 +35,7 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
hasChanges: true,
|
hasChanges: true,
|
||||||
result: { items: [] },
|
result: { values: [], nullCount: 0 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +85,16 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
event.accountId === input.accountId &&
|
event.accountId === input.accountId &&
|
||||||
event.workspaceId === input.workspaceId
|
event.workspaceId === input.workspaceId
|
||||||
) {
|
) {
|
||||||
|
if (
|
||||||
|
event.node.type === 'database' &&
|
||||||
|
event.node.id === input.databaseId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
hasChanges: true,
|
||||||
|
result: { values: [], nullCount: 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
event.node.type === 'record' &&
|
event.node.type === 'record' &&
|
||||||
event.node.attributes.databaseId === input.databaseId
|
event.node.attributes.databaseId === input.databaseId
|
||||||
@@ -97,16 +105,6 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
result: newResult,
|
result: newResult,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
event.node.type === 'database' &&
|
|
||||||
event.node.id === input.databaseId
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
hasChanges: true,
|
|
||||||
result: { items: [] },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -116,12 +114,15 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
|
|
||||||
private async fetchFieldValueCounts(
|
private async fetchFieldValueCounts(
|
||||||
input: RecordFieldValueCountQueryInput
|
input: RecordFieldValueCountQueryInput
|
||||||
): Promise<RecordFieldValueCount[]> {
|
): Promise<RecordFieldValueCountQueryOutput> {
|
||||||
const database = await this.fetchDatabase(input);
|
const database = await this.fetchDatabase(input);
|
||||||
const field = database.attributes.fields[input.fieldId];
|
const field = database.attributes.fields[input.fieldId];
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
return [];
|
return {
|
||||||
|
values: [],
|
||||||
|
nullCount: 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterQuery = buildFiltersQuery(
|
const filterQuery = buildFiltersQuery(
|
||||||
@@ -137,7 +138,24 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
);
|
);
|
||||||
|
|
||||||
const result = await workspace.database.executeQuery(query);
|
const result = await workspace.database.executeQuery(query);
|
||||||
return result.rows;
|
|
||||||
|
const output: RecordFieldValueCountQueryOutput = {
|
||||||
|
values: [],
|
||||||
|
nullCount: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const row of result.rows) {
|
||||||
|
if (row.value === 'null') {
|
||||||
|
output.nullCount = row.count;
|
||||||
|
} else {
|
||||||
|
output.values.push({
|
||||||
|
value: row.value,
|
||||||
|
count: row.count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildQuery(
|
private buildQuery(
|
||||||
@@ -190,10 +208,23 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
FROM nodes n,
|
FROM nodes n,
|
||||||
json_each(${this.buildFieldSelector(field)})
|
json_each(${this.buildFieldSelector(field)})
|
||||||
WHERE n.parent_id = '${databaseId}'
|
WHERE n.parent_id = '${databaseId}'
|
||||||
AND n.type = 'record'
|
AND n.type = 'record'
|
||||||
AND ${this.buildFieldSelector(field)} IS NOT NULL
|
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
GROUP BY json_each.value
|
GROUP BY json_each.value
|
||||||
|
|
||||||
|
UNION ALL
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
'null' as value,
|
||||||
|
COUNT(*) as count
|
||||||
|
FROM nodes n
|
||||||
|
WHERE n.parent_id = '${databaseId}'
|
||||||
|
AND n.type = 'record'
|
||||||
|
AND (${this.buildFieldSelector(field)} IS NULL
|
||||||
|
OR ${this.buildFieldSelector(field)} = '[]'
|
||||||
|
OR json_array_length(${this.buildFieldSelector(field)}) = 0)
|
||||||
|
${filterQuery}
|
||||||
|
|
||||||
ORDER BY count DESC, value ASC
|
ORDER BY count DESC, value ASC
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -210,7 +241,6 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
FROM nodes n
|
FROM nodes n
|
||||||
WHERE n.parent_id = '${databaseId}'
|
WHERE n.parent_id = '${databaseId}'
|
||||||
AND n.type = 'record'
|
AND n.type = 'record'
|
||||||
AND ${this.buildFieldSelector(field)} IS NOT NULL
|
|
||||||
${filterQuery}
|
${filterQuery}
|
||||||
GROUP BY value
|
GROUP BY value
|
||||||
ORDER BY count DESC, value ASC
|
ORDER BY count DESC, value ASC
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export type RecordFieldValueCount = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type RecordFieldValueCountQueryOutput = {
|
export type RecordFieldValueCountQueryOutput = {
|
||||||
items: RecordFieldValueCount[];
|
values: RecordFieldValueCount[];
|
||||||
|
nullCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module '@colanode/client/queries' {
|
declare module '@colanode/client/queries' {
|
||||||
|
|||||||
@@ -39,13 +39,14 @@ export const BoardViewColumnsCollaborator = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const collaborators = collaboratorCountQuery.data?.items ?? [];
|
const collaborators = collaboratorCountQuery.data?.values ?? [];
|
||||||
const noValueFilter: DatabaseViewFilterAttributes = {
|
const noValueFilter: DatabaseViewFilterAttributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'field',
|
type: 'field',
|
||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
operator: 'is_empty',
|
operator: 'is_empty',
|
||||||
};
|
};
|
||||||
|
const noValueCount = collaboratorCountQuery.data?.nullCount ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -75,6 +76,7 @@ export const BoardViewColumnsCollaborator = ({
|
|||||||
<BoardViewColumnCollaboratorHeader
|
<BoardViewColumnCollaboratorHeader
|
||||||
field={field}
|
field={field}
|
||||||
collaborator={collaborator.value}
|
collaborator={collaborator.value}
|
||||||
|
count={collaborator.count}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
canDrag: (record) => record.canEdit,
|
canDrag: (record) => record.canEdit,
|
||||||
@@ -145,6 +147,7 @@ export const BoardViewColumnsCollaborator = ({
|
|||||||
<BoardViewColumnCollaboratorHeader
|
<BoardViewColumnCollaboratorHeader
|
||||||
field={field}
|
field={field}
|
||||||
collaborator={null}
|
collaborator={null}
|
||||||
|
count={noValueCount}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
canDrag: () => true,
|
canDrag: () => true,
|
||||||
@@ -187,11 +190,13 @@ export const BoardViewColumnsCollaborator = ({
|
|||||||
interface BoardViewColumnCollaboratorHeaderProps {
|
interface BoardViewColumnCollaboratorHeaderProps {
|
||||||
field: CollaboratorFieldAttributes;
|
field: CollaboratorFieldAttributes;
|
||||||
collaborator: string | null;
|
collaborator: string | null;
|
||||||
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BoardViewColumnCollaboratorHeader = ({
|
const BoardViewColumnCollaboratorHeader = ({
|
||||||
field,
|
field,
|
||||||
collaborator,
|
collaborator,
|
||||||
|
count,
|
||||||
}: BoardViewColumnCollaboratorHeaderProps) => {
|
}: BoardViewColumnCollaboratorHeaderProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
|
|
||||||
@@ -212,6 +217,9 @@ const BoardViewColumnCollaboratorHeader = ({
|
|||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<CircleDashed className="size-5" />
|
<CircleDashed className="size-5" />
|
||||||
<p className="text-muted-foreground">No {field.name}</p>
|
<p className="text-muted-foreground">No {field.name}</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -221,6 +229,9 @@ const BoardViewColumnCollaboratorHeader = ({
|
|||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<Spinner className="size-5" />
|
<Spinner className="size-5" />
|
||||||
<p className="text-muted-foreground">Loading...</p>
|
<p className="text-muted-foreground">Loading...</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -231,6 +242,9 @@ const BoardViewColumnCollaboratorHeader = ({
|
|||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<CircleAlert className="size-5" />
|
<CircleAlert className="size-5" />
|
||||||
<p className="text-muted-foreground">Unknown</p>
|
<p className="text-muted-foreground">Unknown</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -244,6 +258,9 @@ const BoardViewColumnCollaboratorHeader = ({
|
|||||||
className="size-5"
|
className="size-5"
|
||||||
/>
|
/>
|
||||||
<p>{user.name}</p>
|
<p>{user.name}</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export const BoardViewColumnsCreatedBy = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = createdByCountQuery.data?.items ?? [];
|
const users = createdByCountQuery.data?.values ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -62,6 +62,7 @@ export const BoardViewColumnsCreatedBy = ({
|
|||||||
<BoardViewColumnCreatedByHeader
|
<BoardViewColumnCreatedByHeader
|
||||||
field={field}
|
field={field}
|
||||||
createdBy={user.value}
|
createdBy={user.value}
|
||||||
|
count={user.count}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
canDrag: () => false,
|
canDrag: () => false,
|
||||||
@@ -79,10 +80,12 @@ export const BoardViewColumnsCreatedBy = ({
|
|||||||
interface BoardViewColumnCreatedByHeaderProps {
|
interface BoardViewColumnCreatedByHeaderProps {
|
||||||
field: CreatedByFieldAttributes;
|
field: CreatedByFieldAttributes;
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
|
count: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BoardViewColumnCreatedByHeader = ({
|
const BoardViewColumnCreatedByHeader = ({
|
||||||
createdBy,
|
createdBy,
|
||||||
|
count,
|
||||||
}: BoardViewColumnCreatedByHeaderProps) => {
|
}: BoardViewColumnCreatedByHeaderProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
|
|
||||||
@@ -98,6 +101,9 @@ const BoardViewColumnCreatedByHeader = ({
|
|||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<Spinner className="size-5" />
|
<Spinner className="size-5" />
|
||||||
<p className="text-muted-foreground">Loading...</p>
|
<p className="text-muted-foreground">Loading...</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -108,6 +114,9 @@ const BoardViewColumnCreatedByHeader = ({
|
|||||||
<div className="flex flex-row gap-2 items-center">
|
<div className="flex flex-row gap-2 items-center">
|
||||||
<CircleAlert className="size-5" />
|
<CircleAlert className="size-5" />
|
||||||
<p className="text-muted-foreground">Unknown</p>
|
<p className="text-muted-foreground">Unknown</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,6 +130,9 @@ const BoardViewColumnCreatedByHeader = ({
|
|||||||
className="size-5"
|
className="size-5"
|
||||||
/>
|
/>
|
||||||
<p>{user.name}</p>
|
<p>{user.name}</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
|
import { CircleDashed } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DatabaseViewFilterAttributes,
|
DatabaseViewFilterAttributes,
|
||||||
FieldValue,
|
FieldValue,
|
||||||
MultiSelectFieldAttributes,
|
MultiSelectFieldAttributes,
|
||||||
|
SelectOptionAttributes,
|
||||||
} from '@colanode/core';
|
} from '@colanode/core';
|
||||||
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
|
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
|
||||||
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
|
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
|
||||||
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
||||||
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
|
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useQuery } from '@colanode/ui/hooks/use-query';
|
||||||
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
||||||
|
|
||||||
interface BoardViewColumnsMultiSelectProps {
|
interface BoardViewColumnsMultiSelectProps {
|
||||||
@@ -19,8 +24,19 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
field,
|
field,
|
||||||
}: BoardViewColumnsMultiSelectProps) => {
|
}: BoardViewColumnsMultiSelectProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const selectOptions = Object.values(field.options ?? {});
|
const database = useDatabase();
|
||||||
|
const view = useDatabaseView();
|
||||||
|
|
||||||
|
const selectOptionCountQuery = useQuery({
|
||||||
|
type: 'record.field.value.count',
|
||||||
|
databaseId: database.id,
|
||||||
|
filters: view.filters,
|
||||||
|
fieldId: field.id,
|
||||||
|
accountId: workspace.accountId,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectOptions = Object.values(field.options ?? {});
|
||||||
const noValueFilter: DatabaseViewFilterAttributes = {
|
const noValueFilter: DatabaseViewFilterAttributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'field',
|
type: 'field',
|
||||||
@@ -28,6 +44,9 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
operator: 'is_empty',
|
operator: 'is_empty',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||||
|
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
||||||
|
|
||||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -45,6 +64,10 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
option.color ?? 'gray'
|
option.color ?? 'gray'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const count =
|
||||||
|
selectOptionCount.find((count) => count.value === option.id)?.count ??
|
||||||
|
0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoardViewContext.Provider
|
<BoardViewContext.Provider
|
||||||
key={option.id}
|
key={option.id}
|
||||||
@@ -60,7 +83,11 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
},
|
},
|
||||||
dragOverClass: draggingClass,
|
dragOverClass: draggingClass,
|
||||||
header: (
|
header: (
|
||||||
<SelectOptionBadge name={option.name} color={option.color} />
|
<BoardViewColumnMultiSelectHeader
|
||||||
|
field={field}
|
||||||
|
option={option}
|
||||||
|
count={count}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
canDrag: (record) => record.canEdit,
|
canDrag: (record) => record.canEdit,
|
||||||
onDragEnd: async (record, value) => {
|
onDragEnd: async (record, value) => {
|
||||||
@@ -126,7 +153,11 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
header: (
|
header: (
|
||||||
<p className="text-sm text-muted-foreground">No {field.name}</p>
|
<BoardViewColumnMultiSelectHeader
|
||||||
|
field={field}
|
||||||
|
option={null}
|
||||||
|
count={noValueCount}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
dragOverClass: noValueDraggingClass,
|
dragOverClass: noValueDraggingClass,
|
||||||
canDrag: () => true,
|
canDrag: () => true,
|
||||||
@@ -165,3 +196,36 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface BoardViewColumnMultiSelectHeaderProps {
|
||||||
|
field: MultiSelectFieldAttributes;
|
||||||
|
option: SelectOptionAttributes | null;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoardViewColumnMultiSelectHeader = ({
|
||||||
|
field,
|
||||||
|
option,
|
||||||
|
count,
|
||||||
|
}: BoardViewColumnMultiSelectHeaderProps) => {
|
||||||
|
if (!option) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<CircleDashed className="size-5" />
|
||||||
|
<p className="text-muted-foreground">No {field.name}</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<SelectOptionBadge name={option.name} color={option.color} />
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
|
import { CircleDashed } from 'lucide-react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DatabaseViewFilterAttributes,
|
DatabaseViewFilterAttributes,
|
||||||
SelectFieldAttributes,
|
SelectFieldAttributes,
|
||||||
|
SelectOptionAttributes,
|
||||||
} from '@colanode/core';
|
} from '@colanode/core';
|
||||||
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
|
import { BoardViewColumn } from '@colanode/ui/components/databases/boards/board-view-column';
|
||||||
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
|
import { SelectOptionBadge } from '@colanode/ui/components/databases/fields/select-option-badge';
|
||||||
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
import { BoardViewContext } from '@colanode/ui/contexts/board-view';
|
||||||
|
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||||
|
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||||
|
import { useQuery } from '@colanode/ui/hooks/use-query';
|
||||||
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
import { getSelectOptionLightColorClass } from '@colanode/ui/lib/databases';
|
||||||
|
|
||||||
interface BoardViewColumnsSelectProps {
|
interface BoardViewColumnsSelectProps {
|
||||||
@@ -18,8 +23,19 @@ export const BoardViewColumnsSelect = ({
|
|||||||
field,
|
field,
|
||||||
}: BoardViewColumnsSelectProps) => {
|
}: BoardViewColumnsSelectProps) => {
|
||||||
const workspace = useWorkspace();
|
const workspace = useWorkspace();
|
||||||
const selectOptions = Object.values(field.options ?? {});
|
const database = useDatabase();
|
||||||
|
const view = useDatabaseView();
|
||||||
|
|
||||||
|
const selectOptionCountQuery = useQuery({
|
||||||
|
type: 'record.field.value.count',
|
||||||
|
databaseId: database.id,
|
||||||
|
filters: view.filters,
|
||||||
|
fieldId: field.id,
|
||||||
|
accountId: workspace.accountId,
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectOptions = Object.values(field.options ?? {});
|
||||||
const noValueFilter: DatabaseViewFilterAttributes = {
|
const noValueFilter: DatabaseViewFilterAttributes = {
|
||||||
id: '1',
|
id: '1',
|
||||||
type: 'field',
|
type: 'field',
|
||||||
@@ -27,6 +43,9 @@ export const BoardViewColumnsSelect = ({
|
|||||||
operator: 'is_empty',
|
operator: 'is_empty',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||||
|
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
||||||
|
|
||||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -44,6 +63,10 @@ export const BoardViewColumnsSelect = ({
|
|||||||
option.color ?? 'gray'
|
option.color ?? 'gray'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const count =
|
||||||
|
selectOptionCount.find((count) => count.value === option.id)?.count ??
|
||||||
|
0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BoardViewContext.Provider
|
<BoardViewContext.Provider
|
||||||
key={option.id}
|
key={option.id}
|
||||||
@@ -59,7 +82,11 @@ export const BoardViewColumnsSelect = ({
|
|||||||
},
|
},
|
||||||
dragOverClass: draggingClass,
|
dragOverClass: draggingClass,
|
||||||
header: (
|
header: (
|
||||||
<SelectOptionBadge name={option.name} color={option.color} />
|
<BoardViewColumnSelectHeader
|
||||||
|
field={field}
|
||||||
|
option={option}
|
||||||
|
count={count}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
canDrag: (record) => record.canEdit,
|
canDrag: (record) => record.canEdit,
|
||||||
onDragEnd: async (record, value) => {
|
onDragEnd: async (record, value) => {
|
||||||
@@ -105,7 +132,11 @@ export const BoardViewColumnsSelect = ({
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
header: (
|
header: (
|
||||||
<p className="text-sm text-muted-foreground">No {field.name}</p>
|
<BoardViewColumnSelectHeader
|
||||||
|
field={field}
|
||||||
|
option={null}
|
||||||
|
count={noValueCount}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
dragOverClass: noValueDraggingClass,
|
dragOverClass: noValueDraggingClass,
|
||||||
canDrag: () => true,
|
canDrag: () => true,
|
||||||
@@ -144,3 +175,36 @@ export const BoardViewColumnsSelect = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface BoardViewColumnSelectHeaderProps {
|
||||||
|
field: SelectFieldAttributes;
|
||||||
|
option: SelectOptionAttributes | null;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoardViewColumnSelectHeader = ({
|
||||||
|
field,
|
||||||
|
option,
|
||||||
|
count,
|
||||||
|
}: BoardViewColumnSelectHeaderProps) => {
|
||||||
|
if (!option) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<CircleDashed className="size-5" />
|
||||||
|
<p className="text-muted-foreground">No {field.name}</p>
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-row gap-2 items-center">
|
||||||
|
<SelectOptionBadge name={option?.name} color={option?.color} />
|
||||||
|
<p className="text-muted-foreground text-sm ml-1">
|
||||||
|
{count.toLocaleString()}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user