torch-glare 2.1.2 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/lib/components/Avatar.tsx +1 -1
- package/apps/lib/components/Card.tsx +68 -54
- package/apps/lib/components/DataViews/DataViewRadio.tsx +47 -0
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +56 -45
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +130 -28
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +32 -2
- package/apps/lib/components/DataViews/FilterPanel.tsx +148 -3
- package/apps/lib/components/DataViews/HeaderSearch.tsx +97 -0
- package/apps/lib/components/DataViews/InboxView.tsx +263 -282
- package/apps/lib/components/DataViews/InboxViewCard.tsx +136 -0
- package/apps/lib/components/DataViews/KanbanView.tsx +264 -153
- package/apps/lib/components/DataViews/PanelControls.tsx +10 -41
- package/apps/lib/components/DataViews/TreeView.tsx +220 -191
- package/apps/lib/components/DataViews/index.ts +6 -0
- package/apps/lib/components/DataViews/types.ts +30 -1
- package/apps/lib/components/Radio.tsx +18 -21
- package/apps/lib/components/Switch.tsx +3 -1
- package/apps/lib/components/Table.tsx +1 -1
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +160 -137
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +221 -93
- package/apps/lib/components/TreeFolder/types.ts +9 -0
- package/apps/lib/layouts/DataViewCard.tsx +76 -0
- package/dist/src/shared/copyComponentsRecursively.js +9 -1
- package/dist/src/shared/copyComponentsRecursively.js.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.d.ts.map +1 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js +9 -1
- package/dist/src/shared/getDependenciesAndInstallNestedComponents.js.map +1 -1
- package/docs/components/data-views-config-panel.md +204 -0
- package/docs/components/data-views-layout.md +270 -0
- package/package.json +1 -1
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import { useMemo, useState } from "react"
|
|
4
|
-
import { Badge } from "../Badge"
|
|
5
|
-
import { FilterPanel } from "./FilterPanel"
|
|
3
|
+
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
4
|
+
import { Badge } from "../Badge";
|
|
5
|
+
import { FilterPanel } from "./FilterPanel";
|
|
6
6
|
import {
|
|
7
|
-
Search,
|
|
8
7
|
Star,
|
|
9
8
|
Archive,
|
|
10
9
|
Trash2,
|
|
@@ -13,10 +12,9 @@ import {
|
|
|
13
12
|
Forward,
|
|
14
13
|
Paperclip,
|
|
15
14
|
InboxIcon,
|
|
16
|
-
Mail,
|
|
17
15
|
AlertCircle,
|
|
18
|
-
} from "lucide-react"
|
|
19
|
-
import { cn } from "../../utils/cn"
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { cn } from "../../utils/cn";
|
|
20
18
|
import type {
|
|
21
19
|
DynamicRecord,
|
|
22
20
|
ViewConfig,
|
|
@@ -26,52 +24,68 @@ import type {
|
|
|
26
24
|
FilterValue,
|
|
27
25
|
FieldConfig,
|
|
28
26
|
InboxConfig,
|
|
29
|
-
} from "./types"
|
|
30
|
-
import TabFormItem from "../TabFormItem"
|
|
31
|
-
import { Button } from "../Button"
|
|
32
|
-
import { Avatar, AvatarFallback } from "../Avatar"
|
|
33
|
-
import { Card } from "../Card"
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
import {
|
|
27
|
+
} from "./types";
|
|
28
|
+
import TabFormItem from "../TabFormItem";
|
|
29
|
+
import { Button } from "../Button";
|
|
30
|
+
import { Avatar, AvatarFallback } from "../Avatar";
|
|
31
|
+
import { Card } from "../Card";
|
|
32
|
+
import { Divider } from "../Divider";
|
|
33
|
+
import { renderDetailView } from "../../utils/dataViews/nestedDataUtils";
|
|
34
|
+
import {
|
|
35
|
+
getByPath,
|
|
36
|
+
setByPath,
|
|
37
|
+
matchesFilterValues,
|
|
38
|
+
} from "../../utils/dataViews/pathUtils";
|
|
39
|
+
import { renderField } from "./fieldRenderers";
|
|
40
|
+
import {
|
|
41
|
+
resolveInboxConfig,
|
|
42
|
+
visibleFields,
|
|
43
|
+
} from "../../utils/dataViews/fieldUtils";
|
|
44
|
+
import { useIsMobile } from "../../hooks/useIsMobile";
|
|
45
|
+
import { resolveBadgeVariant } from "./badgeAdapter";
|
|
46
|
+
import { InboxViewCard } from "./InboxViewCard";
|
|
42
47
|
|
|
43
48
|
export type InboxViewProps = {
|
|
44
|
-
data: DynamicRecord[]
|
|
45
|
-
columns?: DynamicColumnConfig[]
|
|
46
|
-
fields: FieldConfig[]
|
|
47
|
-
inboxConfig?: InboxConfig
|
|
48
|
-
config: ViewConfig
|
|
49
|
-
onDataUpdate?: (data: DynamicRecord[]) => void
|
|
50
|
-
filters?: DynamicFilterConfig[]
|
|
51
|
-
filterState?: FilterState
|
|
52
|
-
onFilterChange?: (filters: FilterState) => void
|
|
53
|
-
showFilters?: boolean
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
data: DynamicRecord[];
|
|
50
|
+
columns?: DynamicColumnConfig[];
|
|
51
|
+
fields: FieldConfig[];
|
|
52
|
+
inboxConfig?: InboxConfig;
|
|
53
|
+
config: ViewConfig;
|
|
54
|
+
onDataUpdate?: (data: DynamicRecord[]) => void;
|
|
55
|
+
filters?: DynamicFilterConfig[];
|
|
56
|
+
filterState?: FilterState;
|
|
57
|
+
onFilterChange?: (filters: FilterState) => void;
|
|
58
|
+
showFilters?: boolean;
|
|
59
|
+
itemHref?: (item: DynamicRecord, id: any) => string;
|
|
60
|
+
selectedItemId?: any;
|
|
61
|
+
renderDetail?: (item: DynamicRecord | null) => ReactNode;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
type InboxFilter = "all" | "starred" | "priority";
|
|
65
|
+
|
|
66
|
+
function getId(
|
|
67
|
+
item: DynamicRecord,
|
|
68
|
+
fallbackPath: string | undefined,
|
|
69
|
+
idx: number,
|
|
70
|
+
): any {
|
|
71
|
+
if (item?.id != null) return item.id;
|
|
60
72
|
if (fallbackPath) {
|
|
61
|
-
const v = getByPath(item, fallbackPath)
|
|
62
|
-
if (v != null) return v
|
|
73
|
+
const v = getByPath(item, fallbackPath);
|
|
74
|
+
if (v != null) return v;
|
|
63
75
|
}
|
|
64
|
-
return idx
|
|
76
|
+
return idx;
|
|
65
77
|
}
|
|
66
78
|
|
|
67
79
|
function getInitials(name: any): string {
|
|
68
|
-
const s = String(name ?? "?").trim()
|
|
69
|
-
if (!s) return "?"
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
const s = String(name ?? "?").trim();
|
|
81
|
+
if (!s) return "?";
|
|
82
|
+
return (
|
|
83
|
+
s
|
|
84
|
+
.split(/\s+/)
|
|
85
|
+
.slice(0, 2)
|
|
86
|
+
.map((p) => p[0]?.toUpperCase() ?? "")
|
|
87
|
+
.join("") || "?"
|
|
88
|
+
);
|
|
75
89
|
}
|
|
76
90
|
|
|
77
91
|
export function InboxView({
|
|
@@ -85,143 +99,156 @@ export function InboxView({
|
|
|
85
99
|
filterState: externalFilterState,
|
|
86
100
|
onFilterChange,
|
|
87
101
|
showFilters = true,
|
|
102
|
+
itemHref,
|
|
103
|
+
selectedItemId,
|
|
104
|
+
renderDetail,
|
|
88
105
|
}: InboxViewProps) {
|
|
89
|
-
const isMobile = useIsMobile()
|
|
90
|
-
const [selectedItem, setSelectedItem] = useState<DynamicRecord | null>(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const [
|
|
106
|
+
const isMobile = useIsMobile();
|
|
107
|
+
const [selectedItem, setSelectedItem] = useState<DynamicRecord | null>(
|
|
108
|
+
data[0] || null,
|
|
109
|
+
);
|
|
110
|
+
const [inboxFilter, setInboxFilter] = useState<InboxFilter>("all");
|
|
111
|
+
const [internalFilters, setInternalFilters] = useState<FilterState>({});
|
|
94
112
|
|
|
95
|
-
const activeFilters = externalFilterState ?? internalFilters
|
|
113
|
+
const activeFilters = externalFilterState ?? internalFilters;
|
|
96
114
|
|
|
97
115
|
const displayFields = useMemo(
|
|
98
116
|
() => visibleFields(fields).sort((a, b) => (a.order ?? 0) - (b.order ?? 0)),
|
|
99
117
|
[fields],
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
const inboxCfg = useMemo(
|
|
103
|
-
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const inboxCfg = useMemo(
|
|
121
|
+
() => resolveInboxConfig(data, userInboxConfig),
|
|
122
|
+
[data, userInboxConfig],
|
|
123
|
+
);
|
|
124
|
+
const idPath = displayFields[0]?.path;
|
|
125
|
+
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (selectedItemId == null) return;
|
|
128
|
+
const match = data.find((item, idx) => {
|
|
129
|
+
const cur = getId(item, idPath, idx);
|
|
130
|
+
return String(cur) === String(selectedItemId);
|
|
131
|
+
});
|
|
132
|
+
if (match) setSelectedItem(match);
|
|
133
|
+
}, [selectedItemId, data, idPath]);
|
|
104
134
|
|
|
105
135
|
const titleField = useMemo(() => {
|
|
106
|
-
const path = inboxCfg.titlePath
|
|
107
|
-
if (path)
|
|
108
|
-
|
|
109
|
-
|
|
136
|
+
const path = inboxCfg.titlePath;
|
|
137
|
+
if (path)
|
|
138
|
+
return (
|
|
139
|
+
fields.find((f) => f.path === path) ?? {
|
|
140
|
+
path,
|
|
141
|
+
label: path,
|
|
142
|
+
type: "text" as const,
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
return displayFields[0];
|
|
146
|
+
}, [inboxCfg.titlePath, displayFields, fields]);
|
|
110
147
|
|
|
111
148
|
const previewField = useMemo(() => {
|
|
112
|
-
const path = inboxCfg.previewPath
|
|
113
|
-
if (path)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
149
|
+
const path = inboxCfg.previewPath;
|
|
150
|
+
if (path)
|
|
151
|
+
return (
|
|
152
|
+
fields.find((f) => f.path === path) ?? {
|
|
153
|
+
path,
|
|
154
|
+
label: path,
|
|
155
|
+
type: "text" as const,
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
return displayFields[1];
|
|
159
|
+
}, [inboxCfg.previewPath, displayFields, fields]);
|
|
118
160
|
|
|
119
161
|
const isStarred = (item: DynamicRecord) =>
|
|
120
|
-
inboxCfg.starredField ? !!getByPath(item, inboxCfg.starredField) : false
|
|
121
|
-
const isRead = (item: DynamicRecord) =>
|
|
122
|
-
inboxCfg.readField ? !!getByPath(item, inboxCfg.readField) : true
|
|
162
|
+
inboxCfg.starredField ? !!getByPath(item, inboxCfg.starredField) : false;
|
|
123
163
|
const hasAttachment = (item: DynamicRecord) => {
|
|
124
|
-
if (!inboxCfg.attachmentField) return false
|
|
125
|
-
const v = getByPath(item, inboxCfg.attachmentField)
|
|
126
|
-
if (typeof v === "boolean") return v
|
|
127
|
-
if (Array.isArray(v)) return v.length > 0
|
|
128
|
-
return v != null
|
|
129
|
-
}
|
|
164
|
+
if (!inboxCfg.attachmentField) return false;
|
|
165
|
+
const v = getByPath(item, inboxCfg.attachmentField);
|
|
166
|
+
if (typeof v === "boolean") return v;
|
|
167
|
+
if (Array.isArray(v)) return v.length > 0;
|
|
168
|
+
return v != null;
|
|
169
|
+
};
|
|
130
170
|
const isHighPriority = (item: DynamicRecord) => {
|
|
131
|
-
if (!inboxCfg.priorityField) return false
|
|
132
|
-
const v = getByPath(item, inboxCfg.priorityField)
|
|
133
|
-
return String(v).toLowerCase() === "high"
|
|
134
|
-
}
|
|
171
|
+
if (!inboxCfg.priorityField) return false;
|
|
172
|
+
const v = getByPath(item, inboxCfg.priorityField);
|
|
173
|
+
return String(v).toLowerCase() === "high";
|
|
174
|
+
};
|
|
135
175
|
|
|
136
176
|
const toggleStar = (itemId: any) => {
|
|
137
|
-
if (!inboxCfg.starredField) return
|
|
177
|
+
if (!inboxCfg.starredField) return;
|
|
138
178
|
const updatedData = data.map((item, idx) => {
|
|
139
|
-
const cur = getId(item, idPath, idx)
|
|
179
|
+
const cur = getId(item, idPath, idx);
|
|
140
180
|
if (cur === itemId) {
|
|
141
|
-
const next = !getByPath(item, inboxCfg.starredField!)
|
|
142
|
-
return setByPath(item, inboxCfg.starredField!, next)
|
|
181
|
+
const next = !getByPath(item, inboxCfg.starredField!);
|
|
182
|
+
return setByPath(item, inboxCfg.starredField!, next);
|
|
143
183
|
}
|
|
144
|
-
return item
|
|
145
|
-
})
|
|
146
|
-
onDataUpdate?.(updatedData)
|
|
184
|
+
return item;
|
|
185
|
+
});
|
|
186
|
+
onDataUpdate?.(updatedData);
|
|
147
187
|
|
|
148
188
|
if (selectedItem && getId(selectedItem, idPath, -1) === itemId) {
|
|
149
189
|
setSelectedItem((prev) =>
|
|
150
190
|
prev && inboxCfg.starredField
|
|
151
|
-
? setByPath(
|
|
191
|
+
? setByPath(
|
|
192
|
+
prev,
|
|
193
|
+
inboxCfg.starredField,
|
|
194
|
+
!getByPath(prev, inboxCfg.starredField),
|
|
195
|
+
)
|
|
152
196
|
: prev,
|
|
153
|
-
)
|
|
197
|
+
);
|
|
154
198
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const markAsRead = (itemId: any) => {
|
|
158
|
-
if (!inboxCfg.readField) return
|
|
159
|
-
const updatedData = data.map((item, idx) => {
|
|
160
|
-
const cur = getId(item, idPath, idx)
|
|
161
|
-
if (cur === itemId) {
|
|
162
|
-
return setByPath(item, inboxCfg.readField!, true)
|
|
163
|
-
}
|
|
164
|
-
return item
|
|
165
|
-
})
|
|
166
|
-
onDataUpdate?.(updatedData)
|
|
167
|
-
}
|
|
199
|
+
};
|
|
168
200
|
|
|
169
|
-
const handleSelectItem = (item: DynamicRecord
|
|
170
|
-
setSelectedItem(item)
|
|
171
|
-
|
|
172
|
-
}
|
|
201
|
+
const handleSelectItem = (item: DynamicRecord) => {
|
|
202
|
+
setSelectedItem(item);
|
|
203
|
+
};
|
|
173
204
|
|
|
174
205
|
const handleFilterChange = (path: string, value: FilterValue) => {
|
|
175
|
-
const newFilters: FilterState = { ...activeFilters, [path]: value }
|
|
176
|
-
if (onFilterChange) onFilterChange(newFilters)
|
|
177
|
-
else setInternalFilters(newFilters)
|
|
178
|
-
}
|
|
206
|
+
const newFilters: FilterState = { ...activeFilters, [path]: value };
|
|
207
|
+
if (onFilterChange) onFilterChange(newFilters);
|
|
208
|
+
else setInternalFilters(newFilters);
|
|
209
|
+
};
|
|
179
210
|
|
|
180
211
|
const clearAllFilters = () => {
|
|
181
|
-
if (onFilterChange) onFilterChange({})
|
|
182
|
-
else setInternalFilters({})
|
|
183
|
-
}
|
|
212
|
+
if (onFilterChange) onFilterChange({});
|
|
213
|
+
else setInternalFilters({});
|
|
214
|
+
};
|
|
184
215
|
|
|
185
216
|
const filteredData = useMemo(() => {
|
|
186
217
|
return data.filter((item) => {
|
|
187
|
-
const matchesSearch = !searchQuery
|
|
188
|
-
? true
|
|
189
|
-
: displayFields.some((f) => {
|
|
190
|
-
const value = getByPath(item, f.path)
|
|
191
|
-
if (value == null) return false
|
|
192
|
-
return String(value).toLowerCase().includes(searchQuery.toLowerCase())
|
|
193
|
-
})
|
|
194
|
-
|
|
195
218
|
const matchesInboxFilter =
|
|
196
219
|
inboxFilter === "all" ||
|
|
197
|
-
(inboxFilter === "unread" && !isRead(item)) ||
|
|
198
220
|
(inboxFilter === "starred" && isStarred(item)) ||
|
|
199
|
-
(inboxFilter === "priority" && isHighPriority(item))
|
|
221
|
+
(inboxFilter === "priority" && isHighPriority(item));
|
|
200
222
|
|
|
201
|
-
const matchesFilters = Object.entries(activeFilters).every(
|
|
202
|
-
matchesFilterValues(item, path, filterValues),
|
|
203
|
-
)
|
|
223
|
+
const matchesFilters = Object.entries(activeFilters).every(
|
|
224
|
+
([path, filterValues]) => matchesFilterValues(item, path, filterValues),
|
|
225
|
+
);
|
|
204
226
|
|
|
205
|
-
return
|
|
206
|
-
})
|
|
227
|
+
return matchesInboxFilter && matchesFilters;
|
|
228
|
+
});
|
|
207
229
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
208
|
-
}, [data,
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
230
|
+
}, [data, inboxFilter, activeFilters, inboxCfg]);
|
|
231
|
+
|
|
232
|
+
const starredCount = inboxCfg.starredField
|
|
233
|
+
? data.filter((i) => isStarred(i)).length
|
|
234
|
+
: 0;
|
|
235
|
+
const priorityCount = inboxCfg.priorityField
|
|
236
|
+
? data.filter((i) => isHighPriority(i)).length
|
|
237
|
+
: 0;
|
|
238
|
+
|
|
239
|
+
const filtersEnabled = showFilters && config.showFilters !== false;
|
|
240
|
+
const countBadge = resolveBadgeVariant("gray");
|
|
241
|
+
const fallbackColumns: DynamicColumnConfig[] =
|
|
242
|
+
columns ??
|
|
243
|
+
displayFields.map((f, i) => ({
|
|
244
|
+
id: f.path,
|
|
245
|
+
label: f.label ?? f.path,
|
|
246
|
+
visible: true,
|
|
247
|
+
order: i,
|
|
248
|
+
}));
|
|
222
249
|
|
|
223
250
|
return (
|
|
224
|
-
<div className="flex h-full flex-col
|
|
251
|
+
<div className="flex h-full flex-col md:flex-row gap-2">
|
|
225
252
|
{filtersEnabled && !isMobile && (
|
|
226
253
|
<div className="w-64 border-r border-border-presentation-global-primary bg-background-presentation-body-overlay-primary flex flex-col">
|
|
227
254
|
<div className="p-4 space-y-1">
|
|
@@ -232,21 +259,13 @@ export function InboxView({
|
|
|
232
259
|
>
|
|
233
260
|
<InboxIcon className="h-4 w-4" />
|
|
234
261
|
All Items
|
|
235
|
-
<Badge
|
|
262
|
+
<Badge
|
|
263
|
+
{...countBadge}
|
|
264
|
+
label={String(data.length)}
|
|
265
|
+
className="ml-auto"
|
|
266
|
+
size="XS"
|
|
267
|
+
/>
|
|
236
268
|
</TabFormItem>
|
|
237
|
-
{inboxCfg.readField && (
|
|
238
|
-
<TabFormItem
|
|
239
|
-
componentType="side"
|
|
240
|
-
className="w-full justify-start gap-2"
|
|
241
|
-
onClick={() => setInboxFilter("unread")}
|
|
242
|
-
>
|
|
243
|
-
<Mail className="h-4 w-4" />
|
|
244
|
-
Unread
|
|
245
|
-
{unreadCount > 0 && (
|
|
246
|
-
<Badge {...countBadge} label={String(unreadCount)} className="ml-auto" size="XS" />
|
|
247
|
-
)}
|
|
248
|
-
</TabFormItem>
|
|
249
|
-
)}
|
|
250
269
|
{inboxCfg.starredField && (
|
|
251
270
|
<TabFormItem
|
|
252
271
|
componentType="side"
|
|
@@ -256,7 +275,12 @@ export function InboxView({
|
|
|
256
275
|
<Star className="h-4 w-4" />
|
|
257
276
|
Starred
|
|
258
277
|
{starredCount > 0 && (
|
|
259
|
-
<Badge
|
|
278
|
+
<Badge
|
|
279
|
+
{...countBadge}
|
|
280
|
+
label={String(starredCount)}
|
|
281
|
+
className="ml-auto"
|
|
282
|
+
size="XS"
|
|
283
|
+
/>
|
|
260
284
|
)}
|
|
261
285
|
</TabFormItem>
|
|
262
286
|
)}
|
|
@@ -269,7 +293,12 @@ export function InboxView({
|
|
|
269
293
|
<AlertCircle className="h-4 w-4" />
|
|
270
294
|
Priority
|
|
271
295
|
{priorityCount > 0 && (
|
|
272
|
-
<Badge
|
|
296
|
+
<Badge
|
|
297
|
+
{...countBadge}
|
|
298
|
+
label={String(priorityCount)}
|
|
299
|
+
className="ml-auto"
|
|
300
|
+
size="XS"
|
|
301
|
+
/>
|
|
273
302
|
)}
|
|
274
303
|
</TabFormItem>
|
|
275
304
|
)}
|
|
@@ -277,7 +306,7 @@ export function InboxView({
|
|
|
277
306
|
|
|
278
307
|
<Divider />
|
|
279
308
|
|
|
280
|
-
<div className="flex-1
|
|
309
|
+
<div className="flex-1 overflow-y-auto">
|
|
281
310
|
<FilterPanel
|
|
282
311
|
data={data}
|
|
283
312
|
fields={fields}
|
|
@@ -290,120 +319,50 @@ export function InboxView({
|
|
|
290
319
|
</div>
|
|
291
320
|
)}
|
|
292
321
|
|
|
293
|
-
<div
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
</div>
|
|
322
|
+
<div
|
|
323
|
+
className={cn(
|
|
324
|
+
"border rounded-[16px] border-border-presentation-global-primary flex flex-col bg-background-presentation-form-base overflow-hidden",
|
|
325
|
+
isMobile ? "flex-1" : "w-full md:w-96",
|
|
326
|
+
)}
|
|
327
|
+
>
|
|
328
|
+
<div className="px-3 py-2 border-b border-border-presentation-global-primary">
|
|
329
|
+
<span
|
|
330
|
+
style={{ fontFeatureSettings: "'cv05' on" }}
|
|
331
|
+
className="typography-display-medium-medium uppercase text-content-presentation-global-primary"
|
|
332
|
+
>
|
|
333
|
+
inbox
|
|
334
|
+
</span>
|
|
307
335
|
</div>
|
|
308
|
-
|
|
309
|
-
<div className="flex-1 overflow-y-auto">
|
|
336
|
+
<div className="flex-1 flex flex-col overflow-y-auto gap-1 py-1 bg-background-presentation-button-disabled">
|
|
310
337
|
{filteredData.map((item, idx) => {
|
|
311
|
-
const itemId = getId(item, idPath, idx)
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
338
|
+
const itemId = getId(item, idPath, idx);
|
|
339
|
+
const selected =
|
|
340
|
+
(selectedItemId != null &&
|
|
341
|
+
String(selectedItemId) === String(itemId)) ||
|
|
342
|
+
(selectedItem != null &&
|
|
343
|
+
getId(selectedItem, idPath, -1) === itemId);
|
|
317
344
|
|
|
318
345
|
return (
|
|
319
|
-
<
|
|
346
|
+
<InboxViewCard
|
|
320
347
|
key={itemId}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
>
|
|
329
|
-
<Avatar className="h-10 w-10 shrink-0">
|
|
330
|
-
<AvatarFallback className="bg-background-presentation-action-primary text-content-presentation-action-primary text-sm">
|
|
331
|
-
{getInitials(previewValue || titleValue)}
|
|
332
|
-
</AvatarFallback>
|
|
333
|
-
</Avatar>
|
|
334
|
-
<div className="flex-1 min-w-0">
|
|
335
|
-
<div className="flex items-start justify-between gap-2 mb-1">
|
|
336
|
-
<p
|
|
337
|
-
className={cn(
|
|
338
|
-
"text-sm truncate",
|
|
339
|
-
read
|
|
340
|
-
? "font-normal text-content-presentation-global-secondary"
|
|
341
|
-
: "font-semibold text-content-presentation-global-primary",
|
|
342
|
-
)}
|
|
343
|
-
>
|
|
344
|
-
{String(previewValue ?? "")}
|
|
345
|
-
</p>
|
|
346
|
-
<div className="flex items-center gap-1 shrink-0">
|
|
347
|
-
{hasAttachment(item) && (
|
|
348
|
-
<Paperclip className="h-3 w-3 text-content-presentation-global-tertiary" />
|
|
349
|
-
)}
|
|
350
|
-
{inboxCfg.starredField && (
|
|
351
|
-
<button
|
|
352
|
-
onClick={(e) => {
|
|
353
|
-
e.stopPropagation()
|
|
354
|
-
toggleStar(itemId)
|
|
355
|
-
}}
|
|
356
|
-
className="hover:text-content-presentation-badge-yellow transition-colors"
|
|
357
|
-
aria-label="Toggle star"
|
|
358
|
-
>
|
|
359
|
-
<Star
|
|
360
|
-
className={cn(
|
|
361
|
-
"h-4 w-4",
|
|
362
|
-
isStarred(item)
|
|
363
|
-
? "fill-content-presentation-badge-yellow text-content-presentation-badge-yellow"
|
|
364
|
-
: "text-content-presentation-global-tertiary",
|
|
365
|
-
)}
|
|
366
|
-
/>
|
|
367
|
-
</button>
|
|
368
|
-
)}
|
|
369
|
-
</div>
|
|
370
|
-
</div>
|
|
371
|
-
<p
|
|
372
|
-
className={cn(
|
|
373
|
-
"text-sm mb-1 truncate",
|
|
374
|
-
read
|
|
375
|
-
? "font-normal text-content-presentation-global-secondary"
|
|
376
|
-
: "font-medium text-content-presentation-global-primary",
|
|
377
|
-
)}
|
|
378
|
-
>
|
|
379
|
-
{String(titleValue ?? "")}
|
|
380
|
-
</p>
|
|
381
|
-
{detailField && detailValue != null && (
|
|
382
|
-
<p className="text-xs text-content-presentation-global-secondary truncate leading-relaxed">
|
|
383
|
-
{String(detailValue)}
|
|
384
|
-
</p>
|
|
385
|
-
)}
|
|
386
|
-
<div className="flex items-center gap-2 mt-2">
|
|
387
|
-
{displayFields.slice(3, 5).map((field) => {
|
|
388
|
-
const value = getByPath(item, field.path)
|
|
389
|
-
if (value == null) return null
|
|
390
|
-
return (
|
|
391
|
-
<span key={field.path} className="text-xs">
|
|
392
|
-
{renderField(value, field, item)}
|
|
393
|
-
</span>
|
|
394
|
-
)
|
|
395
|
-
})}
|
|
396
|
-
</div>
|
|
397
|
-
</div>
|
|
398
|
-
</div>
|
|
399
|
-
)
|
|
348
|
+
item={item}
|
|
349
|
+
rowFields={displayFields}
|
|
350
|
+
selected={selected}
|
|
351
|
+
onSelect={() => handleSelectItem(item)}
|
|
352
|
+
href={itemHref?.(item, itemId)}
|
|
353
|
+
/>
|
|
354
|
+
);
|
|
400
355
|
})}
|
|
401
356
|
</div>
|
|
402
357
|
</div>
|
|
403
358
|
|
|
404
|
-
{
|
|
405
|
-
<div className="flex-1 flex flex-col bg-background-presentation-
|
|
406
|
-
|
|
359
|
+
{renderDetail && !isMobile ? (
|
|
360
|
+
<div className="flex-1 flex flex-col bg-background-presentation-form-base overflow-hidden rounded-[16px]">
|
|
361
|
+
{renderDetail(selectedItem)}
|
|
362
|
+
</div>
|
|
363
|
+
) : config.showPreviewPane && !isMobile && selectedItem ? (
|
|
364
|
+
<div className="flex-1 flex flex-col bg-background-presentation-form-base overflow-hidden rounded-[16px] border border-border-presentation-global-primary">
|
|
365
|
+
<div className="flex items-center justify-between gap-4 p-4 border-b border-border-presentation-global-primary bg-background-presentation-form-base">
|
|
407
366
|
<div className="flex items-center gap-2">
|
|
408
367
|
<Button variant="BorderStyle" buttonType="icon">
|
|
409
368
|
<Archive className="h-4 w-4" />
|
|
@@ -423,14 +382,22 @@ export function InboxView({
|
|
|
423
382
|
<div className="flex items-start gap-4 mb-6">
|
|
424
383
|
<Avatar className="h-12 w-12">
|
|
425
384
|
<AvatarFallback className="bg-background-presentation-action-primary text-content-presentation-action-primary">
|
|
426
|
-
{getInitials(
|
|
385
|
+
{getInitials(
|
|
386
|
+
previewField
|
|
387
|
+
? getByPath(selectedItem, previewField.path)
|
|
388
|
+
: "",
|
|
389
|
+
)}
|
|
427
390
|
</AvatarFallback>
|
|
428
391
|
</Avatar>
|
|
429
392
|
<div className="flex-1">
|
|
430
393
|
<div className="flex items-start justify-between gap-4 mb-2">
|
|
431
394
|
<div>
|
|
432
395
|
<h2 className="text-xl font-semibold text-content-presentation-global-primary mb-1">
|
|
433
|
-
{String(
|
|
396
|
+
{String(
|
|
397
|
+
titleField
|
|
398
|
+
? getByPath(selectedItem, titleField.path)
|
|
399
|
+
: "",
|
|
400
|
+
)}
|
|
434
401
|
</h2>
|
|
435
402
|
{previewField && (
|
|
436
403
|
<p className="text-sm text-content-presentation-global-tertiary">
|
|
@@ -443,7 +410,9 @@ export function InboxView({
|
|
|
443
410
|
</div>
|
|
444
411
|
{inboxCfg.starredField && (
|
|
445
412
|
<button
|
|
446
|
-
onClick={() =>
|
|
413
|
+
onClick={() =>
|
|
414
|
+
toggleStar(getId(selectedItem, idPath, -1))
|
|
415
|
+
}
|
|
447
416
|
className="hover:text-content-presentation-badge-yellow transition-colors"
|
|
448
417
|
aria-label="Toggle star"
|
|
449
418
|
>
|
|
@@ -460,9 +429,13 @@ export function InboxView({
|
|
|
460
429
|
</div>
|
|
461
430
|
<div className="flex items-center gap-2 flex-wrap">
|
|
462
431
|
{displayFields.slice(3).map((field) => {
|
|
463
|
-
const value = getByPath(selectedItem, field.path)
|
|
464
|
-
if (value == null) return null
|
|
465
|
-
return
|
|
432
|
+
const value = getByPath(selectedItem, field.path);
|
|
433
|
+
if (value == null) return null;
|
|
434
|
+
return (
|
|
435
|
+
<span key={field.path}>
|
|
436
|
+
{renderField(value, field, selectedItem)}
|
|
437
|
+
</span>
|
|
438
|
+
);
|
|
466
439
|
})}
|
|
467
440
|
</div>
|
|
468
441
|
</div>
|
|
@@ -474,9 +447,9 @@ export function InboxView({
|
|
|
474
447
|
selectedItem,
|
|
475
448
|
fallbackColumns.filter((c) => c.visible),
|
|
476
449
|
(value, column, row) => {
|
|
477
|
-
const f = fields.find((field) => field.path === column.id)
|
|
478
|
-
if (f) return renderField(value, f, row)
|
|
479
|
-
return <span>{String(value ?? "")}</span
|
|
450
|
+
const f = fields.find((field) => field.path === column.id);
|
|
451
|
+
if (f) return renderField(value, f, row);
|
|
452
|
+
return <span>{String(value ?? "")}</span>;
|
|
480
453
|
},
|
|
481
454
|
)}
|
|
482
455
|
|
|
@@ -485,15 +458,19 @@ export function InboxView({
|
|
|
485
458
|
<Divider className="my-6" />
|
|
486
459
|
<div className="flex items-center gap-2 p-3 rounded-lg bg-background-presentation-form-field-primary">
|
|
487
460
|
<Paperclip className="h-4 w-4 text-content-presentation-global-tertiary" />
|
|
488
|
-
<span className="text-sm text-content-presentation-global-primary">
|
|
489
|
-
|
|
461
|
+
<span className="text-sm text-content-presentation-global-primary">
|
|
462
|
+
attachment.pdf
|
|
463
|
+
</span>
|
|
464
|
+
<span className="text-xs text-content-presentation-global-tertiary">
|
|
465
|
+
(2.4 MB)
|
|
466
|
+
</span>
|
|
490
467
|
</div>
|
|
491
468
|
</>
|
|
492
469
|
)}
|
|
493
470
|
</Card>
|
|
494
471
|
</div>
|
|
495
472
|
|
|
496
|
-
<div className="flex items-center gap-2 p-4 border-t border-border-presentation-global-primary bg-background-presentation-
|
|
473
|
+
<div className="flex items-center gap-2 p-4 border-t border-border-presentation-global-primary bg-background-presentation-form-base">
|
|
497
474
|
<Button className="gap-2">
|
|
498
475
|
<Reply className="h-4 w-4" />
|
|
499
476
|
Reply
|
|
@@ -504,11 +481,15 @@ export function InboxView({
|
|
|
504
481
|
</Button>
|
|
505
482
|
</div>
|
|
506
483
|
</div>
|
|
507
|
-
) :
|
|
508
|
-
|
|
509
|
-
<
|
|
510
|
-
|
|
484
|
+
) : (
|
|
485
|
+
!isMobile && (
|
|
486
|
+
<div className="flex-1 flex items-center justify-center bg-background-presentation-form-base overflow-hidden rounded-[16px] border border-border-presentation-global-primary">
|
|
487
|
+
<p className="text-content-presentation-global-tertiary">
|
|
488
|
+
Select an item to view details
|
|
489
|
+
</p>
|
|
490
|
+
</div>
|
|
491
|
+
)
|
|
511
492
|
)}
|
|
512
493
|
</div>
|
|
513
|
-
)
|
|
494
|
+
);
|
|
514
495
|
}
|