mirror of
https://github.com/colanode/colanode.git
synced 2025-12-16 11:47:47 +01:00
Show the count and a list of records that have no value for calendar group field (#124)
This commit is contained in:
@@ -35,7 +35,7 @@ export class RecordFieldValueCountQueryHandler
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: { values: [], nullCount: 0 },
|
||||
result: { values: [], noValueCount: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export class RecordFieldValueCountQueryHandler
|
||||
) {
|
||||
return {
|
||||
hasChanges: true,
|
||||
result: { values: [], nullCount: 0 },
|
||||
result: { values: [], noValueCount: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export class RecordFieldValueCountQueryHandler
|
||||
if (!field) {
|
||||
return {
|
||||
values: [],
|
||||
nullCount: 0,
|
||||
noValueCount: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,12 +141,12 @@ export class RecordFieldValueCountQueryHandler
|
||||
|
||||
const output: RecordFieldValueCountQueryOutput = {
|
||||
values: [],
|
||||
nullCount: 0,
|
||||
noValueCount: 0,
|
||||
};
|
||||
|
||||
for (const row of result.rows) {
|
||||
if (row.value === 'null') {
|
||||
output.nullCount = row.count;
|
||||
output.noValueCount = row.count;
|
||||
} else {
|
||||
output.values.push({
|
||||
value: row.value,
|
||||
|
||||
@@ -16,7 +16,7 @@ export type RecordFieldValueCount = {
|
||||
|
||||
export type RecordFieldValueCountQueryOutput = {
|
||||
values: RecordFieldValueCount[];
|
||||
nullCount: number;
|
||||
noValueCount: number;
|
||||
};
|
||||
|
||||
declare module '@colanode/client/queries' {
|
||||
|
||||
@@ -46,7 +46,7 @@ export const BoardViewColumnsCollaborator = ({
|
||||
fieldId: field.id,
|
||||
operator: 'is_empty',
|
||||
};
|
||||
const noValueCount = collaboratorCountQuery.data?.nullCount ?? 0;
|
||||
const noValueCount = collaboratorCountQuery.data?.noValueCount ?? 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -45,7 +45,7 @@ export const BoardViewColumnsMultiSelect = ({
|
||||
};
|
||||
|
||||
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
||||
const noValueCount = selectOptionCountQuery.data?.noValueCount ?? 0;
|
||||
|
||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ export const BoardViewColumnsSelect = ({
|
||||
};
|
||||
|
||||
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
||||
const noValueCount = selectOptionCountQuery.data?.noValueCount ?? 0;
|
||||
|
||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||
|
||||
|
||||
@@ -42,7 +42,11 @@ export const CalendarViewDay = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<p className={isToday ? 'rounded-md bg-red-500 p-0.5 text-white' : ''}>
|
||||
<p
|
||||
className={
|
||||
isToday ? 'rounded-md bg-red-500 py-1 px-2 text-white' : ''
|
||||
}
|
||||
>
|
||||
{date.getDate()}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -76,12 +76,12 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
|
||||
),
|
||||
button_previous: cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none size-7',
|
||||
defaultClassNames.button_previous
|
||||
),
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none',
|
||||
'size-(--cell-size) aria-disabled:opacity-50 p-0 select-none size-7',
|
||||
defaultClassNames.button_next
|
||||
),
|
||||
month_caption: cn(
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { CircleDashed } from 'lucide-react';
|
||||
|
||||
import { DatabaseViewFilterAttributes, FieldAttributes } from '@colanode/core';
|
||||
import { CalendarViewNoValueList } from '@colanode/ui/components/databases/calendars/calendar-view-no-value-list';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
} from '@colanode/ui/components/ui/dialog';
|
||||
import { useDatabase } from '@colanode/ui/contexts/database';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useWorkspace } from '@colanode/ui/contexts/workspace';
|
||||
import { useQuery } from '@colanode/ui/hooks/use-query';
|
||||
|
||||
interface CalendarViewNoValueCountProps {
|
||||
field: FieldAttributes;
|
||||
}
|
||||
|
||||
export const CalendarViewNoValueCount = ({
|
||||
field,
|
||||
}: CalendarViewNoValueCountProps) => {
|
||||
const workspace = useWorkspace();
|
||||
const database = useDatabase();
|
||||
const view = useDatabaseView();
|
||||
|
||||
const filters: DatabaseViewFilterAttributes[] = [
|
||||
...view.filters,
|
||||
{
|
||||
id: 'no_value',
|
||||
type: 'field',
|
||||
fieldId: field.id,
|
||||
operator: 'is_empty',
|
||||
},
|
||||
];
|
||||
|
||||
const noValueCountQuery = useQuery({
|
||||
type: 'record.field.value.count',
|
||||
databaseId: database.id,
|
||||
filters: filters,
|
||||
fieldId: field.id,
|
||||
accountId: workspace.accountId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const noValueCount = noValueCountQuery.data?.noValueCount ?? 0;
|
||||
if (noValueCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<div
|
||||
className="flex flex-row items-center gap-1 text-sm mr-2 hover:bg-muted hover:cursor-pointer rounded-md p-1"
|
||||
role="presentation"
|
||||
>
|
||||
<CircleDashed className="size-4" />
|
||||
<p>No {field.name}</p>
|
||||
<p>({noValueCount.toLocaleString()})</p>
|
||||
</div>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="w-128 min-w-128 min-h-40 max-h-128 overflow-auto p-4">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{view.name}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Record with no {field.name} value ({noValueCount.toLocaleString()})
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CalendarViewNoValueList filters={filters} field={field} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import { InView } from 'react-intersection-observer';
|
||||
|
||||
import { DatabaseViewFilterAttributes, FieldAttributes } from '@colanode/core';
|
||||
import { Avatar } from '@colanode/ui/components/avatars/avatar';
|
||||
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
|
||||
import { useLayout } from '@colanode/ui/contexts/layout';
|
||||
import { useRecordsQuery } from '@colanode/ui/hooks/use-records-query';
|
||||
|
||||
interface CalendarViewNoValueListProps {
|
||||
filters: DatabaseViewFilterAttributes[];
|
||||
field: FieldAttributes;
|
||||
}
|
||||
|
||||
export const CalendarViewNoValueList = ({
|
||||
filters,
|
||||
field,
|
||||
}: CalendarViewNoValueListProps) => {
|
||||
const view = useDatabaseView();
|
||||
const layout = useLayout();
|
||||
|
||||
const { records, hasMore, loadMore, isPending } = useRecordsQuery(
|
||||
filters,
|
||||
view.sorts
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 overflow-y-auto">
|
||||
{records.length === 0 && (
|
||||
<div className="text-center text-sm text-muted-foreground">
|
||||
No records with no {field.name} value
|
||||
</div>
|
||||
)}
|
||||
{records.map((record) => {
|
||||
const name = record.attributes.name ?? 'Unnamed';
|
||||
return (
|
||||
<div
|
||||
key={record.id}
|
||||
className="flex flex-row items-center border rounded-md p-1 gap-2 cursor-pointer hover:bg-muted"
|
||||
onClick={() => {
|
||||
layout.previewLeft(record.id, true);
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
id={record.id}
|
||||
name={name}
|
||||
avatar={record.attributes.avatar}
|
||||
size="small"
|
||||
/>
|
||||
<p>{name}</p>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<InView
|
||||
rootMargin="200px"
|
||||
onChange={(inView) => {
|
||||
if (inView && hasMore && !isPending) {
|
||||
loadMore();
|
||||
}
|
||||
}}
|
||||
></InView>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { Fragment } from 'react';
|
||||
|
||||
import { CalendarViewGrid } from '@colanode/ui/components/databases/calendars/calendar-view-grid';
|
||||
import { CalendarViewNoGroup } from '@colanode/ui/components/databases/calendars/calendar-view-no-group';
|
||||
import { CalendarViewNoValueCount } from '@colanode/ui/components/databases/calendars/calendar-view-no-value-count';
|
||||
import { CalendarViewSettings } from '@colanode/ui/components/databases/calendars/calendar-view-settings';
|
||||
import { ViewFilterButton } from '@colanode/ui/components/databases/search/view-filter-button';
|
||||
import { ViewSearchBar } from '@colanode/ui/components/databases/search/view-search-bar';
|
||||
@@ -23,6 +24,7 @@ export const CalendarView = () => {
|
||||
<div className="flex flex-row justify-between border-b">
|
||||
<ViewTabs />
|
||||
<div className="invisible flex flex-row items-center justify-end group-hover/database:visible">
|
||||
{groupByField && <CalendarViewNoValueCount field={groupByField} />}
|
||||
<CalendarViewSettings />
|
||||
<ViewSortButton />
|
||||
<ViewFilterButton />
|
||||
|
||||
Reference in New Issue
Block a user