diff --git a/object/chat.go b/object/chat.go
index 5d13970c..70bcfcbd 100644
--- a/object/chat.go
+++ b/object/chat.go
@@ -30,6 +30,7 @@ type Chat struct {
Organization string `xorm:"varchar(100)" json:"organization"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Type string `xorm:"varchar(100)" json:"type"`
+ Category string `xorm:"varchar(100)" json:"category"`
User1 string `xorm:"varchar(100)" json:"user1"`
User2 string `xorm:"varchar(100)" json:"user2"`
Users []string `xorm:"varchar(100)" json:"users"`
diff --git a/web/src/App.js b/web/src/App.js
index 08df2555..a5f2581c 100644
--- a/web/src/App.js
+++ b/web/src/App.js
@@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
-import {BarsOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
+import {BarsOutlined, CommentOutlined, DownOutlined, InfoCircleFilled, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
@@ -44,8 +44,9 @@ import SyncerListPage from "./SyncerListPage";
import SyncerEditPage from "./SyncerEditPage";
import CertListPage from "./CertListPage";
import CertEditPage from "./CertEditPage";
-import ChatEditPage from "./ChatEditPage";
import ChatListPage from "./ChatListPage";
+import ChatEditPage from "./ChatEditPage";
+import ChatPage from "./ChatPage";
import MessageEditPage from "./MessageEditPage";
import MessageListPage from "./MessageListPage";
import ProductListPage from "./ProductListPage";
@@ -325,12 +326,17 @@ class App extends Component {
items.push(Setting.getItem(<> {i18next.t("account:My Account")}>,
"/account"
));
+ items.push(Setting.getItem(<> {i18next.t("account:Chats & Messages")}>,
+ "/chat"
+ ));
items.push(Setting.getItem(<> {i18next.t("account:Logout")}>,
"/logout"));
const onClick = (e) => {
if (e.key === "/account") {
this.props.history.push("/account");
+ } else if (e.key === "/chat") {
+ this.props.history.push("/chat");
} else if (e.key === "/logout") {
this.logout();
}
@@ -547,6 +553,7 @@ class App extends Component {
this.renderLoginIfNotLoggedIn()} />
this.renderLoginIfNotLoggedIn()} />
this.renderLoginIfNotLoggedIn()} />
+ this.renderLoginIfNotLoggedIn()} />
this.renderLoginIfNotLoggedIn()} />
this.renderLoginIfNotLoggedIn()} />
this.renderLoginIfNotLoggedIn()} />
diff --git a/web/src/ChatEditPage.js b/web/src/ChatEditPage.js
index 3d48a36e..59b88db5 100644
--- a/web/src/ChatEditPage.js
+++ b/web/src/ChatEditPage.js
@@ -140,6 +140,16 @@ class ChatEditPage extends React.Component {
/>
+
+
+ {Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
+
+
+ {
+ this.updateChatField("category", e.target.value);
+ }} />
+
+
{Setting.getLabel(i18next.t("chat:User1"), i18next.t("chat:User1 - Tooltip"))} :
diff --git a/web/src/ChatListPage.js b/web/src/ChatListPage.js
index e7682152..77279330 100644
--- a/web/src/ChatListPage.js
+++ b/web/src/ChatListPage.js
@@ -33,6 +33,7 @@ class ChatListPage extends BaseListPage {
organization: this.props.account.owner,
displayName: `New Chat - ${randomName}`,
type: "Single",
+ category: "Chat Category - 1",
user1: `${this.props.account.owner}/${this.props.account.name}`,
user2: "",
users: [`${this.props.account.owner}/${this.props.account.name}`],
@@ -151,6 +152,14 @@ class ChatListPage extends BaseListPage {
return i18next.t(`chat:${text}`);
},
},
+ {
+ title: i18next.t("provider:Category"),
+ dataIndex: "category",
+ key: "category",
+ // width: '100px',
+ sorter: true,
+ ...this.getColumnSearchProps("category"),
+ },
{
title: i18next.t("chat:User1"),
dataIndex: "user1",
diff --git a/web/src/ChatMenu.js b/web/src/ChatMenu.js
new file mode 100644
index 00000000..55bd067b
--- /dev/null
+++ b/web/src/ChatMenu.js
@@ -0,0 +1,78 @@
+// Copyright 2023 The Casdoor Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import React from "react";
+import {Menu} from "antd";
+import {MailOutlined} from "@ant-design/icons";
+
+class ChatMenu extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ openKeys: ["0"],
+ };
+ }
+
+ chatsToItems(chats) {
+ const categories = {};
+ chats.forEach((chat) => {
+ if (!categories[chat.category]) {
+ categories[chat.category] = [];
+ }
+ categories[chat.category].push(chat);
+ });
+
+ return Object.keys(categories).map((category, index) => {
+ return {
+ key: `${index}`,
+ icon: ,
+ label: category,
+ children: categories[category].map((chat) => ({
+ key: chat.id,
+ label: chat.displayName,
+ })),
+ };
+ });
+ }
+
+ // 处理菜单展开事件
+ onOpenChange = (keys) => {
+ const rootSubmenuKeys = this.props.chats.map((_, index) => `${index}`);
+ const latestOpenKey = keys.find((key) => this.state.openKeys.indexOf(key) === -1);
+
+ if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
+ this.setState({openKeys: keys});
+ } else {
+ this.setState({openKeys: latestOpenKey ? [latestOpenKey] : []});
+ }
+ };
+
+ render() {
+ const items = this.chatsToItems(this.props.chats);
+
+ return (
+
+ );
+ }
+}
+
+export default ChatMenu;
diff --git a/web/src/ChatPage.js b/web/src/ChatPage.js
new file mode 100644
index 00000000..5fdbf093
--- /dev/null
+++ b/web/src/ChatPage.js
@@ -0,0 +1,127 @@
+// Copyright 2023 The Casdoor Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Col, Row} from "antd";
+// import React from "react";
+import moment from "moment";
+import React from "react";
+import ChatMenu from "./ChatMenu";
+import * as Setting from "./Setting";
+import * as ChatBackend from "./backend/ChatBackend";
+import i18next from "i18next";
+import BaseListPage from "./BaseListPage";
+
+class ChatListPage extends BaseListPage {
+ newChat() {
+ const randomName = Setting.getRandomName();
+ return {
+ owner: "admin", // this.props.account.applicationName,
+ name: `chat_${randomName}`,
+ createdTime: moment().format(),
+ updatedTime: moment().format(),
+ organization: this.props.account.owner,
+ displayName: `New Chat - ${randomName}`,
+ type: "Single",
+ category: "Chat Category - 1",
+ user1: `${this.props.account.owner}/${this.props.account.name}`,
+ user2: "",
+ users: [`${this.props.account.owner}/${this.props.account.name}`],
+ messageCount: 0,
+ };
+ }
+
+ addChat() {
+ const newChat = this.newChat();
+ ChatBackend.addChat(newChat)
+ .then((res) => {
+ if (res.status === "ok") {
+ this.props.history.push({pathname: `/chats/${newChat.name}`, mode: "add"});
+ Setting.showMessage("success", i18next.t("general:Successfully added"));
+ } else {
+ Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
+ }
+ })
+ .catch(error => {
+ Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
+ });
+ }
+
+ deleteChat(i) {
+ ChatBackend.deleteChat(this.state.data[i])
+ .then((res) => {
+ if (res.status === "ok") {
+ Setting.showMessage("success", i18next.t("general:Successfully deleted"));
+ this.setState({
+ data: Setting.deleteRow(this.state.data, i),
+ pagination: {total: this.state.pagination.total - 1},
+ });
+ } else {
+ Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
+ }
+ })
+ .catch(error => {
+ Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
+ });
+ }
+
+ renderTable(chats) {
+ return (
+
+
+
+
+
+ 222
+
+
+ );
+ }
+
+ fetch = (params = {}) => {
+ let field = params.searchedColumn, value = params.searchText;
+ const sortField = params.sortField, sortOrder = params.sortOrder;
+ if (params.category !== undefined && params.category !== null) {
+ field = "category";
+ value = params.category;
+ } else if (params.type !== undefined && params.type !== null) {
+ field = "type";
+ value = params.type;
+ }
+ this.setState({loading: true});
+ ChatBackend.getChats("admin", params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
+ .then((res) => {
+ if (res.status === "ok") {
+ this.setState({
+ loading: false,
+ data: res.data,
+ pagination: {
+ ...params.pagination,
+ total: res.data2,
+ },
+ searchText: params.searchText,
+ searchedColumn: params.searchedColumn,
+ });
+ } else {
+ if (Setting.isResponseDenied(res)) {
+ this.setState({
+ loading: false,
+ isAuthorized: false,
+ });
+ }
+ }
+ });
+ };
+}
+
+export default ChatListPage;