blixify-ui-web-mcp 0.1.2 → 0.1.3
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/lib/components/chart/ganttChart/README.md +96 -110
- package/lib/components/chart/ganttChart/ganttChart.stories.tsx +132 -116
- package/lib/components/chart/heatmapChart/README.md +93 -82
- package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +118 -74
- package/lib/components/chat/chatAnchor/README.md +61 -79
- package/lib/components/chat/chatBase/README.md +166 -199
- package/lib/components/chat/chatBase/chatBase.stories.tsx +243 -265
- package/lib/components/chat/chatDashboard/README.md +87 -129
- package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +84 -107
- package/lib/components/data/dataGraph/README.md +135 -1
- package/lib/components/data/dataGraph/dataGraph.stories.tsx +86 -67
- package/lib/components/data/dataReference/README.md +141 -43
- package/lib/components/data/dataReference/dataReference.stories.tsx +107 -35
- package/lib/components/data/dataWrapper/README.md +2 -1
- package/lib/components/display/table/README.md +1 -1
- package/lib/components/navigation/bars/README.md +90 -17
- package/lib/components/navigation/bars/bars.stories.tsx +125 -51
- package/lib/components/navigation/breadcrumb/README.md +40 -20
- package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +30 -4
- package/lib/components/navigation/header/README.md +67 -108
- package/lib/components/navigation/header/header.stories.tsx +143 -39
- package/lib/components/navigation/leftNav/README.md +121 -66
- package/lib/components/navigation/leftNav/leftNav.stories.tsx +176 -83
- package/lib/components/navigation/tabs/tabs.stories.tsx +53 -29
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<hr>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A two-panel chat layout with a scrollable conversation list on the left and a `ChatBase` chat window on the right. On mobile, the two panels toggle — the list hides when a conversation is selected and reappears when the user navigates back. The conversation list is sorted and updated automatically as new messages are sent.
|
|
8
8
|
|
|
9
9
|
##### Import
|
|
10
10
|
|
|
@@ -16,146 +16,104 @@ import { ChatDashboard } from "blixify-ui-web/components/chat/chatDashboard";
|
|
|
16
16
|
|
|
17
17
|
```jsx
|
|
18
18
|
const [message, setMessage] = useState("");
|
|
19
|
-
const [
|
|
20
|
-
const [
|
|
19
|
+
const [conversation, setConversation] = useState<Conversation>(conversationList[0]);
|
|
20
|
+
const [conversationList, setConversationList] = useState<Conversation[]>(seedList);
|
|
21
21
|
const [mobileSelectedChat, setMobileSelectedChat] = useState(false);
|
|
22
|
-
const [filteredConversation, setFilteredConversation] =
|
|
23
|
-
useState<Conversation | null>();
|
|
24
|
-
const [conversation, setConversation] = useState<Conversation>({
|
|
25
|
-
id: "",
|
|
26
|
-
members: [],
|
|
27
|
-
messageList: [],
|
|
28
|
-
});
|
|
29
|
-
const [conversationList, setConversationList] = useState<Conversation[]>([]);
|
|
30
|
-
|
|
31
|
-
const handleOnSendMessage = (conversation: Conversation) => {
|
|
32
|
-
setConversation({
|
|
33
|
-
id: conversation.id,
|
|
34
|
-
members: [...conversation.members],
|
|
35
|
-
messageList: [...conversation.messageList],
|
|
36
|
-
});
|
|
37
|
-
setMessage("");
|
|
38
|
-
const dupConvoList = conversationList.map((eachConvo, i) => {
|
|
39
|
-
if (eachConvo.id === conversation.id) {
|
|
40
|
-
return conversation;
|
|
41
|
-
}
|
|
42
|
-
return eachConvo;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
handleSortConvoList(dupConvoList);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const handleSortConvoList = (convoList: Conversation[]) => {
|
|
49
|
-
const sortedConvoList = convoList.sort((a, b) => {
|
|
50
|
-
return moment(b.messageList[b.messageList.length - 1].createdAt).diff(
|
|
51
|
-
moment(a.messageList[a.messageList.length - 1].createdAt)
|
|
52
|
-
);
|
|
53
|
-
});
|
|
54
|
-
setConversationList(sortedConvoList);
|
|
55
|
-
setConversation(sortedConvoList[0]);
|
|
56
|
-
};
|
|
57
22
|
|
|
58
|
-
const
|
|
59
|
-
setIsLoading(true);
|
|
23
|
+
const handleSelectConversation = (id: string) => {
|
|
60
24
|
setMobileSelectedChat(true);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
conversationList.map((eachConversation) => {
|
|
64
|
-
if (eachConversation.id === id) {
|
|
65
|
-
setConversation(eachConversation);
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
});
|
|
69
|
-
}, 1000);
|
|
25
|
+
const found = conversationList.find((c) => c.id === id);
|
|
26
|
+
if (found) setConversation(found);
|
|
70
27
|
};
|
|
71
28
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
return eachMessage.messageBody
|
|
79
|
-
.toLowerCase()
|
|
80
|
-
.includes(searchMessage.toLowerCase());
|
|
81
|
-
});
|
|
82
|
-
const dupConversation = {
|
|
83
|
-
id: conversation.id,
|
|
84
|
-
members: [...conversation.members],
|
|
85
|
-
messageList: [...dupMessageList],
|
|
86
|
-
};
|
|
87
|
-
setFilteredConversation(dupConversation);
|
|
88
|
-
}
|
|
29
|
+
const handleSendMessage = (updated: Conversation) => {
|
|
30
|
+
setConversation(updated);
|
|
31
|
+
setMessage("");
|
|
32
|
+
setConversationList((prev) =>
|
|
33
|
+
prev.map((c) => (c.id === updated.id ? updated : c))
|
|
34
|
+
);
|
|
89
35
|
};
|
|
90
36
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
bodyElement={renderBodyElement()}
|
|
103
|
-
onChangeMessage={(e) => setMessage(e.target.value)}
|
|
104
|
-
handleSelectedConversation={handleMessageList}
|
|
105
|
-
onSendMessage={handleOnSendMessage}
|
|
106
|
-
onChangeSearch={(e) => setSearch(e.target.value)}
|
|
107
|
-
onSearch={handleOnSearch}
|
|
108
|
-
/>
|
|
37
|
+
// Custom header with a back button for mobile
|
|
38
|
+
const headerElement = (
|
|
39
|
+
<div className="flex items-center h-12 bg-primary-500 text-white px-4">
|
|
40
|
+
<button
|
|
41
|
+
className="sm:hidden mr-2"
|
|
42
|
+
onClick={() => setMobileSelectedChat(false)}
|
|
43
|
+
>
|
|
44
|
+
←
|
|
45
|
+
</button>
|
|
46
|
+
<span>{otherUser?.name}</span>
|
|
47
|
+
</div>
|
|
109
48
|
);
|
|
49
|
+
|
|
50
|
+
<ChatDashboard
|
|
51
|
+
value={message}
|
|
52
|
+
currentUser={currentUser}
|
|
53
|
+
selectedConversation={conversation}
|
|
54
|
+
conversationList={conversationList}
|
|
55
|
+
mobileSelectedChat={mobileSelectedChat}
|
|
56
|
+
sizeClassName="w-64 h-[60vh]"
|
|
57
|
+
chatBaseSizeClassName="w-full h-[60vh]"
|
|
58
|
+
headerElement={headerElement}
|
|
59
|
+
onChangeMessage={(e) => setMessage(e.target.value)}
|
|
60
|
+
onSendMessage={handleSendMessage}
|
|
61
|
+
handleSelectedConversation={handleSelectConversation}
|
|
62
|
+
/>
|
|
110
63
|
```
|
|
111
64
|
|
|
112
65
|
##### Required props
|
|
113
66
|
|
|
114
|
-
| Name
|
|
115
|
-
|
|
|
116
|
-
| value
|
|
117
|
-
| currentUser
|
|
118
|
-
| selectedConversation
|
|
119
|
-
| conversationList
|
|
67
|
+
| Name | Type | Description |
|
|
68
|
+
| ---------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
69
|
+
| `value` | `string` | Current message input value |
|
|
70
|
+
| `currentUser` | `User` | The logged-in user — used to identify which side messages appear on and to find the other user in each conversation |
|
|
71
|
+
| `selectedConversation` | `Conversation` | The conversation currently displayed in the chat panel |
|
|
72
|
+
| `conversationList` | `Conversation[]` | All conversations shown in the left panel list |
|
|
73
|
+
| `handleSelectedConversation` | `(id: string) => void` | Called with the conversation `id` when a list item is clicked |
|
|
120
74
|
|
|
121
75
|
##### Optional props
|
|
122
76
|
|
|
123
|
-
| Name
|
|
124
|
-
|
|
|
125
|
-
| mobileSelectedChat
|
|
126
|
-
| searchMessage
|
|
127
|
-
| headerElement
|
|
128
|
-
| bodyElement
|
|
129
|
-
| className
|
|
130
|
-
| sizeClassName
|
|
131
|
-
| chatBaseClassName
|
|
132
|
-
| chatBaseSizeClassName
|
|
133
|
-
| onSendMessage
|
|
134
|
-
| onChangeMessage
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
|
153
|
-
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
|
161
|
-
|
|
|
77
|
+
| Name | Type | Default | Description |
|
|
78
|
+
| ----------------------- | -------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `mobileSelectedChat` | `boolean` | `false` | When `true`, hides the conversation list and shows the chat panel on mobile. Set to `false` to go back to the list. |
|
|
80
|
+
| `searchMessage` | `string` | — | Current search input value. Pass a filtered conversation to `selectedConversation` to show results. |
|
|
81
|
+
| `headerElement` | `ReactElement` | — | Replaces the default `ChatBase` header. Use this to show the other user's name and a mobile back button. |
|
|
82
|
+
| `bodyElement` | `ReactElement` | — | Replaces the `ChatBase` message list. Useful for showing a loading spinner while fetching messages. |
|
|
83
|
+
| `className` | `string` | — | Tailwind classes for the conversation list panel container |
|
|
84
|
+
| `sizeClassName` | `string` | `h-[75vh]` | Tailwind classes controlling the height/width of the conversation list panel |
|
|
85
|
+
| `chatBaseClassName` | `string` | — | Tailwind classes for the `ChatBase` container |
|
|
86
|
+
| `chatBaseSizeClassName` | `string` | — | Tailwind classes controlling the height/width of the `ChatBase` panel |
|
|
87
|
+
| `onSendMessage` | `(conversation: Conversation) => void` | — | Called with the updated conversation when a message is sent |
|
|
88
|
+
| `onChangeMessage` | `(event: any) => void` | — | Called on every keystroke in the message input |
|
|
89
|
+
| `onSearch` | `(searchMessage: string) => void` | — | Called when the search form is submitted inside `ChatBase` |
|
|
90
|
+
| `onChangeSearch` | `(event: any) => void` | — | Called on every keystroke in the search input inside `ChatBase` |
|
|
91
|
+
|
|
92
|
+
##### Conversation list behaviour
|
|
93
|
+
|
|
94
|
+
Each item in `conversationList` renders the other user's avatar and name (the member whose `id` differs from `currentUser.id`) and the last message body as a preview. Items with a `FileObj` message body show an empty preview string.
|
|
95
|
+
|
|
96
|
+
##### User
|
|
97
|
+
|
|
98
|
+
| Name | Type | Description |
|
|
99
|
+
| ---------- | -------- | ------------------------------------------------- |
|
|
100
|
+
| `id` | `string` | Unique identifier |
|
|
101
|
+
| `name` | `string` | Display name |
|
|
102
|
+
| `imageUrl` | `string` | Avatar URL — shown in the conversation list items |
|
|
103
|
+
|
|
104
|
+
##### Conversation
|
|
105
|
+
|
|
106
|
+
| Name | Type | Description |
|
|
107
|
+
| ------------- | ----------- | ------------------------------------------------------------ |
|
|
108
|
+
| `id` | `string` | Unique conversation identifier |
|
|
109
|
+
| `members` | `User[]` | Participants — must include `currentUser` and one other user |
|
|
110
|
+
| `messageList` | `Message[]` | Ordered list of messages |
|
|
111
|
+
|
|
112
|
+
##### Message
|
|
113
|
+
|
|
114
|
+
| Name | Type | Description |
|
|
115
|
+
| -------------- | ------------------- | ----------------------------------------------------------------- |
|
|
116
|
+
| `sender` | `User` | The user who sent the message |
|
|
117
|
+
| `messageBody` | `string \| FileObj` | Text content or a file object for image/video messages |
|
|
118
|
+
| `reactElement` | `ReactElement` | Optional — replaces `messageBody` rendering with a custom element |
|
|
119
|
+
| `createdAt` | `Date` | Timestamp |
|
|
@@ -3,11 +3,11 @@ import { storiesOf } from "@storybook/react";
|
|
|
3
3
|
import moment from "moment";
|
|
4
4
|
import React, { useEffect, useState } from "react";
|
|
5
5
|
import { ChatDashboard } from ".";
|
|
6
|
-
import
|
|
6
|
+
import { StorySection } from "../../storyUtils";
|
|
7
7
|
import { IconButton } from "../../action/iconButton";
|
|
8
8
|
import { Loading } from "../../display/loading";
|
|
9
9
|
import { Conversation } from "../chatBase";
|
|
10
|
-
import { conversationList as
|
|
10
|
+
import { conversationList as seedConvoList, currentUser } from "../chatUtils";
|
|
11
11
|
import README from "./README.md";
|
|
12
12
|
|
|
13
13
|
const stories = storiesOf("Chat", module);
|
|
@@ -18,137 +18,114 @@ stories.addParameters({
|
|
|
18
18
|
},
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
const sortByLatest = (list: Conversation[]) =>
|
|
22
|
+
[...list].sort((a, b) =>
|
|
23
|
+
moment(b.messageList[b.messageList.length - 1].createdAt).diff(
|
|
24
|
+
moment(a.messageList[a.messageList.length - 1].createdAt)
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
|
|
21
28
|
stories.add("Chat Dashboard", () => {
|
|
22
29
|
const [message, setMessage] = useState("");
|
|
23
30
|
const [search, setSearch] = useState("");
|
|
24
31
|
const [isLoading, setIsLoading] = useState(false);
|
|
25
32
|
const [mobileSelectedChat, setMobileSelectedChat] = useState(false);
|
|
26
33
|
const [filteredConversation, setFilteredConversation] =
|
|
27
|
-
useState<Conversation | null>();
|
|
34
|
+
useState<Conversation | null>(null);
|
|
35
|
+
const [conversationList, setConversationList] = useState<Conversation[]>([]);
|
|
28
36
|
const [conversation, setConversation] = useState<Conversation>({
|
|
29
37
|
id: "",
|
|
30
38
|
members: [],
|
|
31
39
|
messageList: [],
|
|
32
40
|
});
|
|
33
|
-
const [conversationList, setConversationList] = useState<Conversation[]>([]);
|
|
34
41
|
|
|
35
42
|
useEffect(() => {
|
|
36
|
-
|
|
43
|
+
const sorted = sortByLatest(seedConvoList);
|
|
44
|
+
setConversationList(sorted);
|
|
45
|
+
setConversation(sorted[0]);
|
|
37
46
|
}, []);
|
|
38
47
|
|
|
39
|
-
const
|
|
40
|
-
if (searchMessage.length === 0) {
|
|
41
|
-
setSearch("");
|
|
42
|
-
setFilteredConversation(null);
|
|
43
|
-
} else {
|
|
44
|
-
const dupMessageList = conversation.messageList.filter((eachMessage) => {
|
|
45
|
-
if (typeof eachMessage.messageBody === "string") {
|
|
46
|
-
return eachMessage.messageBody
|
|
47
|
-
.toLowerCase()
|
|
48
|
-
.includes(searchMessage.toLowerCase());
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
});
|
|
52
|
-
const dupConversation = {
|
|
53
|
-
id: conversation.id,
|
|
54
|
-
members: [...conversation.members],
|
|
55
|
-
messageList: [...dupMessageList],
|
|
56
|
-
};
|
|
57
|
-
setFilteredConversation(dupConversation);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const handleOnSendMessage = (conversation: Conversation) => {
|
|
62
|
-
setConversation({
|
|
63
|
-
id: conversation.id,
|
|
64
|
-
members: [...conversation.members],
|
|
65
|
-
messageList: [...conversation.messageList],
|
|
66
|
-
});
|
|
67
|
-
setMessage("");
|
|
68
|
-
const dupConvoList = conversationList.map((eachConvo, i) => {
|
|
69
|
-
if (eachConvo.id === conversation.id) {
|
|
70
|
-
return conversation;
|
|
71
|
-
}
|
|
72
|
-
return eachConvo;
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
handleSortConvoList(dupConvoList);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handleSortConvoList = (convoList: Conversation[]) => {
|
|
79
|
-
const sortedConvoList = convoList.sort((a, b) => {
|
|
80
|
-
return moment(b.messageList[b.messageList.length - 1].createdAt).diff(
|
|
81
|
-
moment(a.messageList[a.messageList.length - 1].createdAt)
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
setConversationList(sortedConvoList);
|
|
85
|
-
setConversation(sortedConvoList[0]);
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const handleMessageList = (id: string) => {
|
|
48
|
+
const handleSelectConversation = (id: string) => {
|
|
89
49
|
setIsLoading(true);
|
|
90
50
|
setMobileSelectedChat(true);
|
|
91
51
|
setTimeout(() => {
|
|
92
52
|
setIsLoading(false);
|
|
93
|
-
conversationList.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
});
|
|
99
|
-
}, 1000);
|
|
53
|
+
const found = conversationList.find((c) => c.id === id);
|
|
54
|
+
if (found) setConversation(found);
|
|
55
|
+
}, 800);
|
|
100
56
|
};
|
|
101
57
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
)
|
|
58
|
+
const handleSendMessage = (updated: Conversation) => {
|
|
59
|
+
setConversation(updated);
|
|
60
|
+
setMessage("");
|
|
61
|
+
const updatedList = conversationList.map((c) =>
|
|
62
|
+
c.id === updated.id ? updated : c
|
|
63
|
+
);
|
|
64
|
+
setConversationList(sortByLatest(updatedList));
|
|
108
65
|
};
|
|
109
66
|
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
<p className="ml-2 sm:ml-12">{userName}</p>
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
67
|
+
const handleSearch = (searchMessage: string) => {
|
|
68
|
+
if (!searchMessage) {
|
|
69
|
+
setSearch("");
|
|
70
|
+
setFilteredConversation(null);
|
|
71
|
+
} else {
|
|
72
|
+
setFilteredConversation({
|
|
73
|
+
...conversation,
|
|
74
|
+
messageList: conversation.messageList.filter(
|
|
75
|
+
(m) =>
|
|
76
|
+
typeof m.messageBody === "string" &&
|
|
77
|
+
m.messageBody.toLowerCase().includes(searchMessage.toLowerCase())
|
|
78
|
+
),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
127
81
|
};
|
|
128
82
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
currentUser={currentUser}
|
|
139
|
-
selectedConversation={filteredConversation ?? conversation}
|
|
140
|
-
conversationList={conversationList}
|
|
141
|
-
mobileSelectedChat={mobileSelectedChat}
|
|
142
|
-
sizeClassName="w-full h-[50vh] sm:h-[50vh]"
|
|
143
|
-
chatBaseSizeClassName="w-full h-[50vh] sm:h-[50vh]"
|
|
144
|
-
headerElement={renderHeaderElement()}
|
|
145
|
-
bodyElement={renderBodyElement()}
|
|
146
|
-
onChangeMessage={(e) => setMessage(e.target.value)}
|
|
147
|
-
handleSelectedConversation={handleMessageList}
|
|
148
|
-
onSendMessage={handleOnSendMessage}
|
|
149
|
-
onChangeSearch={(e) => setSearch(e.target.value)}
|
|
150
|
-
onSearch={handleOnSearch}
|
|
83
|
+
const otherUser = conversation.members.find((m) => m.id !== currentUser.id);
|
|
84
|
+
|
|
85
|
+
const headerElement = (
|
|
86
|
+
<div className="w-full flex items-center h-12 text-white bg-primary-500">
|
|
87
|
+
<IconButton
|
|
88
|
+
icon={<ChevronLeftIcon className="w-6 h-6" />}
|
|
89
|
+
background="bg-transparent"
|
|
90
|
+
onClick={() => setMobileSelectedChat(false)}
|
|
91
|
+
className={`${!mobileSelectedChat ? "hidden sm:flex" : ""} sm:hidden`}
|
|
151
92
|
/>
|
|
93
|
+
<p className="ml-2 sm:ml-4 font-medium">{otherUser?.name ?? "Chat"}</p>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const bodyElement = isLoading ? (
|
|
98
|
+
<div className="w-full h-full flex items-center justify-center">
|
|
99
|
+
<Loading />
|
|
152
100
|
</div>
|
|
101
|
+
) : undefined;
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<>
|
|
105
|
+
<StorySection
|
|
106
|
+
title="Chat Dashboard"
|
|
107
|
+
description="A two-panel layout: a conversation list on the left and the active chat on the right. handleSelectedConversation is called with the conversation id when a list item is clicked. On mobile, the list and chat panels toggle via mobileSelectedChat — the header's back button sets it to false."
|
|
108
|
+
>
|
|
109
|
+
<div className="w-full">
|
|
110
|
+
<ChatDashboard
|
|
111
|
+
value={message}
|
|
112
|
+
searchMessage={search}
|
|
113
|
+
currentUser={currentUser}
|
|
114
|
+
selectedConversation={filteredConversation ?? conversation}
|
|
115
|
+
conversationList={conversationList}
|
|
116
|
+
mobileSelectedChat={mobileSelectedChat}
|
|
117
|
+
sizeClassName="w-64 h-[60vh]"
|
|
118
|
+
chatBaseSizeClassName="w-full h-[60vh]"
|
|
119
|
+
headerElement={headerElement}
|
|
120
|
+
bodyElement={bodyElement}
|
|
121
|
+
onChangeMessage={(e) => setMessage(e.target.value)}
|
|
122
|
+
onSendMessage={handleSendMessage}
|
|
123
|
+
onChangeSearch={(e) => setSearch(e.target.value)}
|
|
124
|
+
onSearch={handleSearch}
|
|
125
|
+
handleSelectedConversation={handleSelectConversation}
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</StorySection>
|
|
129
|
+
</>
|
|
153
130
|
);
|
|
154
131
|
});
|
|
@@ -4,10 +4,144 @@
|
|
|
4
4
|
|
|
5
5
|
<hr>
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A dashboard chart grid that fetches and renders analytics data from a remote API. Supports card, bar, line, pie, and radar chart types in a responsive grid layout. Each chart handles its own data fetching, date range filtering, and attribute filtering.
|
|
8
8
|
|
|
9
9
|
##### Import
|
|
10
10
|
|
|
11
11
|
```js
|
|
12
12
|
import { DataGraph } from "blixify-ui-web/components/data/dataGraph";
|
|
13
13
|
```
|
|
14
|
+
|
|
15
|
+
##### Usage
|
|
16
|
+
|
|
17
|
+
Basic dashboard with card and bar charts:
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
<DataGraph
|
|
21
|
+
charts={[
|
|
22
|
+
{
|
|
23
|
+
gridSize: 1,
|
|
24
|
+
type: "card",
|
|
25
|
+
title: "Total Products",
|
|
26
|
+
spaceId: "products",
|
|
27
|
+
query: [],
|
|
28
|
+
queryBaseType: "count",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
gridSize: 1,
|
|
32
|
+
type: "card",
|
|
33
|
+
title: "Total Revenue",
|
|
34
|
+
spaceId: "orders",
|
|
35
|
+
query: [],
|
|
36
|
+
queryBaseType: "sum",
|
|
37
|
+
queryAttributeId: ["price"],
|
|
38
|
+
queryBaseDateId: "createdAt",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
gridSize: 2,
|
|
42
|
+
type: "bar",
|
|
43
|
+
title: "Sales by Product",
|
|
44
|
+
spaceId: "orders",
|
|
45
|
+
query: [
|
|
46
|
+
{ type: "=", queryId: "code", value: "P001" },
|
|
47
|
+
{ type: "=", queryId: "code", value: "P002" },
|
|
48
|
+
],
|
|
49
|
+
queryBaseType: "sum",
|
|
50
|
+
queryAttributeId: ["price"],
|
|
51
|
+
},
|
|
52
|
+
]}
|
|
53
|
+
workspace={{
|
|
54
|
+
apiEndpoint: "http://localhost:3000/api",
|
|
55
|
+
apiEndpointType: "mongoDB",
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Line chart with date range:
|
|
61
|
+
|
|
62
|
+
```jsx
|
|
63
|
+
{
|
|
64
|
+
gridSize: 2,
|
|
65
|
+
type: "line",
|
|
66
|
+
title: "Orders Over Time",
|
|
67
|
+
spaceId: "orders",
|
|
68
|
+
query: [],
|
|
69
|
+
queryBaseType: "count",
|
|
70
|
+
queryBaseDateId: "createdAt",
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Radar chart comparing multiple attributes across query groups:
|
|
75
|
+
|
|
76
|
+
```jsx
|
|
77
|
+
{
|
|
78
|
+
gridSize: 2,
|
|
79
|
+
type: "radar",
|
|
80
|
+
title: "Performance",
|
|
81
|
+
spaceId: "reports",
|
|
82
|
+
query: [
|
|
83
|
+
{ type: "=", queryId: "status", value: "Completed" },
|
|
84
|
+
{ type: "=", queryId: "status", value: "Pending" },
|
|
85
|
+
],
|
|
86
|
+
queryBaseType: "sum",
|
|
87
|
+
queryAttributeId: ["revenue", "units", "returns"],
|
|
88
|
+
queryBaseDateId: "createdAt",
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Custom layout via renderSettings:
|
|
93
|
+
|
|
94
|
+
```jsx
|
|
95
|
+
<DataGraph
|
|
96
|
+
charts={charts}
|
|
97
|
+
workspace={workspace}
|
|
98
|
+
renderSettings={(chartsElement, data) => (
|
|
99
|
+
<div className="my-custom-wrapper">{chartsElement}</div>
|
|
100
|
+
)}
|
|
101
|
+
/>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
##### Required props
|
|
105
|
+
|
|
106
|
+
| Name | Type | Description |
|
|
107
|
+
| ----------- | ---------------------- | -------------------------------------------------------- |
|
|
108
|
+
| `charts` | `Charts[]` | List of chart definitions — see Charts below |
|
|
109
|
+
| `workspace` | `WorkspaceDevSettings` | API connection settings — see WorkspaceDevSettings below |
|
|
110
|
+
|
|
111
|
+
##### Optional props
|
|
112
|
+
|
|
113
|
+
| Name | Type | Default | Description |
|
|
114
|
+
| ---------------- | ------------------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------- |
|
|
115
|
+
| `gridSize` | `2 \| 3` | — | Override the grid column layout. Default is a responsive `1 → 2 → 4` column grid. `2` forces 2 cols, `3` forces 3 cols. |
|
|
116
|
+
| `renderSettings` | `(charts: ReactElement, data: any) => any` | — | Custom render function. Receives the rendered charts element and the raw analytics data object. Use to wrap charts in a custom layout. |
|
|
117
|
+
|
|
118
|
+
##### Charts
|
|
119
|
+
|
|
120
|
+
| Name | Type | Description |
|
|
121
|
+
| ------------------ | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
122
|
+
| `type` | `"card" \| "bar" \| "line" \| "pie" \| "radar"` | Chart type to render |
|
|
123
|
+
| `title` | `string` | Chart title — also used as the key for internal state |
|
|
124
|
+
| `spaceId` | `string` | The collection/space ID to query data from |
|
|
125
|
+
| `gridSize` | `number` | Number of grid columns this chart spans |
|
|
126
|
+
| `query` | `Query[]` | Data series definitions. Each query entry produces one data point or series segment. Pass `[]` for a single aggregate. |
|
|
127
|
+
| `queryBaseType` | `"count" \| "sum" \| "avg" \| "value"` | Aggregate function. Defaults to `"count"`. |
|
|
128
|
+
| `queryAttributeId` | `string[]` | Required when `queryBaseType` is `"sum"`, `"avg"`, or `"value"`. The field(s) to aggregate. For `radar`, each entry becomes a radar axis. |
|
|
129
|
+
| `queryBaseDateId` | `string` | Field used for date range filtering. When set, a date range picker is shown on the chart. |
|
|
130
|
+
| `queryReferenceId` | `string` | Field used for reference-based filtering when a reference filter is active. |
|
|
131
|
+
| `baseQuery` | `Query[]` | Static filter queries always applied alongside any dynamic filters. |
|
|
132
|
+
| `description` | `string` | Subtitle shown on `card` type charts |
|
|
133
|
+
| `cardIcon` | `string` | Heroicon name (solid) for the card icon, e.g. `"UserIcon"`. Defaults to `FolderIcon`. |
|
|
134
|
+
| `cardIconBgColor` | `string` | Tailwind bg class for the card icon background |
|
|
135
|
+
| `colors` | `string[]` | Custom color array for chart segments/bars/lines |
|
|
136
|
+
| `orientation` | `"col" \| "row"` | Bar chart orientation. Defaults to `"row"`. |
|
|
137
|
+
| `stackedVertical` | `boolean` | Enables stacked bars on bar charts |
|
|
138
|
+
|
|
139
|
+
##### WorkspaceDevSettings
|
|
140
|
+
|
|
141
|
+
| Name | Type | Description |
|
|
142
|
+
| ------------------ | -------------------------------------------------------------------------- | ------------------------------- |
|
|
143
|
+
| `apiEndpoint` | `string` | Base URL for the data API |
|
|
144
|
+
| `apiEndpointType` | `"mongoDB" \| "firebase" \| "mongoServer" \| "fbServer" \| "mondayServer"` | Server/DB type driving the API |
|
|
145
|
+
| `apiEndpointToken` | `string` | Auth token for the API endpoint |
|
|
146
|
+
| `apiUserToken` | `string` | Per-user auth token |
|
|
147
|
+
| `assetEndpoint` | `string` | Base URL for asset/file storage |
|