Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,21 @@ import type {
import {
VuuDatePicker,
VuuInput,
VuuTimePicker,
VuuTypeaheadInput,
VuuTypeaheadInputProps,
} from "@vuu-ui/vuu-ui-controls";
import { CommitHandler, isDateTimeDataValue } from "@vuu-ui/vuu-utils";
import {
CommitHandler,
isDateTimeDataValue,
isTimeDataValue,
} from "@vuu-ui/vuu-utils";
import { InputProps } from "@salt-ds/core";

export interface DataItemEditControlProps {
InputProps?: Partial<InputProps>;
TypeaheadProps?: Pick<VuuTypeaheadInputProps, "highlightFirstSuggestion">;
className?: string;
commitWhenCleared?: boolean;
/**
* A table column or form field Descriptor.
Expand All @@ -29,6 +35,7 @@ export type ValidationStatus = "initial" | true | string;
export const getDataItemEditControl = ({
InputProps,
TypeaheadProps,
className,
commitWhenCleared,
dataDescriptor,
errorMessage,
Expand All @@ -51,13 +58,20 @@ export const getDataItemEditControl = ({
readOnly
/>
);
} else if (isTimeDataValue(dataDescriptor)) {
return (
<VuuTimePicker className={className} onCommit={handleCommitNumber} />
);
} else if (isDateTimeDataValue(dataDescriptor)) {
return <VuuDatePicker onCommit={handleCommitNumber} />;
return (
<VuuDatePicker className={className} onCommit={handleCommitNumber} />
);
} else if (dataDescriptor.serverDataType === "string" && table) {
return (
<VuuTypeaheadInput
{...InputProps}
{...TypeaheadProps}
className={className}
column={dataDescriptor.name}
onCommit={onCommit}
table={table}
Expand All @@ -68,6 +82,7 @@ export const getDataItemEditControl = ({
<VuuInput
variant="secondary"
{...InputProps}
className={className}
commitWhenCleared={commitWhenCleared}
onCommit={onCommit}
errorMessage={errorMessage}
Expand Down
9 changes: 9 additions & 0 deletions vuu-ui/packages/vuu-data-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,14 @@ export declare type DataValueTypeSimple =
| "boolean"
| "json"
| DateTimeDataValueTypeSimple
| TimeDataValueTypeSimple
| "checkbox";

export declare type DataValueType =
| DataValueTypeSimple
| DataValueTypeDescriptor;

export declare type TimeDataValueTypeSimple = "time";
export declare type DateTimeDataValueTypeSimple = "date/time";

export declare type DateTimeDataValueType =
Expand Down Expand Up @@ -160,6 +162,13 @@ export declare type DateTimeDataValueDescriptor = Omit<
type: DateTimeDataValueType;
};

export declare type TimeDataValueDescriptor = Omit<
DataValueDescriptor,
"type"
> & {
type: TimeDataValueType;
};

export interface DataSourceFilter extends VuuFilter {
filterStruct?: Filter;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ describe("FilterClause", () => {
it("THEN component is rendered with controls for column, operator and value, value suggestions are offered", () => {
cy.mount(
<LocalDataSourceProvider>
<PartialFilterClauseColumnAndOperatorWithDataSource />
<PartialFilterClauseColumnAndOperator />{" "}
</LocalDataSourceProvider>,
);
const container = cy.findByTestId("filterclause");
Expand Down
10 changes: 10 additions & 0 deletions vuu-ui/packages/vuu-filters/src/column-filter/ColumnFilter.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.vuuColumnFilter {
border: solid 1px var(--salt-editable-borderColor);
border-radius: 4px;
max-width: 200px;
}

.vuuColumnFilter-trigger {
--saltButton-borderRadius: 4px 0px 0px 4px;
--saltButton-padding: var(--salt-spacing-200);
}
109 changes: 109 additions & 0 deletions vuu-ui/packages/vuu-filters/src/column-filter/ColumnFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Button,
Menu,
MenuItem,
MenuPanel,
MenuTrigger,
SegmentedButtonGroup,
SegmentedButtonGroupProps,
} from "@salt-ds/core";
import { useComponentCssInjection } from "@salt-ds/styles";
import { useWindow } from "@salt-ds/window";
import cx from "clsx";

import columnFilterCss from "./ColumnFilter.css";
import { getDataItemEditControl } from "@vuu-ui/vuu-data-react";
import { useCallback, useMemo } from "react";
import { CommitHandler } from "@vuu-ui/vuu-utils";
import { VuuTable } from "@vuu-ui/vuu-protocol-types";
import {
assertValidValue,
ColumnFilterHookProps,
FilterValue,
} from "./useColumnFilter";

const classBase = "vuuColumnFilter";

export interface ColumnFilterProps
extends ColumnFilterHookProps,
SegmentedButtonGroupProps {
showOperatorPicker?: boolean;
/**
* VuuTable is required if typeahead support is expected.
*/
table?: VuuTable;

/**
* Initial filter value. Pair of values expewcted when operator is
* 'between'
*/
value?: FilterValue | [FilterValue, FilterValue];
}

export const ColumnFilter = ({
column,
className,
operator = "=",
showOperatorPicker = false,
table,
value,
...buttonGroupProps
}: ColumnFilterProps) => {
const targetWindow = useWindow();
useComponentCssInjection({
testId: "vuu-filter-bar",
css: columnFilterCss,
window: targetWindow,
});

useMemo(
() => assertValidValue(column, operator, value),
[column, operator, value],
);

const onCommit = useCallback<CommitHandler<HTMLElement>>((e, value) => {
console.log(`onCommit ${value}`);
}, []);

return (
<SegmentedButtonGroup
{...buttonGroupProps}
className={cx(classBase, className)}
>
{showOperatorPicker ? (
<Menu placement="bottom-start">
<MenuTrigger>
<Button
appearance="solid"
aria-label="Open Menu"
className={`${classBase}-trigger`}
data-embedded
sentiment="neutral"
>
{operator}
</Button>
</MenuTrigger>
<MenuPanel>
<MenuItem>=</MenuItem>
<MenuItem>!=</MenuItem>
<MenuItem>starts</MenuItem>
<MenuItem>ends</MenuItem>
<MenuItem>contains</MenuItem>
</MenuPanel>
</Menu>
) : null}
{getDataItemEditControl({
dataDescriptor: column,
onCommit,
table,
})}
{operator === "between"
? getDataItemEditControl({
dataDescriptor: column,
onCommit,
table,
})
: null}
</SegmentedButtonGroup>
);
};
39 changes: 39 additions & 0 deletions vuu-ui/packages/vuu-filters/src/column-filter/useColumnFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { FilterOp } from "@vuu-ui/vuu-filter-types";
import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";

export type FilterValue = string | number;

export const assertValidValue = (
{ serverDataType: _ }: ColumnDescriptor,
operator: FilterOp | "between",
value?: FilterValue | FilterValue[],
) => {
if (value !== undefined) {
if (operator === "between") {
if (!Array.isArray(value)) {
throw Error(
"[useColumnFilter] 'between operator requires array of two values'",
);
} else if (value.length !== 2) {
throw Error(
`[useColumnFilter] 'between operator requires two values, received ${value.length}'`,
);
} else if (typeof value[0] !== typeof value[1]) {
throw Error(
`[useColumnFilter] 'between operator values must be of same type, received ${typeof value[0]} and ${typeof value[1]}`,
);
}
}
// TODO validate value(s) against serverDataType
}
};

export interface ColumnFilterHookProps {
column: ColumnDescriptor;
operator?: FilterOp | "between";
}

// export const useColumnFilter = ({
// column,
// operator = "=",
// }: ColumnFilterHookProps) => {};
2 changes: 1 addition & 1 deletion vuu-ui/packages/vuu-filters/src/filter-bar/FilterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { HTMLAttributes, useMemo } from "react";
import { CustomFilters } from "../custom-filters";
import { QuickFilterProps, QuickFilters } from "../quick-filters";
import { FilterMode, useFilterBar } from "./useFilterBar";
import type { VuuTable } from "@vuu-ui/vuu-protocol-types";

import filterBarCss from "./FilterBar.css";
import { VuuTable } from "@vuu-ui/vuu-protocol-types";

export type FilterBarVariant =
| "custom-filters-only"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const getFocusedFieldDetails = (): [number, string] | [] => {
return [getElementDataIndex(filterClause), getFilterClauseDetails(field)];
}
} else if (field?.matches(".saltCalendar")) {
console.log("muthafucker is ins a calendar");
console.log("its in a calendar");
}
// const filterClause = queryClosest(field, ".vuuFilterClause");
// if (filterClause && field) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
NumericFilterClauseOp,
SingleValueFilterClauseOp,
} from "@vuu-ui/vuu-filter-types";
import { isDateTimeDataValue } from "@vuu-ui/vuu-utils";
import { isDateTimeDataValue, isTimeDataValue } from "@vuu-ui/vuu-utils";
import { ForwardedRef, forwardRef } from "react";
import { FilterClauseValueEditorDate } from "./FilterClauseValueEditorDate";
import { FilterClauseValueEditorTime } from "./FilterClauseValueEditorTime";

const classBase = "vuuFilterClause";

Expand Down Expand Up @@ -51,7 +52,16 @@ export const FilterClauseValueEditor = forwardRef(
<FilterClauseValueEditorDate
inputProps={inputProps}
className={cx(`${classBase}Field`, `${classBase}Value`)}
// ref={forwardedRef}
value={value as number}
operator={operator as NumericFilterClauseOp}
onChangeValue={onChangeValue}
/>
);
} else if (isTimeDataValue(selectedColumn)) {
return (
<FilterClauseValueEditorTime
inputProps={inputProps}
className={cx(`${classBase}Field`, `${classBase}Value`)}
value={value as number}
operator={operator as NumericFilterClauseOp}
onChangeValue={onChangeValue}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useCallback, useState } from "react";
import { getLocalTimeZone, DateValue } from "@internationalized/date";
import { CommitHandler, toCalendarDate } from "@vuu-ui/vuu-utils";
import { NumericFilterClauseOp } from "@vuu-ui/vuu-filter-types";
import { FilterClauseValueEditor } from "../filterClauseTypes";
import { VuuTimePicker } from "@vuu-ui/vuu-ui-controls";

interface FilterClauseValueEditorTimeProps
extends Pick<FilterClauseValueEditor, "onChangeValue" | "inputProps"> {
className?: string;
value: number | undefined;
operator: NumericFilterClauseOp;
}

export const FilterClauseValueEditorTime = (
props: FilterClauseValueEditorTimeProps,
) => {
const { className, onChangeValue, operator, value } = props;
const toEpochMilliS = getEpochMillisConverter(operator);

const [date, setDate] = useState<DateValue | undefined>(() =>
getInitialState(value),
);

const handleCommit = useCallback<CommitHandler<HTMLElement, number>>(
(e, selectedDateInputValue) => {
console.log("change time");
if (selectedDateInputValue) {
const dateValue = toCalendarDate(new Date(selectedDateInputValue));
setDate(dateValue);
if (selectedDateInputValue /* && source === "calendar"*/) {
onChangeValue(toEpochMilliS(dateValue));
}
}
},
[onChangeValue, toEpochMilliS],
);

const onBlur = useCallback(() => {
date && onChangeValue(toEpochMilliS(date));
}, [date, onChangeValue, toEpochMilliS]);

return (
<VuuTimePicker
data-field="value"
// inputProps={inputProps}
className={className}
onBlur={onBlur}
onCommit={handleCommit}
// selectedDate={date}
/>
);
};

function getInitialState(value: FilterClauseValueEditorTimeProps["value"]) {
return value ? toCalendarDate(new Date(value)) : undefined;
}

const getEpochMillisConverter =
(op: NumericFilterClauseOp) =>
(date: DateValue, timezone: string = getLocalTimeZone()): number => {
const d = date.toDate(timezone);
switch (op) {
case ">":
case "<=":
d.setHours(23, 59, 59, 999);
return d.getTime();
case ">=":
case "<":
case "=": // converted to "< `start of next day` and >= `start of this day`" when query is created
case "!=": // converted to ">= `start of next day` or < `start of this day`" when query is created
d.setHours(0, 0, 0, 0);
return d.getTime();
}
};
4 changes: 4 additions & 0 deletions vuu-ui/packages/vuu-filters/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export * from "./FilterModel";
export {
ColumnFilter,
type ColumnFilterProps,
} from "./column-filter/ColumnFilter";
export * from "./filter-bar";
export * from "./filter-clause";
export * from "./filter-editor";
Expand Down
Loading
Loading