Skip to content

Commit b5fd07a

Browse files
committed
updates to fix filtering
1 parent 5e0b132 commit b5fd07a

9 files changed

Lines changed: 416 additions & 48 deletions

File tree

src/core/components/Table.tsx

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,18 @@ import { ChevronUpIcon, ChevronDownIcon, ChevronUpDownIcon } from "@heroicons/re
1616

1717
import Filter from "src/core/components/Filter"
1818
import { buildSearchableString } from "src/core/utils/tableFilters"
19-
20-
const specialSearchTokens = new Set(["read", "unread", "completed", "complete", "not completed"])
19+
import TooltipWrapper from "./TooltipWrapper"
20+
21+
const specialSearchTokens = new Set([
22+
"read",
23+
"unread",
24+
"completed",
25+
"complete",
26+
"not completed",
27+
"approved",
28+
"not approved",
29+
"pending",
30+
])
2131

2232
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
2333
const containsWholeWord = (text: string, word: string) => {
@@ -48,10 +58,11 @@ const matchesSpecialTokenInText = (text: string, token: string) => {
4858
return containsWholeWord(normalized, token)
4959
}
5060

51-
const matchesBooleanToken = (token: string, value: boolean, keyPath: string): boolean => {
61+
const matchesBooleanToken = (token: string, value: boolean | null, keyPath: string): boolean => {
5262
const normalizedKey = keyPath.toLowerCase()
5363
const isReadKey = normalizedKey.includes("read")
5464
const isCompletionKey = normalizedKey.includes("status") || normalizedKey.includes("complete")
65+
const isApprovalKey = normalizedKey.includes("approve")
5566

5667
if (token === "read") {
5768
return isReadKey && value === true
@@ -69,12 +80,24 @@ const matchesBooleanToken = (token: string, value: boolean, keyPath: string): bo
6980
return isCompletionKey && value === false
7081
}
7182

83+
if (token === "approved") {
84+
return isApprovalKey && value === true
85+
}
86+
87+
if (token === "not approved") {
88+
return isApprovalKey && value === false
89+
}
90+
91+
if (token === "pending") {
92+
return isApprovalKey && (value === null || value === undefined)
93+
}
94+
7295
return false
7396
}
7497

7598
const matchesSpecialToken = (data: unknown, token: string, keyPath = ""): boolean => {
7699
if (data === null || data === undefined) {
77-
return false
100+
return matchesBooleanToken(token, data as null, keyPath)
78101
}
79102

80103
if (typeof data === "boolean") {
@@ -190,6 +213,8 @@ const Table = <TData,>({
190213
const pageCount = table.getPageCount()
191214
const pageIndex = table.getState().pagination.pageIndex
192215

216+
const globalSearchTooltipId = React.useId()
217+
193218
React.useEffect(() => {
194219
if (!addPagination) {
195220
return
@@ -203,17 +228,24 @@ const Table = <TData,>({
203228
return (
204229
<>
205230
{enableGlobalSearch && (
206-
<div className={`mb-4 flex justify-end ${classNames?.searchContainer || ""}`}>
231+
<div className={`mb-2 mt-2 mr-2 flex justify-end ${classNames?.searchContainer || ""}`}>
207232
<input
208233
type="text"
209234
value={globalFilter ?? ""}
210235
onChange={(event) => setGlobalFilter(event.target.value)}
211236
placeholder={globalSearchPlaceholder}
212237
aria-label="Search table data"
238+
data-tooltip-id={globalSearchTooltipId}
239+
data-tooltip-content="Searches all data in table (including comments, log dates, and more)."
213240
className={`input input-primary input-bordered border-2 bg-base-300 rounded input-sm w-full max-w-xs focus:outline-secondary ${
214241
classNames?.searchInput || ""
215242
}`}
216243
/>
244+
<TooltipWrapper
245+
id={globalSearchTooltipId}
246+
content="Global search scans all table data, including hidden columns and filters."
247+
className="z-[1099] ourtooltips"
248+
/>
217249
</div>
218250
)}
219251
<table className={classNames?.table || "table"}>

src/tags/tables/columns/TagPeopleColumns.tsx

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createDateTextFilter } from "src/core/utils/tableFilters"
66

77
export type TagPeopleData = {
88
name: string
9-
createdAt: Date
9+
createdAt: Date | null
1010
percentTasksComplete: number | null
1111
percentApproved: number | null
1212
percentFormsComplete: number | null
@@ -15,21 +15,37 @@ export type TagPeopleData = {
1515
type: string
1616
userId: number
1717
projectId: number
18-
completionStatus: "Completed" | "Not completed"
1918
}
2019

2120
const columnHelper = createColumnHelper<TagPeopleData>()
2221
const createdDateFilter = createDateTextFilter({ emptyLabel: "no date" })
23-
const completionStatusFilter: FilterFn<TagPeopleData> = (row, columnId, filterValue) => {
24-
const selected = String(filterValue ?? "").trim()
2522

26-
if (!selected) {
23+
const nullableRangeFilter: FilterFn<TagPeopleData> = (row, columnId, filterValue) => {
24+
const value = row.getValue<number | null>(columnId)
25+
26+
// Always include rows without numeric data
27+
if (value === null || value === undefined) {
2728
return true
2829
}
2930

30-
return String(row.getValue(columnId) ?? "") === selected
31-
}
31+
if (!Array.isArray(filterValue)) {
32+
return true
33+
}
34+
35+
const parseBound = (bound: unknown, fallback: number) => {
36+
if (bound === null || bound === undefined || bound === "") {
37+
return fallback
38+
}
39+
40+
const numeric = typeof bound === "number" ? bound : Number(bound)
41+
return Number.isNaN(numeric) ? fallback : numeric
42+
}
3243

44+
const min = parseBound(filterValue[0], Number.NEGATIVE_INFINITY)
45+
const max = parseBound(filterValue[1], Number.POSITIVE_INFINITY)
46+
47+
return value >= min && value <= max
48+
}
3349
export const TagPeopleColumns = [
3450
columnHelper.accessor("name", {
3551
header: "Name",
@@ -58,28 +74,20 @@ export const TagPeopleColumns = [
5874
}),
5975
columnHelper.accessor("percentTasksComplete", {
6076
header: "Tasks Complete",
61-
cell: (info) => (info.getValue() === null ? "N/A" : `${info.getValue()}%`),
77+
cell: (info) => (info.getValue() === null ? "No tasks" : `${info.getValue()}%`),
6278
enableColumnFilter: true,
6379
enableSorting: true,
80+
filterFn: nullableRangeFilter,
6481
meta: {
6582
filterVariant: "range",
6683
},
6784
}),
68-
columnHelper.accessor("completionStatus", {
69-
header: "Status",
70-
cell: (info) => info.getValue(),
71-
enableColumnFilter: true,
72-
enableSorting: true,
73-
filterFn: completionStatusFilter,
74-
meta: {
75-
filterVariant: "select",
76-
},
77-
}),
7885
columnHelper.accessor("percentApproved", {
7986
header: "Tasks Approved",
80-
cell: (info) => (info.getValue() === null ? "N/A" : `${info.getValue()}%`),
87+
cell: (info) => (info.getValue() === null ? "No tasks" : `${info.getValue()}%`),
8188
enableColumnFilter: true,
8289
enableSorting: true,
90+
filterFn: nullableRangeFilter,
8391
meta: {
8492
filterVariant: "range",
8593
},
@@ -88,10 +96,11 @@ export const TagPeopleColumns = [
8896
header: "Forms Complete",
8997
cell: (info) => {
9098
const row = info.row.original
91-
return row.formAssignedCount === 0 ? "N/A" : `${info.getValue()}%`
99+
return row.formAssignedCount === 0 ? "No forms" : `${info.getValue()}%`
92100
},
93101
enableColumnFilter: true,
94102
enableSorting: true,
103+
filterFn: nullableRangeFilter,
95104
meta: {
96105
filterVariant: "range",
97106
},

src/tags/tables/processing/processTagPeople.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ export function processTagPeople(
7676
type: person.name ? "Team" : "Individual",
7777
userId: person.id,
7878
projectId: projectId,
79-
completionStatus: total > 0 && complete === total ? "Completed" : "Not completed",
8079
}
8180
})
8281
}

src/tasklogs/components/TaskLogHistoryModal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const TaskLogHistoryModal = ({
5454
<ToggleModal
5555
buttonLabel="Show History"
5656
buttonClassName="w-full"
57+
modalSize="w-1/2 max-w-5xl"
5758
modalTitle={
5859
<div className="flex justify-center items-center">
5960
Task History
@@ -78,15 +79,14 @@ export const TaskLogHistoryModal = ({
7879
}}
7980
onClose={handleClose}
8081
>
81-
<div className="modal-action flex flex-col">
82+
<div className="modal-action flex flex-col w-full">
8283
<Table
8384
columns={schema && ui ? TaskLogHistoryFormColumns : TaskLogHistoryCompleteColumns}
8485
data={internalTaskLogHistory}
86+
addPagination={true}
8587
classNames={{
86-
thead: "text-base",
87-
tbody: "text-base",
88+
table: "table w-full",
8889
}}
89-
addPagination={true}
9090
/>
9191
</div>
9292
</ToggleModal>

src/tasklogs/tables/columns/TaskLogCompleteColumns.tsx

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from "react"
2-
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
2+
import { ColumnDef, FilterFn, createColumnHelper } from "@tanstack/react-table"
33
import { TaskLogToggleModal } from "../../components/TaskLogToggleModal"
44
import { ProcessedIndividualTaskLog, ProcessedTeamTaskLog } from "../processing/processTaskLogs"
55
import ToggleModal from "src/core/components/ToggleModal"
@@ -18,10 +18,63 @@ import {
1818
} from "@heroicons/react/24/outline"
1919
import TaskLogHistoryModal from "src/tasklogs/components/TaskLogHistoryModal"
2020
import DateFormat from "src/core/components/DateFormat"
21+
import { createDateTextFilter } from "src/core/utils/tableFilters"
2122

2223
// Column helper
2324
const columnHelper = createColumnHelper<ProcessedIndividualTaskLog | ProcessedTeamTaskLog>()
2425

26+
const lastUpdateFilter = createDateTextFilter({ emptyLabel: "no date" })
27+
28+
const statusFilter: FilterFn<ProcessedIndividualTaskLog | ProcessedTeamTaskLog> = (
29+
row,
30+
columnId,
31+
filterValue
32+
) => {
33+
const selected = String(filterValue ?? "")
34+
.trim()
35+
.toLowerCase()
36+
37+
if (!selected) {
38+
return true
39+
}
40+
41+
const value = String(row.getValue(columnId) ?? "")
42+
.trim()
43+
.toLowerCase()
44+
45+
return value === selected
46+
}
47+
48+
const approvalFilter: FilterFn<ProcessedIndividualTaskLog | ProcessedTeamTaskLog> = (
49+
row,
50+
columnId,
51+
filterValue
52+
) => {
53+
const selected = String(filterValue ?? "")
54+
.trim()
55+
.toLowerCase()
56+
57+
if (!selected) {
58+
return true
59+
}
60+
61+
const value = row.getValue<boolean | null>(columnId)
62+
63+
if (selected === "approved") {
64+
return value === true
65+
}
66+
67+
if (selected === "not approved") {
68+
return value === false
69+
}
70+
71+
if (selected === "pending") {
72+
return value === null
73+
}
74+
75+
return true
76+
}
77+
2578
// ColumnDefs
2679
// Table for assignment without a form
2780
export const TaskLogCompleteColumns: ColumnDef<
@@ -82,7 +135,7 @@ export const TaskLogCompleteColumns: ColumnDef<
82135
<HandRaisedIcon className="h-5 w-5 inline-block" />
83136
</span>
84137
)}
85-
<DateFormat date={info.getValue()} preset="dateShort" />
138+
<DateFormat date={info.getValue()} preset="date" />
86139
</div>
87140
)
88141
},
@@ -101,13 +154,19 @@ export const TaskLogCompleteColumns: ColumnDef<
101154
</div>
102155
),
103156
id: "updatedAt",
157+
enableColumnFilter: true,
158+
enableSorting: true,
159+
filterFn: lastUpdateFilter,
160+
meta: {
161+
filterVariant: "text",
162+
},
104163
}),
105164
columnHelper.accessor("status", {
106165
cell: (info) => {
107166
const value = info.getValue()
108167
const isCompleted = value === "Completed"
109168
return (
110-
<div className="flex justify-center items-center">
169+
<div className="flex">
111170
{isCompleted ? (
112171
<CheckCircleIcon className="h-6 w-6 text-success" title="Completed" />
113172
) : (
@@ -120,8 +179,13 @@ export const TaskLogCompleteColumns: ColumnDef<
120179
id: "status",
121180
enableColumnFilter: true,
122181
enableSorting: true,
182+
filterFn: statusFilter,
123183
meta: {
124184
filterVariant: "select",
185+
selectOptions: [
186+
{ label: "Completed", value: "completed" },
187+
{ label: "Not completed", value: "not completed" },
188+
],
125189
},
126190
}),
127191
columnHelper.accessor("approved", {
@@ -135,14 +199,20 @@ export const TaskLogCompleteColumns: ColumnDef<
135199
} else {
136200
icon = <ClockIcon className="h-6 w-6 text-warning" title="Pending" />
137201
}
138-
return <div className="flex justify-center items-center">{icon}</div>
202+
return <div className="flex">{icon}</div>
139203
},
140204
header: "Approved",
141205
id: "approved",
142206
enableColumnFilter: true,
143207
enableSorting: true,
208+
filterFn: approvalFilter,
144209
meta: {
145210
filterVariant: "select",
211+
selectOptions: [
212+
{ label: "Approved", value: "approved" },
213+
{ label: "Pending", value: "pending" },
214+
{ label: "Not approved", value: "not approved" },
215+
],
146216
},
147217
}),
148218
columnHelper.accessor("taskHistory", {

0 commit comments

Comments
 (0)