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 {
|
return {
|
||||||
hasChanges: true,
|
hasChanges: true,
|
||||||
result: { values: [], nullCount: 0 },
|
result: { values: [], noValueCount: 0 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
hasChanges: true,
|
hasChanges: true,
|
||||||
result: { values: [], nullCount: 0 },
|
result: { values: [], noValueCount: 0 },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
if (!field) {
|
if (!field) {
|
||||||
return {
|
return {
|
||||||
values: [],
|
values: [],
|
||||||
nullCount: 0,
|
noValueCount: 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,12 +141,12 @@ export class RecordFieldValueCountQueryHandler
|
|||||||
|
|
||||||
const output: RecordFieldValueCountQueryOutput = {
|
const output: RecordFieldValueCountQueryOutput = {
|
||||||
values: [],
|
values: [],
|
||||||
nullCount: 0,
|
noValueCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const row of result.rows) {
|
for (const row of result.rows) {
|
||||||
if (row.value === 'null') {
|
if (row.value === 'null') {
|
||||||
output.nullCount = row.count;
|
output.noValueCount = row.count;
|
||||||
} else {
|
} else {
|
||||||
output.values.push({
|
output.values.push({
|
||||||
value: row.value,
|
value: row.value,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export type RecordFieldValueCount = {
|
|||||||
|
|
||||||
export type RecordFieldValueCountQueryOutput = {
|
export type RecordFieldValueCountQueryOutput = {
|
||||||
values: RecordFieldValueCount[];
|
values: RecordFieldValueCount[];
|
||||||
nullCount: number;
|
noValueCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
declare module '@colanode/client/queries' {
|
declare module '@colanode/client/queries' {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export const BoardViewColumnsCollaborator = ({
|
|||||||
fieldId: field.id,
|
fieldId: field.id,
|
||||||
operator: 'is_empty',
|
operator: 'is_empty',
|
||||||
};
|
};
|
||||||
const noValueCount = collaboratorCountQuery.data?.nullCount ?? 0;
|
const noValueCount = collaboratorCountQuery.data?.noValueCount ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export const BoardViewColumnsMultiSelect = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||||
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
const noValueCount = selectOptionCountQuery.data?.noValueCount ?? 0;
|
||||||
|
|
||||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const BoardViewColumnsSelect = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
const selectOptionCount = selectOptionCountQuery.data?.values ?? [];
|
||||||
const noValueCount = selectOptionCountQuery.data?.nullCount ?? 0;
|
const noValueCount = selectOptionCountQuery.data?.noValueCount ?? 0;
|
||||||
|
|
||||||
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
const noValueDraggingClass = getSelectOptionLightColorClass('gray');
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,11 @@ export const CalendarViewDay = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</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()}
|
{date.getDate()}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,12 +76,12 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
|
|||||||
),
|
),
|
||||||
button_previous: cn(
|
button_previous: cn(
|
||||||
buttonVariants({ variant: 'ghost' }),
|
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
|
defaultClassNames.button_previous
|
||||||
),
|
),
|
||||||
button_next: cn(
|
button_next: cn(
|
||||||
buttonVariants({ variant: 'ghost' }),
|
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
|
defaultClassNames.button_next
|
||||||
),
|
),
|
||||||
month_caption: cn(
|
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 { CalendarViewGrid } from '@colanode/ui/components/databases/calendars/calendar-view-grid';
|
||||||
import { CalendarViewNoGroup } from '@colanode/ui/components/databases/calendars/calendar-view-no-group';
|
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 { CalendarViewSettings } from '@colanode/ui/components/databases/calendars/calendar-view-settings';
|
||||||
import { ViewFilterButton } from '@colanode/ui/components/databases/search/view-filter-button';
|
import { ViewFilterButton } from '@colanode/ui/components/databases/search/view-filter-button';
|
||||||
import { ViewSearchBar } from '@colanode/ui/components/databases/search/view-search-bar';
|
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">
|
<div className="flex flex-row justify-between border-b">
|
||||||
<ViewTabs />
|
<ViewTabs />
|
||||||
<div className="invisible flex flex-row items-center justify-end group-hover/database:visible">
|
<div className="invisible flex flex-row items-center justify-end group-hover/database:visible">
|
||||||
|
{groupByField && <CalendarViewNoValueCount field={groupByField} />}
|
||||||
<CalendarViewSettings />
|
<CalendarViewSettings />
|
||||||
<ViewSortButton />
|
<ViewSortButton />
|
||||||
<ViewFilterButton />
|
<ViewFilterButton />
|
||||||
|
|||||||
Reference in New Issue
Block a user