Enhance ticket handling and UI interactions
Backend: Added lineId to ticket creation, improved ticket update logic, and switched ticket update route to POST. Added 'update_ticket' event to socket.io provider. Frontend: Integrated 'motion' for animated bottom toolbar, added expand/collapse functionality, improved ticket creation and update flows, and ensured terminal focus on CLI open. Adjusted delays in ButtonDPELP, improved ticket info copy, and enhanced input handling in ModalTerminal. Updated dependencies to include 'motion'.
This commit is contained in:
parent
d908cf204c
commit
c36b9f69df
|
|
@ -85,6 +85,7 @@ export default class TicketsController {
|
||||||
model: payload.model.trim(),
|
model: payload.model.trim(),
|
||||||
sn: payload.sn.trim(),
|
sn: payload.sn.trim(),
|
||||||
stationId: payload.station_id,
|
stationId: payload.station_id,
|
||||||
|
lineId: payload.line_id,
|
||||||
status: 'open',
|
status: 'open',
|
||||||
history: JSON.stringify(history),
|
history: JSON.stringify(history),
|
||||||
},
|
},
|
||||||
|
|
@ -149,11 +150,14 @@ export default class TicketsController {
|
||||||
const listHistory = ticket.history ? JSON.parse(ticket.history) : []
|
const listHistory = ticket.history ? JSON.parse(ticket.history) : []
|
||||||
listHistory.unshift(history)
|
listHistory.unshift(history)
|
||||||
payload.history = JSON.stringify(listHistory)
|
payload.history = JSON.stringify(listHistory)
|
||||||
|
delete payload.userName
|
||||||
|
delete payload.userId
|
||||||
ticket.merge(payload)
|
ticket.merge(payload)
|
||||||
await ticket.save()
|
await ticket.save()
|
||||||
|
|
||||||
return response.ok({ status: true, message: 'Ticket updated successfully', data: ticket })
|
return response.ok({ status: true, message: 'Ticket updated successfully', data: ticket })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
return response.internalServerError({
|
return response.internalServerError({
|
||||||
status: false,
|
status: false,
|
||||||
message: 'Failed to update ticket',
|
message: 'Failed to update ticket',
|
||||||
|
|
|
||||||
|
|
@ -448,6 +448,10 @@ export class WebSocketIo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
socket.on('update_ticket', async (data) => {
|
||||||
|
io.emit('update_ticket', data)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
socketServer.listen(SOCKET_IO_PORT, () => {
|
socketServer.listen(SOCKET_IO_PORT, () => {
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ router
|
||||||
router.post('/all', '#controllers/tickets_controller.getAll')
|
router.post('/all', '#controllers/tickets_controller.getAll')
|
||||||
router.post('create', '#controllers/tickets_controller.create')
|
router.post('create', '#controllers/tickets_controller.create')
|
||||||
|
|
||||||
router.put('update/:id', '#controllers/tickets_controller.update')
|
router.post('update/:id', '#controllers/tickets_controller.update')
|
||||||
router.delete('delete/:id', '#controllers/tickets_controller.delete')
|
router.delete('delete/:id', '#controllers/tickets_controller.delete')
|
||||||
})
|
})
|
||||||
.prefix('api/ticket')
|
.prefix('api/ticket')
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"motion": "^12.23.24",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.9.4",
|
"react-router-dom": "^7.9.4",
|
||||||
|
|
@ -2868,6 +2869,33 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/framer-motion": {
|
||||||
|
"version": "12.23.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
|
||||||
|
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-dom": "^12.23.23",
|
||||||
|
"motion-utils": "^12.23.6",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
|
@ -3339,6 +3367,47 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/motion": {
|
||||||
|
"version": "12.23.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion/-/motion-12.23.24.tgz",
|
||||||
|
"integrity": "sha512-Rc5E7oe2YZ72N//S3QXGzbnXgqNrTESv8KKxABR20q2FLch9gHLo0JLyYo2hZ238bZ9Gx6cWhj9VO0IgwbMjCw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"framer-motion": "^12.23.24",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@emotion/is-prop-valid": "*",
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@emotion/is-prop-valid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-dom": {
|
||||||
|
"version": "12.23.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
|
||||||
|
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"motion-utils": "^12.23.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/motion-utils": {
|
||||||
|
"version": "12.23.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
|
||||||
|
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"motion": "^12.23.24",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-router-dom": "^7.9.4",
|
"react-router-dom": "^7.9.4",
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,7 @@ function App() {
|
||||||
const [usersConnecting, setUsersConnecting] = useState<TUser[]>([]);
|
const [usersConnecting, setUsersConnecting] = useState<TUser[]>([]);
|
||||||
const [testLogContent, setTestLogContent] = useState("");
|
const [testLogContent, setTestLogContent] = useState("");
|
||||||
const [isLogModalOpen, setIsLogModalOpen] = useState(false);
|
const [isLogModalOpen, setIsLogModalOpen] = useState(false);
|
||||||
|
const [expandedBottomBar, setExpandedBottomBar] = useState(true);
|
||||||
|
|
||||||
const connectApcSwitch = (station: TStation) => {
|
const connectApcSwitch = (station: TStation) => {
|
||||||
if (station?.apc_1_ip && station?.apc_1_port) {
|
if (station?.apc_1_ip && station?.apc_1_port) {
|
||||||
|
|
@ -281,6 +282,18 @@ function App() {
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket?.on("update_ticket", (data) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
updateValueLineStation(
|
||||||
|
data.lineId,
|
||||||
|
{
|
||||||
|
tickets: data.data,
|
||||||
|
},
|
||||||
|
data?.stationId
|
||||||
|
);
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ cleanup on unmount or when socket changes
|
// ✅ cleanup on unmount or when socket changes
|
||||||
return () => {
|
return () => {
|
||||||
socket.off("init");
|
socket.off("init");
|
||||||
|
|
@ -293,6 +306,7 @@ function App() {
|
||||||
socket.off("user_close_cli");
|
socket.off("user_close_cli");
|
||||||
socket.off("response_content_log");
|
socket.off("response_content_log");
|
||||||
socket.off("data_textfsm");
|
socket.off("data_textfsm");
|
||||||
|
socket.off("update_ticket");
|
||||||
};
|
};
|
||||||
}, [socket, stations, selectedLine]);
|
}, [socket, stations, selectedLine]);
|
||||||
|
|
||||||
|
|
@ -396,7 +410,7 @@ function App() {
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ScrollArea h={"73vh"}>
|
<ScrollArea h={expandedBottomBar ? "73vh" : "85vh"}>
|
||||||
{station.lines.length > 8 ? (
|
{station.lines.length > 8 ? (
|
||||||
<Grid
|
<Grid
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -492,6 +506,7 @@ function App() {
|
||||||
setIsLogModalOpen={setIsLogModalOpen}
|
setIsLogModalOpen={setIsLogModalOpen}
|
||||||
setTestLogContent={setTestLogContent}
|
setTestLogContent={setTestLogContent}
|
||||||
scenarios={scenarios}
|
scenarios={scenarios}
|
||||||
|
setExpanded={setExpandedBottomBar}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
ActionIcon,
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
CloseButton,
|
CloseButton,
|
||||||
|
|
@ -18,6 +19,8 @@ import { ButtonDPELP, ButtonScenario, ButtonSelect } from "./ButtonAction";
|
||||||
import DrawerLogs from "./DrawerLogs";
|
import DrawerLogs from "./DrawerLogs";
|
||||||
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
|
import { DrawerAPCControl, DrawerSwitchControl } from "./DrawerControl";
|
||||||
import { isJsonString } from "../untils/helper";
|
import { isJsonString } from "../untils/helper";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import { IconCaretDown, IconCaretUp } from "@tabler/icons-react";
|
||||||
|
|
||||||
interface TabsProps {
|
interface TabsProps {
|
||||||
selectedLines: TLine[];
|
selectedLines: TLine[];
|
||||||
|
|
@ -31,6 +34,7 @@ interface TabsProps {
|
||||||
setIsLogModalOpen: (value: React.SetStateAction<boolean>) => void;
|
setIsLogModalOpen: (value: React.SetStateAction<boolean>) => void;
|
||||||
setTestLogContent: (value: React.SetStateAction<string>) => void;
|
setTestLogContent: (value: React.SetStateAction<string>) => void;
|
||||||
scenarios: IScenario[];
|
scenarios: IScenario[];
|
||||||
|
setExpanded: (value: React.SetStateAction<boolean>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BottomToolBar = ({
|
const BottomToolBar = ({
|
||||||
|
|
@ -45,6 +49,7 @@ const BottomToolBar = ({
|
||||||
setIsLogModalOpen,
|
setIsLogModalOpen,
|
||||||
setTestLogContent,
|
setTestLogContent,
|
||||||
scenarios,
|
scenarios,
|
||||||
|
setExpanded,
|
||||||
}: TabsProps) => {
|
}: TabsProps) => {
|
||||||
const user = useMemo(() => {
|
const user = useMemo(() => {
|
||||||
return localStorage.getItem("user") &&
|
return localStorage.getItem("user") &&
|
||||||
|
|
@ -54,8 +59,46 @@ const BottomToolBar = ({
|
||||||
}, []);
|
}, []);
|
||||||
const [valueInput, setValueInput] = useState<string>("");
|
const [valueInput, setValueInput] = useState<string>("");
|
||||||
const [activeTabBottom, setActiveBottom] = useState<string>("command");
|
const [activeTabBottom, setActiveBottom] = useState<string>("command");
|
||||||
|
const [isExpand, setIsExpand] = useState<boolean>(true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={false}
|
||||||
|
animate={{
|
||||||
|
height: isExpand ? "15vh" : 0,
|
||||||
|
y: 0, // đẩy xuống khi thu nhỏ
|
||||||
|
}}
|
||||||
|
transition={{ type: "spring", stiffness: 180, damping: 20 }}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
position: "fixed",
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box style={{ position: "relative" }}>
|
||||||
|
<ActionIcon
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: isExpand ? -4 : -24,
|
||||||
|
left: "50%",
|
||||||
|
translate: "-19px 0",
|
||||||
|
backgroundColor: "#e3e0e0",
|
||||||
|
width: "55px",
|
||||||
|
}}
|
||||||
|
variant="light"
|
||||||
|
onClick={() => {
|
||||||
|
setIsExpand((prev) => !prev);
|
||||||
|
setExpanded((prev) => !prev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isExpand ? (
|
||||||
|
<IconCaretDown color="green" />
|
||||||
|
) : (
|
||||||
|
<IconCaretUp color="green" />
|
||||||
|
)}
|
||||||
|
</ActionIcon>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.Col span={1}></Grid.Col>
|
<Grid.Col span={1}></Grid.Col>
|
||||||
<Grid.Col span={10}>
|
<Grid.Col span={10}>
|
||||||
|
|
@ -72,7 +115,8 @@ const BottomToolBar = ({
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: activeTabBottom === "command" ? "#c8d9fd" : "",
|
backgroundColor:
|
||||||
|
activeTabBottom === "command" ? "#c8d9fd" : "",
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
paddingTop: "8px",
|
paddingTop: "8px",
|
||||||
paddingBottom: "8px",
|
paddingBottom: "8px",
|
||||||
|
|
@ -94,7 +138,8 @@ const BottomToolBar = ({
|
||||||
</Tabs.Tab>
|
</Tabs.Tab>
|
||||||
<Tabs.Tab
|
<Tabs.Tab
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: activeTabBottom === "switch" ? "#c8d9fd" : "",
|
backgroundColor:
|
||||||
|
activeTabBottom === "switch" ? "#c8d9fd" : "",
|
||||||
fontSize: "13px",
|
fontSize: "13px",
|
||||||
paddingTop: "8px",
|
paddingTop: "8px",
|
||||||
paddingBottom: "8px",
|
paddingBottom: "8px",
|
||||||
|
|
@ -127,7 +172,9 @@ const BottomToolBar = ({
|
||||||
aria-label="Clear input"
|
aria-label="Clear input"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedLines(
|
setSelectedLines(
|
||||||
selectedLines.filter((line) => line.id !== el.id)
|
selectedLines.filter(
|
||||||
|
(line) => line.id !== el.id
|
||||||
|
)
|
||||||
);
|
);
|
||||||
socket?.emit("close_cli", {
|
socket?.emit("close_cli", {
|
||||||
lineId: el?.id,
|
lineId: el?.id,
|
||||||
|
|
@ -337,6 +384,8 @@ const BottomToolBar = ({
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
<Grid.Col span={1}></Grid.Col>
|
<Grid.Col span={1}></Grid.Col>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,42 +68,42 @@ export const ButtonDPELP = ({
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show diag",
|
send: "show diag",
|
||||||
delay: "2000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show post",
|
send: "show post",
|
||||||
delay: "3000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show env all",
|
send: "show env all",
|
||||||
delay: "3000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show license",
|
send: "show license",
|
||||||
delay: "3000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show log",
|
send: "show log",
|
||||||
delay: "3000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expect: "",
|
expect: "",
|
||||||
send: "show platform",
|
send: "show platform",
|
||||||
delay: "3000",
|
delay: "1500",
|
||||||
repeat: "1",
|
repeat: "1",
|
||||||
note: "",
|
note: "",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const CardLine = ({
|
||||||
}, []);
|
}, []);
|
||||||
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
const [isDisabled, setIsDisabled] = useState<boolean>(false);
|
||||||
const [valueBaud, setValueBaud] = useState<string>("");
|
const [valueBaud, setValueBaud] = useState<string>("");
|
||||||
|
const [focusTerminal, setFocusTerminal] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -109,6 +110,7 @@ const CardLine = ({
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setSelectedLines((pre) => [...pre, line]);
|
setSelectedLines((pre) => [...pre, line]);
|
||||||
|
setFocusTerminal(true);
|
||||||
socket?.emit("open_cli", {
|
socket?.emit("open_cli", {
|
||||||
lineId: line.id,
|
lineId: line.id,
|
||||||
stationId: line.stationId || line.station_id,
|
stationId: line.stationId || line.station_id,
|
||||||
|
|
@ -177,6 +179,10 @@ const CardLine = ({
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`PID: ${line?.inventory?.pid || ""} | SN: ${
|
`PID: ${line?.inventory?.pid || ""} | SN: ${
|
||||||
line?.inventory?.sn || ""
|
line?.inventory?.sn || ""
|
||||||
|
} | Ticket: ${
|
||||||
|
line?.tickets && line?.tickets?.length > 0
|
||||||
|
? line?.tickets[0].description
|
||||||
|
: ""
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|
@ -541,6 +547,7 @@ const CardLine = ({
|
||||||
handleClick();
|
handleClick();
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
|
setFocusTerminal(false);
|
||||||
if (
|
if (
|
||||||
!selectedLines.find((value) => value.id === line?.id) &&
|
!selectedLines.find((value) => value.id === line?.id) &&
|
||||||
line?.userOpenCLI === user?.userName
|
line?.userOpenCLI === user?.userName
|
||||||
|
|
@ -550,6 +557,7 @@ const CardLine = ({
|
||||||
stationId: line.stationId || line.station_id,
|
stationId: line.stationId || line.station_id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
focusTerminal={focusTerminal}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,6 @@ const ModalTerminal = ({
|
||||||
? JSON.parse(localStorage.getItem("user") || "")
|
? JSON.parse(localStorage.getItem("user") || "")
|
||||||
: null;
|
: null;
|
||||||
}, []);
|
}, []);
|
||||||
const [inputTicket, setInputTicket] = useState<string>("");
|
|
||||||
const [isDisable, setIsDisable] = useState<boolean>(false);
|
const [isDisable, setIsDisable] = useState<boolean>(false);
|
||||||
const [isDisableTicket, setIsDisableTicket] = useState<boolean>(false);
|
const [isDisableTicket, setIsDisableTicket] = useState<boolean>(false);
|
||||||
const [latestTicket, setLatestTicket] = useState<TDataTicket>({
|
const [latestTicket, setLatestTicket] = useState<TDataTicket>({
|
||||||
|
|
@ -116,7 +115,7 @@ const ModalTerminal = ({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text style={{ fontSize: "14px", fontWeight: "bold" }} fw={600}>
|
<Text style={{ fontSize: "14px", fontWeight: "bold" }} fw={600}>
|
||||||
@{item?.userName}:
|
{item?.status === "closed" ? "*" : ""}@{item?.userName}:
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
|
|
@ -168,14 +167,18 @@ const ModalTerminal = ({
|
||||||
description: dataTicket.description.trim(),
|
description: dataTicket.description.trim(),
|
||||||
model: dataTicket.model.trim(),
|
model: dataTicket.model.trim(),
|
||||||
sn: dataTicket.sn.trim(),
|
sn: dataTicket.sn.trim(),
|
||||||
station_id: Number(dataTicket.station_id),
|
station_id: Number(stationItem?.id),
|
||||||
|
line_id: Number(line?.id),
|
||||||
status: "open",
|
status: "open",
|
||||||
|
userName: user?.userName,
|
||||||
|
userId: user?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.post(apiUrl + "api/ticket/create", payload);
|
const res = await axios.post(apiUrl + "api/ticket/create", payload);
|
||||||
if (res.status) {
|
if (res.status) {
|
||||||
// setDataTicket(res.data)
|
// setLatestTicket(res.data);
|
||||||
|
// setDataTicket({ ...res.data, description: "" });
|
||||||
|
|
||||||
// notifications.show({
|
// notifications.show({
|
||||||
// title: 'Success',
|
// title: 'Success',
|
||||||
|
|
@ -183,10 +186,11 @@ const ModalTerminal = ({
|
||||||
// color: 'green',
|
// color: 'green',
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// socket?.emit(
|
socket?.emit("update_ticket", {
|
||||||
// "create_ticket",
|
lineId: Number(line?.id),
|
||||||
// payload,
|
data: [res.data.data, ...(line?.tickets || [])],
|
||||||
// )
|
stationId: Number(stationItem?.id),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -218,38 +222,44 @@ const ModalTerminal = ({
|
||||||
: "",
|
: "",
|
||||||
model: dataTicket.model.trim(),
|
model: dataTicket.model.trim(),
|
||||||
sn: dataTicket.sn.trim(),
|
sn: dataTicket.sn.trim(),
|
||||||
station_id: Number(dataTicket.station_id),
|
station_id: Number(stationItem?.id),
|
||||||
|
line_id: Number(line?.id),
|
||||||
status: status,
|
status: status,
|
||||||
|
userName: user?.userName,
|
||||||
|
userId: user?.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.put(
|
const res = await axios.post(
|
||||||
`${apiUrl + "api/ticket/create" + "/" + dataTicket.id}`,
|
`${apiUrl + "api/ticket/update" + "/" + dataTicket.id}`,
|
||||||
payload
|
payload
|
||||||
);
|
);
|
||||||
if (res.status) {
|
if (res.status) {
|
||||||
if (res?.data?.status !== "closed")
|
// if (res?.data?.status !== "closed")
|
||||||
setDataTicket({ ...res.data, description: "" });
|
// setDataTicket({ ...res.data, description: "" });
|
||||||
else
|
// else
|
||||||
setDataTicket({
|
// setDataTicket({
|
||||||
id: 0,
|
// id: 0,
|
||||||
description: "",
|
// description: "",
|
||||||
model: latestTicket.model.trim(),
|
// model: latestTicket.model.trim(),
|
||||||
sn: latestTicket.sn.trim(),
|
// sn: latestTicket.sn.trim(),
|
||||||
station_id: latestTicket.station_id,
|
// station_id: latestTicket.station_id,
|
||||||
history: "",
|
// history: "",
|
||||||
status: "open",
|
// status: "open",
|
||||||
});
|
// });
|
||||||
|
|
||||||
// notifications.show({
|
// notifications.show({
|
||||||
// title: 'Success',
|
// title: 'Success',
|
||||||
// message: res.message,
|
// message: res.message,
|
||||||
// color: 'green',
|
// color: 'green',
|
||||||
// })
|
// })
|
||||||
// socket?.emit(
|
socket?.emit("update_ticket", {
|
||||||
// SOCKET_EVENTS.RELOAD_TICKET.RELOAD_TICKET_FROM_WEB,
|
lineId: Number(line?.id),
|
||||||
// payload,
|
data: line?.tickets?.map((el) =>
|
||||||
// )
|
el.id === dataTicket.id ? res.data.data : el
|
||||||
|
),
|
||||||
|
stationId: Number(stationItem?.id),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -308,7 +318,7 @@ const ModalTerminal = ({
|
||||||
>
|
>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.Col span={2}>
|
<Grid.Col span={2}>
|
||||||
<Flex justify={"space-between"} direction={"column"} h={"100%"}>
|
<Flex justify={"space-between"} direction={"column"} h={"95%"}>
|
||||||
<Box>
|
<Box>
|
||||||
<Flex gap={"sm"} justify={"center"} align={"center"}>
|
<Flex gap={"sm"} justify={"center"} align={"center"}>
|
||||||
<Text size="xl">
|
<Text size="xl">
|
||||||
|
|
@ -324,7 +334,7 @@ const ModalTerminal = ({
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex mt="4px">
|
<Flex mt="4px">
|
||||||
<Text size="md" w={"50px"}>
|
<Text size="md" mr="6px">
|
||||||
BAUD:
|
BAUD:
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="md">
|
<Text size="md">
|
||||||
|
|
@ -332,7 +342,7 @@ const ModalTerminal = ({
|
||||||
</Text>
|
</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex mt="4px">
|
<Flex mt="4px">
|
||||||
<Text size="md" w={"50px"}>
|
<Text size="md" mr="6px">
|
||||||
PID:
|
PID:
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="md">{line?.inventory?.pid || ""}</Text>
|
<Text size="md">{line?.inventory?.pid || ""}</Text>
|
||||||
|
|
@ -345,13 +355,13 @@ const ModalTerminal = ({
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex mt="4px">
|
<Flex mt="4px">
|
||||||
<Text size="md" w={"50px"}>
|
<Text size="md" mr="6px">
|
||||||
SN:
|
SN:
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="md">{line?.inventory?.sn || ""}</Text>
|
<Text size="md">{line?.inventory?.sn || ""}</Text>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex mt="4px">
|
<Flex mt="4px">
|
||||||
<Text size="md" mr={"sm"} fw={"bold"} w={"50px"}>
|
<Text size="md" mr={"6px"} fw={"bold"}>
|
||||||
IOS:
|
IOS:
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="md">{""}</Text>
|
<Text size="md">{""}</Text>
|
||||||
|
|
@ -543,7 +553,6 @@ const ModalTerminal = ({
|
||||||
variant="filled"
|
variant="filled"
|
||||||
color="orange"
|
color="orange"
|
||||||
size="xs"
|
size="xs"
|
||||||
radius="md"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
socket?.emit("write_command_line_from_web", {
|
socket?.emit("write_command_line_from_web", {
|
||||||
lineIds: [line?.id],
|
lineIds: [line?.id],
|
||||||
|
|
@ -648,7 +657,11 @@ const ModalTerminal = ({
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<ScrollArea h={600} style={{ border: "1px solid #ccc" }} p={"4px"}>
|
<ScrollArea
|
||||||
|
h={"65vh"}
|
||||||
|
style={{ border: "1px solid #ccc" }}
|
||||||
|
p={"4px"}
|
||||||
|
>
|
||||||
{renderHistory(latestTicket)}
|
{renderHistory(latestTicket)}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
<Box mt={"8px"}>
|
<Box mt={"8px"}>
|
||||||
|
|
@ -658,14 +671,16 @@ const ModalTerminal = ({
|
||||||
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
|
boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.1)",
|
||||||
}}
|
}}
|
||||||
placeholder={"Input description ticket"}
|
placeholder={"Input description ticket"}
|
||||||
value={inputTicket}
|
value={dataTicket.description || ""}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
const newValue = event.currentTarget.value;
|
setDataTicket((pre) => ({
|
||||||
setInputTicket(newValue);
|
...pre,
|
||||||
|
description: event.target.value,
|
||||||
|
}));
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.key === "Enter") {
|
if (event.key === "Enter" && dataTicket.description) {
|
||||||
setInputTicket("");
|
setDataTicket((pre) => ({ ...pre, description: "" }));
|
||||||
if (dataTicket?.status === "closed") {
|
if (dataTicket?.status === "closed") {
|
||||||
handleCreate();
|
handleCreate();
|
||||||
} else handleUpdate("open");
|
} else handleUpdate("open");
|
||||||
|
|
@ -679,9 +694,11 @@ const ModalTerminal = ({
|
||||||
rightSection={
|
rightSection={
|
||||||
<CloseButton
|
<CloseButton
|
||||||
aria-label="Clear input"
|
aria-label="Clear input"
|
||||||
onClick={() => setInputTicket("")}
|
onClick={() =>
|
||||||
|
setDataTicket((pre) => ({ ...pre, description: "" }))
|
||||||
|
}
|
||||||
style={{
|
style={{
|
||||||
display: inputTicket ? undefined : "none",
|
display: dataTicket?.description ? undefined : "none",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
@ -691,12 +708,13 @@ const ModalTerminal = ({
|
||||||
<Flex justify={"end"} mt={"4px"}>
|
<Flex justify={"end"} mt={"4px"}>
|
||||||
{dataTicket?.status === "closed" ? (
|
{dataTicket?.status === "closed" ? (
|
||||||
<Button
|
<Button
|
||||||
disabled={isDisableTicket}
|
disabled={isDisableTicket || !dataTicket.description}
|
||||||
mr={"8px"}
|
mr={"8px"}
|
||||||
fw={400}
|
fw={400}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setDataTicket((pre) => ({ ...pre, description: "" }));
|
||||||
handleCreate();
|
handleCreate();
|
||||||
setIsDisableTicket(true);
|
setIsDisableTicket(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -708,7 +726,7 @@ const ModalTerminal = ({
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
disabled={isDisableTicket}
|
disabled={isDisableTicket || !dataTicket.description}
|
||||||
mr={"8px"}
|
mr={"8px"}
|
||||||
fw={400}
|
fw={400}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|
@ -725,13 +743,18 @@ const ModalTerminal = ({
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
disabled={isDisableTicket || dataTicket?.status === "closed"}
|
disabled={
|
||||||
|
isDisableTicket ||
|
||||||
|
dataTicket?.status === "closed" ||
|
||||||
|
!dataTicket.description
|
||||||
|
}
|
||||||
mr={"8px"}
|
mr={"8px"}
|
||||||
fw={400}
|
fw={400}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
color="orange"
|
color="orange"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
setDataTicket((pre) => ({ ...pre, description: "" }));
|
||||||
handleUpdate("issue");
|
handleUpdate("issue");
|
||||||
setIsDisableTicket(true);
|
setIsDisableTicket(true);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ interface TerminalCLIProps {
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
miniSize?: boolean;
|
miniSize?: boolean;
|
||||||
loadingContent?: boolean;
|
loadingContent?: boolean;
|
||||||
|
focusTerminal?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
|
|
@ -46,6 +47,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
loadingContent = false,
|
loadingContent = false,
|
||||||
onFocus,
|
onFocus,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
focusTerminal,
|
||||||
}) => {
|
}) => {
|
||||||
const xtermRef = useRef<HTMLDivElement>(null);
|
const xtermRef = useRef<HTMLDivElement>(null);
|
||||||
const terminal = useRef<Terminal>(null);
|
const terminal = useRef<Terminal>(null);
|
||||||
|
|
@ -177,6 +179,12 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (focusTerminal && terminal.current) {
|
||||||
|
terminal.current?.focus();
|
||||||
|
}
|
||||||
|
}, [focusTerminal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
|
|
@ -185,7 +193,7 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
height: "100%",
|
height: "100%",
|
||||||
backgroundColor: "black",
|
backgroundColor: "black",
|
||||||
paddingBottom: customStyle.paddingBottom ?? "10px",
|
paddingBottom: customStyle.paddingBottom ?? "10px",
|
||||||
maxHeight: customStyle.maxHeight ?? "73vh",
|
maxHeight: customStyle.maxHeight ?? "70vh",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
@ -206,8 +214,8 @@ const TerminalCLI: React.FC<TerminalCLIProps> = ({
|
||||||
paddingLeft: customStyle.paddingLeft ?? "10px",
|
paddingLeft: customStyle.paddingLeft ?? "10px",
|
||||||
paddingBottom: customStyle.paddingBottom ?? "10px",
|
paddingBottom: customStyle.paddingBottom ?? "10px",
|
||||||
fontSize: customStyle.fontSize ?? "9px",
|
fontSize: customStyle.fontSize ?? "9px",
|
||||||
maxHeight: customStyle.maxHeight ?? "73vh",
|
maxHeight: customStyle.maxHeight ?? "70vh",
|
||||||
height: customStyle.height ?? "73vh",
|
height: customStyle.height ?? "70vh",
|
||||||
padding: customStyle.padding ?? "4px",
|
padding: customStyle.padding ?? "4px",
|
||||||
}}
|
}}
|
||||||
onDoubleClick={(event) => {
|
onDoubleClick={(event) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue