diff --git a/Ai_work.db b/Ai_work.db index de5282b..6ae4a2f 100644 Binary files a/Ai_work.db and b/Ai_work.db differ diff --git a/backend/run_app.py b/backend/run_app.py index 80a7211..85599bf 100644 --- a/backend/run_app.py +++ b/backend/run_app.py @@ -414,7 +414,7 @@ def deleteReservation(): for member_id in member_ids: if int(member_id[0]) != int(user_id): result2 = Team.insertMeetingRoomReservation(room_id, member_id[0], user_id, start_time, end_time, date, subject, team_id, 1) - print(result2) + # print(result2) activity_name = "会议 - " + room_name + " - " + subject result3 = DBUtil.deleteActivityByActivityName(activity_name, date) print(result3) @@ -422,7 +422,32 @@ def deleteReservation(): if result == False: return jsonify({'code': 1, 'message': '删除会议室预约失败', 'status': 500 }) return jsonify({'code': 0, 'message': '删除会议室预约成功', 'status': 200 }), 200 - + +# getReservationInfo +@app.route(apiPrefix + 'getReservationInfo', methods=['POST']) +def getReservationInfo(): + data = request.get_json() + user_id = data.get('userID') + reservations = Team.getMeetingRoomReservation(user_id) + if reservations is None: + return jsonify({'code': 1, 'message': '获取会议室预约列表失败', 'status': 500 }) + # room_id, room_name, user_id, reserve_user_id, reserve_user_name, start_time, end_time, date, subject, team_id, team_name, type + keys = ['id', 'room_id', 'room_name', 'user_id', 'reserve_user_id', 'reserve_user_name', 'start_time', 'end_time', 'date', 'subject', 'team_id', 'team_name', 'type'] + reservations_list = [dict(zip(keys, reservation)) for reservation in reservations] + return jsonify({'code': 0, 'message': '获取会议室预约列表成功', 'status': 200, 'data': reservations_list}), 200 + +# acceptReservation +@app.route(apiPrefix + 'acceptReservation', methods=['POST']) +def acceptReservation(): + data = request.get_json() + user_id = data.get('userID') + reservation_id = data.get('reservation_id') + type = data.get('type') + result = Team.deleteMeetingRoomReservation(reservation_id, user_id, type) + if result == False: + return jsonify({'code': 1, 'message': '已读失败', 'status': 500 }) + return jsonify({'code': 0, 'message': '已读成功', 'status': 200 }), 200 + @app.route(apiPrefix + 'getUserReservations', methods=['POST']) def getUserReservations(): diff --git a/backend/sqlite_team.py b/backend/sqlite_team.py index 35f18d6..c6819f4 100644 --- a/backend/sqlite_team.py +++ b/backend/sqlite_team.py @@ -75,6 +75,30 @@ def insertMeetingRoomReservation(room_id, user_id, reserve_user_id, start_time, finally: lock_threading.release() +def getMeetingRoomReservation(user_id): + try: + lock_threading.acquire() + cursor.execute('''SELECT * FROM meeting_room_reservation WHERE user_id = ?''', (user_id,)) + return cursor.fetchall() + except sqlite3.Error as e: + print(e) + return False + finally: + lock_threading.release() + +def deleteMeetingRoomReservation(reservation_id, user_id, type): + try: + lock_threading.acquire() + print(reservation_id, user_id, type) + cursor.execute('''DELETE FROM meeting_room_reservation WHERE id = ? AND user_id = ? AND type = ?''', (reservation_id, user_id, type)) + conn.commit() + return True + except sqlite3.Error as e: + print(e) + return False + finally: + lock_threading.release() + def insertTeam(team_name, captain_id): try: diff --git a/frontend/src/pages/MainLayout.tsx b/frontend/src/pages/MainLayout.tsx index fcadfae..750cd3a 100644 --- a/frontend/src/pages/MainLayout.tsx +++ b/frontend/src/pages/MainLayout.tsx @@ -17,6 +17,19 @@ interface inviteinfo { user_id: number } +interface reservationinfo { + id: number, + room_id: number, + room_name: string, + reserve_user_id: number, + reserve_user_name: string, + team_id: number, + team_name: string, + date: string, + start_time: string, + end_time: string, + type: number, +} const MainLayout: React.FC = () => { @@ -28,6 +41,7 @@ const MainLayout: React.FC = () => { const [api, contextHolder] = notification.useNotification(); // let invitations: inviteinfo[] = []; const [invitations, setInvitations] = useState([]); + const [reservations, setReservations] = useState([]); useEffect(() => { const avatar = localStorage.getItem('avatarUrl'); @@ -38,6 +52,7 @@ const MainLayout: React.FC = () => { useEffect(() => { checkInvitations(); + checkMeetings(); }, []); const logout = () => { @@ -86,6 +101,93 @@ const MainLayout: React.FC = () => { } }; + // 类似的,展示会议室预定通知,按钮变为已读和忽略,根据type区别,0为通知有会议,1为会议预定取消 + const checkMeetings = async () => { + try { + const response = await HttpUtil.post(ApiUtil.API_GET_RESERVATION_INFO, { userID: userID }) as ApiResponse; + console.log('response:', response); + if (response.status === 200) { + console.log('reservations1:', reservations); + + const newreservations = response.data; + setReservations(newreservations); + + if (newreservations.length > 0) { + console.log('reservations:', newreservations); + newreservations.forEach((reservation: any) => { + const { id, room_id, room_name, reserve_user_id, reserve_user_name, team_id, team_name, date, start_time, end_time, type } = reservation; + const key = `reservation-${id}`; + if (type === 0) + api.open({ + message: '会议室预定通知', + description: `团队 ${team_name} 于 ${date} ${start_time} - ${end_time} 在 ${room_name} 预定了会议, 请注意查看`, + btn: ( +
+ + +
+ ), + key, + duration: 10, + showProgress: true, + onClose: () => api.destroy(key), + }); + else + api.open({ + message: '会议室预定通知', + description: `团队 ${team_name} 于 ${date} ${start_time} - ${end_time} 在 ${room_name} 取消了会议, 请注意查看`, + btn: ( +
+ + +
+ ), + key, + duration: 10, + showProgress: true, + onClose: () => api.destroy(key), + }); + }); + } + } else + console.log('获取会议室预定通知失败'); + } catch (error) { + console.error('获取会议室预定通知失败:', error); + } + } + + const acceptMeeting = async (reservation_id: number, type: number, notificationKey: string) => { + try { + const response = await HttpUtil.post(ApiUtil.API_ACCEPT_RESERVATION, { reservation_id: reservation_id, userID: userID, type: type}) as ApiResponse; + if (response.status === 200) { + api.success({ + message: '已读成功', + description: '已成功标记为已读', + }); + if (notificationKey !== '') + api.destroy(notificationKey); // Destroy the notification + setReservations(reservations.filter(reservation => reservation.id !== reservation_id)); + } else { + api.error({ + message: '已读失败', + description: response.data, + }); + } + } catch (error) { + console.error('已读失败:', error); + api.error({ + message: '已读失败', + description: '发生未知错误', + }); + } + }; + + + const acceptInvitation = async (teamId: number, captainId: number, notificationKey: string) => { try { const response = await HttpUtil.post(ApiUtil.API_ACCEPT_INVITATION, { userID: userID, teamID: teamId }) as ApiResponse; @@ -290,44 +392,65 @@ const MainLayout: React.FC = () => {
({ key: invitation.team_id, label: ( -
- 团队 {invitation.team_id} 的邀请 - - -
+
+ 团队 {invitation.team_id} 的邀请 + + +
), - })), - }} - trigger={['click']} - > - + })), + ...reservations.map((reservation: any) => { + const { id, room_id, room_name, reserve_user_id, reserve_user_name, team_id, team_name, date, start_time, end_time, type } = reservation; + const key = `reservation-${id}`; + const message = type === 0 ? '会议室预定通知' : '会议室取消通知'; + const description = type === 0 + ? `团队 ${team_name} 于 ${date} ${start_time} - ${end_time} 在 ${room_name} 预定了会议, 请注意查看` + : `团队 ${team_name} 于 ${date} ${start_time} - ${end_time} 在 ${room_name} 取消了会议, 请注意查看`; + return { + key, + label: ( +
+ {message} +
{description}
+ + +
+ ), + }; + }), + ], + }} + trigger={['click']} + > + diff --git a/frontend/src/pages/TodoList/TodoList.tsx b/frontend/src/pages/TodoList/TodoList.tsx index 40041f3..e434ab1 100644 --- a/frontend/src/pages/TodoList/TodoList.tsx +++ b/frontend/src/pages/TodoList/TodoList.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Layout, Button, Input, message, Modal, Collapse, Badge, ConfigProvider, Tag, Segmented } from 'antd'; +import { Layout, Button, Input, message, Modal, Collapse, Badge, ConfigProvider, Tag, Segmented, Drawer } from 'antd'; import { ProTable, ProColumns } from '@ant-design/pro-components'; import { EditOutlined, CloseOutlined, PlusOutlined, SearchOutlined, AppstoreAddOutlined } from '@ant-design/icons'; import InfoDialog from '../InfoDialog'; @@ -47,6 +47,8 @@ interface TodoListState { currentItem: TodoItem | null; // 添加 currentItem 属性 data: TodoActivity[]; activityStatus: string; + drawerVisible: boolean; + drawerActivity: TodoActivity | null; } class TodoList extends React.Component<{}, TodoListState> { @@ -59,12 +61,27 @@ class TodoList extends React.Component<{}, TodoListState> { currentActivity: null, currentItem: null, data: [], - activityStatus: 'ongoing' + activityStatus: 'ongoing', + drawerVisible: false, + drawerActivity: null, }; // 添加一个新的状态用于存储搜索结果 searchData: TodoActivity[] = []; + showDrawer = (activity: TodoActivity) => { + this.setState({ + drawerVisible: true, + drawerActivity: activity, + }); + }; + + closeDrawer = () => { + this.setState({ + drawerVisible: false, + }); + }; + columns: ProColumns[] = [ { title: '活动名称', @@ -85,7 +102,9 @@ class TodoList extends React.Component<{}, TodoListState> { } return ( - + this.showDrawer(record)}> + + ); } }, @@ -134,7 +153,7 @@ class TodoList extends React.Component<{}, TodoListState> { this.filterActivities(); return; } - + // 将搜索值转换为小写以进行不区分大小写的匹配 const searchValue = value.toLowerCase(); @@ -169,12 +188,26 @@ class TodoList extends React.Component<{}, TodoListState> { }) ); this.searchData = activityListWithItems; // 保存所有数据以供搜索使用 - this.setState({ - data: activityListWithItems, - showInfoDialog: false, - }, () => { - this.filterActivities(); - }); + // 如果有打开的 Drawer,找到对应的 Activity 并更新其 items + if (this.state.drawerVisible && this.state.drawerActivity) { + const updatedDrawerActivity = activityListWithItems.find( + (activity) => activity.ActivityID === this.state.drawerActivity?.ActivityID + ); + this.setState({ + data: activityListWithItems, + showInfoDialog: false, + drawerActivity: updatedDrawerActivity || null, + }, () => { + this.filterActivities(); + }); + } else { + this.setState({ + data: activityListWithItems, + showInfoDialog: false, + }, () => { + this.filterActivities(); + }); + } }) .catch((error) => { message.error(error.message); @@ -235,13 +268,17 @@ class TodoList extends React.Component<{}, TodoListState> { } //更新数据 this.getData(); + + + this.setState({ showAddItemDialog: false, - currentActivity: null, - currentItem: null, + // currentActivity: null, + // currentItem: null, activityStatus: this.state.activityStatus, - }); - + + }); + }; removeData = (id: number) => { @@ -422,7 +459,9 @@ class TodoList extends React.Component<{}, TodoListState> { render() { - const { activityStatus } = this.state; + // const { activityStatus } = this.state; + const { activityStatus, drawerVisible, drawerActivity } = this.state; + console.log('Data:', this.state.data); const columns = [...this.columns, this.admin_item] return ( @@ -454,15 +493,15 @@ class TodoList extends React.Component<{}, TodoListState> { style={{ width: 200, marginRight: 16 }} /> + options={[ + { label: '全部', value: 'all' }, + { label: '进行中', value: 'ongoing' }, + { label: '已完成', value: 'completed' }, + { label: '未开始', value: 'notStarted' } + ]} + value={activityStatus} + onChange={this.handleActivityStatusChange} + />
+ +

+ 开始时间:{" "} + {`${drawerActivity?.ActivityBeginDate} ${drawerActivity?.ActivityBeginTime}`} +

+

+ 结束时间:{" "} + {`${drawerActivity?.ActivityEndDate} ${drawerActivity?.ActivityEndTime}`} +

+ {drawerActivity && this.renderItems(drawerActivity.items)} +
); } diff --git a/frontend/src/utils/ApiUtil.ts b/frontend/src/utils/ApiUtil.ts index d219a6f..6ae47d2 100644 --- a/frontend/src/utils/ApiUtil.ts +++ b/frontend/src/utils/ApiUtil.ts @@ -57,4 +57,7 @@ export default class ApiUtil { static API_GET_CAPTAIN_TEAMS = ApiUtil.URL_ROOT + '/getCaptainTeams'; static API_DELETE_MEMBER = ApiUtil.URL_ROOT + '/deleteMember'; static API_UPDATE_TEAM_NAME = ApiUtil.URL_ROOT + '/updateTeamName'; + + static API_GET_RESERVATION_INFO = ApiUtil.URL_ROOT + '/getReservationInfo'; + static API_ACCEPT_RESERVATION = ApiUtil.URL_ROOT + '/acceptReservation'; } \ No newline at end of file