Skip to content

Commit

Permalink
Merge branch 'develop' into meetings-table
Browse files Browse the repository at this point in the history
  • Loading branch information
TPReal committed Dec 16, 2023
2 parents 7772e0b + d054b40 commit e6a5739
Show file tree
Hide file tree
Showing 21 changed files with 229 additions and 112 deletions.
54 changes: 54 additions & 0 deletions resources/js/components/ui/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {FiDelete} from "solid-icons/fi";
import {Show, VoidComponent, createEffect, createSignal, on} from "solid-js";
import {cx, htmlAttributes} from "../utils";
import {Button} from "./Button";
import {TextInput} from "./TextInput";
import {splitProps} from "solid-js";

interface Props extends htmlAttributes.input {
readonly divClass?: string;
}

export const SearchInput: VoidComponent<Props> = (allProps) => {
const [props, inputProps] = splitProps(allProps, ["divClass"]);
let ref: HTMLInputElement | undefined;
const [value, setValue] = createSignal("");
function setNow() {
setValue(ref?.value || "");
}
createEffect(
on(
() => inputProps.value,
() => setTimeout(setNow, 0),
),
);
return (
<div class={cx(props.divClass, "flex items-stretch relative")}>
<TextInput
{...htmlAttributes.merge(inputProps, {
class: "grow !pr-6",
onInput: setNow,
onChange: setNow,
})}
ref={(input) => {
ref = input;
(inputProps.ref as (elem: HTMLInputElement) => void)?.(input);
}}
/>
<Show when={value()}>
<Button
class="absolute right-0 top-0 bottom-0 px-2"
onClick={() => {
if (ref) {
ref.value = "";
ref.dispatchEvent(new InputEvent("input"));
ref.focus();
}
}}
>
<FiDelete />
</Button>
</Show>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,36 +1,36 @@
import {useLangFunc} from "components/utils";
import {cx, useLangFunc} from "components/utils";
import {TbFilter, TbFilterOff} from "solid-icons/tb";
import {VoidComponent, createSignal} from "solid-js";
import {Dynamic} from "solid-js/web";
import {Button} from "../Button";

interface Props {
readonly isFiltering?: boolean;
readonly class?: string;
readonly onClear?: () => void;
readonly isFiltering: boolean;
readonly onClear: () => void;
readonly title?: string;
}

export const FilterIcon: VoidComponent<Props> = (props) => {
export const FilterIconButton: VoidComponent<Props> = (props) => {
const t = useLangFunc();
const [hover, setHover] = createSignal(false);
return (
<Button
class={props.class}
title={
props.isFiltering
? [t("tables.filter.filter_set"), props.onClear && t("tables.filter.click_to_clear")]
.filter(Boolean)
.join("\n")
: t("tables.filter.filter_cleared")
props.title ||
(props.isFiltering
? `${t("tables.filter.filter_set")}\n${t("tables.filter.click_to_clear")}`
: t("tables.filter.filter_cleared"))
}
classList={{"cursor-pointer": props.isFiltering}}
disabled={!props.isFiltering}
onMouseEnter={[setHover, true]}
onMouseLeave={[setHover, false]}
onClick={() => props.onClear?.()}
>
<Dynamic
component={props.isFiltering && !hover() ? TbFilter : TbFilterOff}
class={props.class}
classList={{dimmed: !props.isFiltering}}
class={cx({dimmed: !props.isFiltering})}
/>
</Button>
);
Expand Down
21 changes: 17 additions & 4 deletions resources/js/components/ui/Table/TQueryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
getBaseTableOptions,
useTableCells,
} from ".";
import {TableFiltersClearButton} from "./TableFiltersClearButton";
import {ColumnFilterController, FilteringParams} from "./tquery_filters/ColumnFilterController";

declare module "@tanstack/table-core" {
Expand Down Expand Up @@ -213,7 +214,15 @@ export const TQueryTable: VoidComponent<TQueryTableProps> = (props) => {
),
);
}
const {columnVisibility, globalFilter, getColumnFilter, sorting, pagination} = requestController;
const {
columnVisibility,
globalFilter,
getColumnFilter,
columnsWithActiveFilters,
clearColumnFilters,
sorting,
pagination,
} = requestController;
if (props.staticPersistenceKey) {
createLocalStoragePersistence<PersistentState>({
key: `TQueryTable:${props.staticPersistenceKey}`,
Expand Down Expand Up @@ -325,13 +334,17 @@ export const TQueryTable: VoidComponent<TQueryTableProps> = (props) => {
mode={props.mode}
rowsIteration="Index"
aboveTable={() => (
<div class="h-8 flex items-stretch gap-1">
<TableSearch class="flex-grow" />
<div class="min-h-small-input flex items-stretch gap-1">
<TableSearch divClass="flex-grow" />
<TableFiltersClearButton
columnsWithActiveFilters={columnsWithActiveFilters()}
clearColumnFilters={clearColumnFilters}
/>
<TableColumnVisibilityController />
</div>
)}
belowTable={() => (
<div class="h-8 flex items-stretch gap-2">
<div class="min-h-small-input flex items-stretch gap-2">
<Pagination />
<TableSummary rowsCount={rowsCount()} />
{props.customSectionBelowTable}
Expand Down
28 changes: 28 additions & 0 deletions resources/js/components/ui/Table/TableFiltersClearButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {useLangFunc} from "components/utils";
import {VoidComponent} from "solid-js";
import {FilterIconButton} from "./FilterIconButton";
import {useTable} from "./TableContext";

interface Props {
readonly columnsWithActiveFilters: readonly string[];
readonly clearColumnFilters: () => void;
}

export const TableFiltersClearButton: VoidComponent<Props> = (props) => {
const t = useLangFunc();
const table = useTable();
return (
<FilterIconButton
class="border border-input-border rounded px-1"
isFiltering={props.columnsWithActiveFilters.length > 0}
onClear={props.clearColumnFilters}
title={
props.columnsWithActiveFilters.length
? `${t("tables.filter.column_filters_set")}\n${props.columnsWithActiveFilters
.map((column) => `- ${table.options.meta?.translations?.columnNames(column)}`)
.join("\n")}\n${t("tables.filter.click_to_clear")}`
: t("tables.filter.column_filters_cleared")
}
/>
);
};
32 changes: 15 additions & 17 deletions resources/js/components/ui/Table/TableSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import {htmlAttributes, useLangFunc} from "components/utils";
import {ParentProps, VoidComponent, createComputed, createSignal, splitProps} from "solid-js";
import {cx, useLangFunc} from "components/utils";
import {ParentProps, VoidComponent} from "solid-js";
import {useTable} from ".";
import {SearchInput} from "../SearchInput";

interface Props extends htmlAttributes.div {
interface Props {
readonly divClass?: string;
readonly placeholder?: string;
}

export const TableSearch: VoidComponent<ParentProps<Props>> = (allProps) => {
const [props, divProps] = splitProps(allProps, ["placeholder"]);
export const TableSearch: VoidComponent<ParentProps<Props>> = (props) => {
const t = useLangFunc();
const table = useTable();
const [query, setQuery] = createSignal(table.getState().globalFilter);
createComputed(() => table.setGlobalFilter(query()));
const globalFilter = (): string => table.getState().globalFilter;
return (
<div {...divProps}>
<input
class="w-full h-full px-2 border border-input-border rounded"
name="table_global_search"
type="search"
placeholder={props.placeholder || t("actions.search")}
value={query()}
onInput={({target: {value}}) => setQuery(value)}
/>
</div>
<SearchInput
divClass={cx("flex items-stretch", props.divClass)}
name="table_global_search"
class="px-1"
placeholder={props.placeholder || t("actions.search")}
value={globalFilter()}
onInput={({target: {value}}) => table.setGlobalFilter(value)}
/>
);
};
2 changes: 1 addition & 1 deletion resources/js/components/ui/Table/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from "./ColumnName";
export * from "./FilterIcon";
export * from "./FilterIconButton";
export * from "./Header";
export * from "./IdColumn";
export * from "./Pagination";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,4 @@
}
}
}

.filterIcon {
@apply mb-1;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {JSX, Show, VoidComponent, mergeProps} from "solid-js";
import {FilterIcon, useTable} from "..";
import {FilterIconButton, useTable} from "..";
import {BoolFilterControl} from "./BoolFilterControl";
import s from "./ColumnFilterController.module.scss";
import {DateTimeFilterControl} from "./DateTimeFilterControl";
Expand Down Expand Up @@ -71,7 +71,9 @@ export const ColumnFilterController: VoidComponent<FilterControlProps> = (props)
{(filterControl) => (
<>
<div class={s.filterMain}>{filterControl()()}</div>
<FilterIcon class={s.filterIcon} isFiltering={!!props.filter} onClear={() => props.setFilter(undefined)} />
<div>
<FilterIconButton isFiltering={!!props.filter} onClear={() => props.setFilter(undefined)} />
</div>
</>
)}
</Show>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {SearchInput} from "components/ui/SearchInput";
import {Select, SelectItem} from "components/ui/form/Select";
import {debouncedFilterTextAccessor, useLangFunc} from "components/utils";
import {FilterH} from "data-access/memo-api/tquery/filter_utils";
Expand All @@ -12,9 +13,9 @@ interface StringColumnProps extends FilterControlProps {
}

export const TextualFilterControl: VoidComponent<StringColumnProps> = (props) => {
const t = useLangFunc();
const [mode, setMode] = createSignal("~");
const [text, setText] = createSignal("");
const t = useLangFunc();
createComputed(() => {
if (!props.filter) {
setMode("~");
Expand Down Expand Up @@ -103,11 +104,10 @@ export const TextualFilterControl: VoidComponent<StringColumnProps> = (props) =>
/>
</div>
<div class={s.wideEdit}>
<input
<SearchInput
name={`table_filter_val_${props.name}`}
type="search"
autocomplete="off"
class="h-full w-full min-h-small-input border border-input-border rounded"
class="h-full w-full min-h-small-input"
value={inputUsed() ? text() : ""}
maxlength={inputUsed() ? undefined : 0}
disabled={!inputUsed()}
Expand Down
11 changes: 11 additions & 0 deletions resources/js/components/ui/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {VoidComponent} from "solid-js";
import {htmlAttributes} from "../utils";

export const TextInput: VoidComponent<htmlAttributes.input> = (inputProps) => (
<input
type="text"
{...htmlAttributes.merge(inputProps, {
class: "border border-input-border rounded aria-invalid:border-red-400 disabled:bg-disabled",
})}
/>
);
8 changes: 3 additions & 5 deletions resources/js/components/ui/form/TextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {htmlAttributes} from "components/utils";
import {JSX, VoidComponent, splitProps} from "solid-js";
import {FieldBox} from "./FieldBox";
import {labelIdForField} from "./FieldLabel";
import {TextInput} from "../TextInput";

export interface TextFieldProps
extends Pick<
Expand All @@ -17,14 +18,11 @@ export const TextField: VoidComponent<TextFieldProps> = (allProps) => {
const [props, inputProps] = splitProps(allProps, ["name", "label"]);
return (
<FieldBox {...props}>
<input
<TextInput
id={props.name}
name={props.name}
autocomplete="off"
{...htmlAttributes.merge(inputProps, {
class:
"min-h-big-input border border-input-border rounded px-2 aria-invalid:border-red-400 disabled:bg-disabled",
})}
{...htmlAttributes.merge(inputProps, {class: "min-h-big-input px-2"})}
aria-labelledby={labelIdForField(props.name)}
/>
</FieldBox>
Expand Down
30 changes: 15 additions & 15 deletions resources/js/data-access/memo-api/resources/adminUser.resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,51 @@ import {MemberResource} from "./member.resource";
/**
* @see `/app/Http/Resources/Admin/AdminUserResource.php`
*/
export type AdminUserResource = {
export interface AdminUserResource {
/**
* admin user identifier
* @type {string(uuid)}
* @example '67da972b-34d7-4f89-b8ae-322d96b4954d'
*/
id: string;
readonly id: string;
/**
* admin user full name
* @type {string}
*/
name: string;
readonly name: string;
/**
* email address
* @type {string}
* @example '[email protected]'
*/
email: string | null;
readonly email: string | null;
/**
* facility identifier where the user was last logged in
* @type {string(uuid)}
* @example '67da972b-34d7-4f89-b8ae-322d96b4954d'
*/
lastLoginFacilityId: string | null;
passwordExpireAt: string | null;
hasPassword: boolean;
createdAt: string;
updatedAt: string;
hasEmailVerified: boolean;
readonly lastLoginFacilityId: string | null;
readonly passwordExpireAt: string | null;
readonly hasPassword: boolean;
readonly createdAt: string;
readonly updatedAt: string;
readonly hasEmailVerified: boolean;
/**
* identifier of a user who created this user
* @type {string(uuid)}
* @example '67da972b-34d7-4f89-b8ae-322d96b4954d'
*/
createdBy: string;
hasGlobalAdmin: boolean;
readonly createdBy: string;
readonly hasGlobalAdmin: boolean;
/**
* array of members
* @type {MemberResource[]}
*/
members: MemberResource[];
};
readonly members: readonly MemberResource[];
}

/** The user resource used for creation. */
export type AdminUserResourceForCreate = Pick<
AdminUserResource,
"name" | "email" | "hasEmailVerified" | "passwordExpireAt" | "hasGlobalAdmin"
> & {password: string | null};
> & {readonly password: string | null};
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface DictionaryResource {
readonly facilityId: string | null;
readonly name: DictionaryName;
/** The positions in the dictionary, sorted by their sort order. */
readonly positions: PositionResource[];
readonly positions: readonly PositionResource[];
/**
* Whether the dictionary is unmodifiable even by the admin.
* Fixed dictionaries can be referenced by name in the code.
Expand Down
Loading

0 comments on commit e6a5739

Please sign in to comment.