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;