✨ (tableComponent): add editable prop to TableComponent for column editing

✨ (API): add updateMessageApi function to update messages via API
✨ (chatView): add select dropdown for clearing builds and sessions
♻️ (use-messages-table): refactor to use messages store for setting messages
♻️ (use-remove-messages): remove setRows and use messages store for deletion
✨ (use-updateMessage): add hook for updating messages with API integration
📝 (headerMessages): update header message text in messages page

✨ (messagesPage): add cell edit request handling for message updates
♻️ (messagesPage): refactor state management and hooks usage
✨ (types): add new types for chat and message handling
This commit is contained in:
cristhianzl 2024-06-03 10:31:46 -03:00
commit 93568b4c0d
12 changed files with 161 additions and 39 deletions

View file

@ -321,4 +321,3 @@ class FlowDataRequest(BaseModel):
class ConfigResponse(BaseModel): class ConfigResponse(BaseModel):
frontend_timeout: int frontend_timeout: int

View file

@ -17,6 +17,7 @@ interface TableComponentProps extends AgGridReactProps {
rowData: NonNullable<AgGridReactProps["rowData"]>; rowData: NonNullable<AgGridReactProps["rowData"]>;
alertTitle?: string; alertTitle?: string;
alertDescription?: string; alertDescription?: string;
editable?: boolean | string[];
} }
const TableComponent = forwardRef< const TableComponent = forwardRef<
@ -48,20 +49,29 @@ const TableComponent = forwardRef<
} }
const colDef = props.columnDefs.map((col, index) => { const colDef = props.columnDefs.map((col, index) => {
let newCol = {
...col,
headerName: toTitleCase(col.headerName),
};
if (props.onSelectionChanged && index === 0) { if (props.onSelectionChanged && index === 0) {
return { newCol = {
...col, ...newCol,
headerName: toTitleCase(col.headerName),
checkboxSelection: true, checkboxSelection: true,
headerCheckboxSelection: true, headerCheckboxSelection: true,
headerCheckboxSelectionFilteredOnly: true, headerCheckboxSelectionFilteredOnly: true,
}; };
} else { }
return { if (
...col, (typeof props.editable === "boolean" && props.editable) ||
headerName: toTitleCase(col.headerName), (Array.isArray(props.editable) &&
props.editable.includes(newCol.headerName ?? ""))
) {
newCol = {
...newCol,
editable: true,
}; };
} }
return newCol;
}); });
return ( return (

View file

@ -28,6 +28,7 @@ import {
UploadFileTypeAPI, UploadFileTypeAPI,
errorsTypeAPI, errorsTypeAPI,
} from "./../../types/api/index"; } from "./../../types/api/index";
import { Message } from "../../types/messages";
/** /**
* Fetches all objects from the API endpoint. * Fetches all objects from the API endpoint.
@ -1049,3 +1050,7 @@ export async function deleteMessagesFn(ids: number[]) {
throw error; throw error;
} }
} }
export async function updateMessageApi(data: Message) {
return await api.post(`${BASE_URL_API}monitor/messages/${data.index}`, data);
}

View file

@ -18,6 +18,12 @@ import { chatViewProps } from "../../../../types/components";
import { classNames } from "../../../../utils/utils"; import { classNames } from "../../../../utils/utils";
import ChatInput from "./chatInput"; import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage"; import ChatMessage from "./chatMessage";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "../../../../components/ui/select";
export default function ChatView({ export default function ChatView({
sendMessage, sendMessage,
@ -118,10 +124,21 @@ export default function ChatView({
if (lockChat) setLockChat(false); if (lockChat) setLockChat(false);
} }
function handleSelectChange(event: string): void {
switch (event) {
case "builds":
clearChat();
break;
case "buildsNSession":
console.log("delete build and session");
break;
}
}
function updateChat( function updateChat(
chat: ChatMessageType, chat: ChatMessageType,
message: string, message: string,
stream_url?: string stream_url?: string,
) { ) {
// if (message === "") return; // if (message === "") return;
chat.message = message; chat.message = message;
@ -149,18 +166,44 @@ export default function ChatView({
<div className="eraser-column-arrangement"> <div className="eraser-column-arrangement">
<div className="eraser-size"> <div className="eraser-size">
<div className="eraser-position"> <div className="eraser-position">
<button disabled={lockChat} onClick={() => clearChat()}> <Select
<IconComponent onValueChange={handleSelectChange}
name="Eraser" value=""
className={classNames( disabled={lockChat}
"h-5 w-5", >
lockChat <SelectTrigger className="">
? "animate-pulse text-primary" <button className="flex gap-1">
: "text-primary hover:text-gray-600" <IconComponent
)} name="Eraser"
aria-hidden="true" className={classNames(
/> "h-5 w-5 transition-all duration-100",
</button> lockChat ? "animate-pulse text-primary" : "text-primary",
)}
aria-hidden="true"
/>
</button>
</SelectTrigger>
<SelectContent className="right-[9.5em]">
<SelectItem value="builds" className="cursor-pointer">
<div className="flex">
<IconComponent
name={"Trash2"}
className={`relative top-0.5 mr-2 h-4 w-4`}
/>
<span className="">Clear Builds</span>
</div>
</SelectItem>
<SelectItem value="buildsNSession" className="cursor-pointer">
<div className="flex">
<IconComponent
name={"Trash2"}
className={`relative top-0.5 mr-2 h-4 w-4`}
/>
<span className="">Clear Builds & Session</span>
</div>
</SelectItem>
</SelectContent>
</Select>
</div> </div>
<div ref={messagesRef} className="chat-message-div"> <div ref={messagesRef} className="chat-message-div">
{chatHistory?.length > 0 ? ( {chatHistory?.length > 0 ? (

View file

@ -21,9 +21,7 @@ const HeaderMessagesComponent = ({
className="ml-2 h-5 w-5 text-primary" className="ml-2 h-5 w-5 text-primary"
/> />
</h2> </h2>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">@Rodrigo</p>
Manage your messages as you like.
</p>
</div> </div>
<div className="flex flex-shrink-0 items-center gap-2"> <div className="flex flex-shrink-0 items-center gap-2">
<Button <Button

View file

@ -1,14 +1,15 @@
import { useEffect } from "react"; import { useEffect } from "react";
import { getMessagesTable } from "../../../../../controllers/API"; import { getMessagesTable } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore";
const useMessagesTable = (setColumns, setRows, setMessages) => { const useMessagesTable = (setColumns) => {
const setMessages = useMessagesStore((state) => state.setMessages);
useEffect(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
try { try {
const data = await getMessagesTable("union", undefined, ["index"]); const data = await getMessagesTable("union", undefined, ["index"]);
const { columns, rows } = data; const { columns, rows } = data;
setColumns(columns.map((col) => ({ ...col, editable: true }))); setColumns(columns);
setRows(rows);
setMessages(rows); setMessages(rows);
} catch (error) { } catch (error) {
console.error("Error fetching messages:", error); console.error("Error fetching messages:", error);

View file

@ -2,19 +2,17 @@ import { deleteMessagesFn } from "../../../../../controllers/API";
import { useMessagesStore } from "../../../../../stores/messagesStore"; import { useMessagesStore } from "../../../../../stores/messagesStore";
const useRemoveMessages = ( const useRemoveMessages = (
setRows,
setSelectedRows, setSelectedRows,
setSuccessData, setSuccessData,
setErrorData, setErrorData,
selectedRows, selectedRows
) => { ) => {
const deleteMessages = useMessagesStore((state) => state.removeMessages); const deleteMessages = useMessagesStore((state) => state.removeMessages);
const handleRemoveMessages = async () => { const handleRemoveMessages = async () => {
try { try {
await deleteMessagesFn(selectedRows); await deleteMessagesFn(selectedRows);
const res = await deleteMessages(selectedRows); deleteMessages(selectedRows);
setRows(res);
setSelectedRows([]); setSelectedRows([]);
setSuccessData({ setSuccessData({
title: "Messages deleted successfully.", title: "Messages deleted successfully.",

View file

@ -0,0 +1,29 @@
import { useMessagesStore } from "../../../../../stores/messagesStore";
import { Message } from "../../../../../types/messages";
import { updateMessageApi } from "../../../../../controllers/API";
const useUpdateMessage = (setSuccessData, setErrorData) => {
const updateMessage = useMessagesStore((state) => state.updateMessage);
const handleUpdate = async (data: Message) => {
try {
await updateMessageApi(data);
updateMessage(data);
// Set success message
setSuccessData({
title: "Messages updated successfully.",
});
} catch (error) {
// Set error message
setErrorData({
title: "Error updating messages.",
});
}
};
return { handleUpdate };
};
export default useUpdateMessage;

View file

@ -1,4 +1,9 @@
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community"; import {
CellEditRequestEvent,
ColDef,
ColGroupDef,
SelectionChangedEvent,
} from "ag-grid-community";
import { useState } from "react"; import { useState } from "react";
import TableComponent from "../../../../components/tableComponent"; import TableComponent from "../../../../components/tableComponent";
import { Card, CardContent } from "../../../../components/ui/card"; import { Card, CardContent } from "../../../../components/ui/card";
@ -7,12 +12,11 @@ import { useMessagesStore } from "../../../../stores/messagesStore";
import HeaderMessagesComponent from "./components/headerMessages"; import HeaderMessagesComponent from "./components/headerMessages";
import useMessagesTable from "./hooks/use-messages-table"; import useMessagesTable from "./hooks/use-messages-table";
import useRemoveMessages from "./hooks/use-remove-messages"; import useRemoveMessages from "./hooks/use-remove-messages";
import useUpdateMessage from "./hooks/use-updateMessage";
export default function MessagesPage() { export default function MessagesPage() {
const setMessages = useMessagesStore((state) => state.setMessages);
const [columns, setColumns] = useState<Array<ColDef | ColGroupDef>>([]); const [columns, setColumns] = useState<Array<ColDef | ColGroupDef>>([]);
const [rows, setRows] = useState<any>([]); const messages = useMessagesStore((state) => state.messages);
const setErrorData = useAlertStore((state) => state.setErrorData); const setErrorData = useAlertStore((state) => state.setErrorData);
const setSuccessData = useAlertStore((state) => state.setSuccessData); const setSuccessData = useAlertStore((state) => state.setSuccessData);
@ -20,14 +24,26 @@ export default function MessagesPage() {
const [selectedRows, setSelectedRows] = useState<number[]>([]); const [selectedRows, setSelectedRows] = useState<number[]>([]);
const { handleRemoveMessages } = useRemoveMessages( const { handleRemoveMessages } = useRemoveMessages(
setRows,
setSelectedRows, setSelectedRows,
setSuccessData, setSuccessData,
setErrorData, setErrorData,
selectedRows, selectedRows,
); );
useMessagesTable(setColumns, setRows, setMessages); const { handleUpdate } = useUpdateMessage(setSuccessData, setErrorData);
useMessagesTable(setColumns);
function handleUpdateMessage(event: CellEditRequestEvent<any, string>) {
const newValue = event.newValue;
const field = event.column.getColId();
const row = event.data;
const data = {
...row,
[field]: newValue,
};
handleUpdate(data);
}
return ( return (
<div className="flex h-full w-full flex-col justify-between gap-6"> <div className="flex h-full w-full flex-col justify-between gap-6">
@ -40,6 +56,11 @@ export default function MessagesPage() {
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4"> <Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
<CardContent className="h-full"> <CardContent className="h-full">
<TableComponent <TableComponent
readOnlyEdit
onCellEditRequest={(event) => {
handleUpdateMessage(event);
}}
editable={["Sender", "Sender Name", "Message"]}
overlayNoRowsTemplate="No data available" overlayNoRowsTemplate="No data available"
onSelectionChanged={(event: SelectionChangedEvent) => { onSelectionChanged={(event: SelectionChangedEvent) => {
setSelectedRows( setSelectedRows(
@ -50,7 +71,7 @@ export default function MessagesPage() {
suppressRowClickSelection={true} suppressRowClickSelection={true}
pagination={true} pagination={true}
columnDefs={columns} columnDefs={columns}
rowData={rows} rowData={messages}
/> />
</CardContent> </CardContent>
</Card> </Card>

View file

@ -752,3 +752,17 @@ export type toolbarSelectItemProps = {
dataTestId: string; dataTestId: string;
ping?: boolean; ping?: boolean;
}; };
export type clearChatPropsType = {
lockChat: boolean;
setLockChat: (lock: boolean) => void;
setChatHistory: (chatHistory: ChatMessageType) => void;
method: string;
};
export type handleSelectPropsType = {
event: string;
lockChat: boolean;
setLockChat: (lock: boolean) => void;
setChatHistory: (chatHistory: ChatMessageType) => void;
};

View file

@ -9,3 +9,5 @@ type Message = {
timestamp: string; timestamp: string;
id: string; id: string;
}; };
export type { Message };

View file

@ -1,3 +1,5 @@
import { Message } from "../../messages";
export type MessagesStoreType = { export type MessagesStoreType = {
messages: Message[]; messages: Message[];
setMessages: (messages: Message[]) => void; setMessages: (messages: Message[]) => void;
@ -5,5 +7,5 @@ export type MessagesStoreType = {
removeMessage: (message: Message) => void; removeMessage: (message: Message) => void;
updateMessage: (message: Message) => void; updateMessage: (message: Message) => void;
clearMessages: () => void; clearMessages: () => void;
removeMessages: (ids: number[]) => Promise<Message[]>; removeMessages: (ids: number[]) => void;
}; };