Files
hotel-mqtt/src/App.jsx
yogiedigital aa2eacbf38 add env port
2025-09-20 01:43:40 +07:00

437 lines
14 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Button, Input, Card, notification, Space, Typography, Alert, Row, Col } from 'antd';
import { ReadOutlined, EditOutlined, DeleteOutlined, WifiOutlined } from '@ant-design/icons';
import mqtt from 'mqtt';
import dayjs from 'dayjs';
const { Title, Text } = Typography;
const { TextArea } = Input;
function App() {
const [lockNo, setLockNo] = useState('1');
const [startTime, setStartTime] = useState(dayjs().format('YYYY-MM-DDTHH:mm'));
const [endTime, setEndTime] = useState(
dayjs().add(1, 'day').set('hour', 12).set('minute', 0).format('YYYY-MM-DDTHH:mm')
);
const [isConnected, setIsConnected] = useState(false);
const [connectionError, setConnectionError] = useState('');
const [client, setClient] = useState(null);
const [receivedMessages, setReceivedMessages] = useState('No messages received yet');
// MQTT connection options
const mqttOptions = {
host: `${import.meta.env.VITE_MQTT_SERVER}`,
// host: 'mosquitto-apotek-2',
port: Number(`${import.meta.env.VITE_MQTT_PORT}`),
// protocol: "mqtt",
username: 'morekmorekmorek',
password: 'morek888',
// connectTimeout: 5000,
// reconnectPeriod: 5000,
};
// Update end time when start time changes
useEffect(() => {
const start = dayjs(startTime);
const newEndTime = start.add(1, 'day').set('hour', 12).set('minute', 0).set('second', 0);
setEndTime(newEndTime.format('YYYY-MM-DDTHH:mm'));
}, [startTime]);
// Connect to MQTT broker - and keep connection alive
useEffect(() => {
let mqttClient;
const connectToMqtt = () => {
try {
const connectUrl = `mqtt://${mqttOptions.host}:${mqttOptions.port}`;
console.log('Attempting to connect to:', connectUrl);
mqttClient = mqtt.connect(connectUrl, {
username: mqttOptions.username,
password: mqttOptions.password,
clientId: 'mqttjs_' + Math.random().toString(16).substring(2, 8),
connectTimeout: mqttOptions.connectTimeout,
reconnectPeriod: mqttOptions.reconnectPeriod,
clean: false,
keepalive: 30,
});
setClient(mqttClient);
mqttClient.on('connect', () => {
console.log('Connected to MQTT broker');
setIsConnected(true);
setConnectionError('');
notification.success({
message: 'MQTT Connected',
description: 'Successfully connected to MQTT broker',
duration: 2,
});
mqttClient.subscribe('hotel1/pc1', (err) => {
if (err) {
console.error('Subscription error:', err);
} else {
console.log('Subscribed to topic: hotel1/pc1');
}
});
});
mqttClient.on('error', (err) => {
console.error('MQTT connection error:', err);
setConnectionError(err.message);
setIsConnected(false);
});
mqttClient.on('close', () => {
console.log('MQTT connection closed');
setIsConnected(false);
});
mqttClient.on('reconnect', () => {
console.log('Attempting to reconnect to MQTT broker');
setIsConnected(false);
setConnectionError('Reconnecting...');
});
mqttClient.on('offline', () => {
console.log('MQTT client is offline');
setIsConnected(false);
setConnectionError('Offline - attempting to reconnect');
});
mqttClient.on('message', (topic, message) => {
console.log('Received message:', topic, message.toString());
const newMessage = `[${new Date().toLocaleTimeString()}] ${topic}: ${message.toString()}\n`;
setReceivedMessages(prev => prev === 'No messages received yet' ? newMessage : prev + newMessage);
});
} catch (err) {
console.error('Failed to initialize MQTT client:', err);
setConnectionError(err.message);
setTimeout(connectToMqtt, 5000);
}
};
connectToMqtt();
}, []);
const handleStartTimeChange = (e) => {
const newStartTime = e.target.value;
setStartTime(newStartTime);
};
const handleEndTimeChange = (e) => {
const newEndTime = e.target.value;
const start = dayjs(startTime);
const end = dayjs(newEndTime);
if (end.isBefore(start)) {
notification.error({
message: 'Invalid End Time',
description: 'End time cannot be before start time',
duration: 3,
});
return;
}
setEndTime(newEndTime);
};
// Format datetime to YYYY-MM-DD HH:mm:ss
const formatDateTime = (dateTimeString) => {
return dayjs(dateTimeString).format('YYYY-MM-DD HH:mm:ss');
};
const handleRead = () => {
if (client && isConnected) {
const payload = JSON.stringify({ cmd: "cek_kartu" });
client.publish('hotel1/pc1', payload);
notification.info({
message: 'Read Command Sent',
description: `Published: ${payload}`,
duration: 2,
});
} else {
notification.warning({
message: 'Not Connected',
description: 'Cannot publish message - not connected to MQTT',
duration: 2,
});
}
};
const handleWrite = () => {
if (client && isConnected) {
if (!lockNo || !startTime || !endTime) {
notification.error({
message: 'Validation Error',
description: 'Please fill in all fields',
duration: 2,
});
return;
}
const start = dayjs(startTime);
const end = dayjs(endTime);
if (end.isBefore(start)) {
notification.error({
message: 'Validation Error',
description: 'End time must be after start time',
duration: 2,
});
return;
}
// Format the datetime values before sending
const formattedStartTime = formatDateTime(startTime);
const formattedEndTime = formatDateTime(endTime);
const payload = JSON.stringify({
cmd: "tulis_kartu",
lock_no: parseInt(lockNo),
start_time: formattedStartTime,
end_time: formattedEndTime
});
client.publish('hotel1/pc1', payload);
notification.info({
message: 'Write Command Sent',
description: `Published: ${payload}`,
duration: 2,
});
} else {
notification.warning({
message: 'Not Connected',
description: 'Cannot publish message - not connected to MQTT',
duration: 2,
});
}
};
const handleDelete = () => {
if (client && isConnected) {
const payload = JSON.stringify({ cmd: "hapus_kartu" });
client.publish('hotel1/pc1', payload);
notification.info({
message: 'Delete Command Sent',
description: `Published: ${payload}`,
duration: 2,
});
} else {
notification.warning({
message: 'Not Connected',
description: 'Cannot publish message - not connected to MQTT',
duration: 2,
});
}
};
const clearMessages = () => {
setReceivedMessages('No messages received yet');
};
const disconnectMqtt = () => {
if (client) {
client.end(false, () => {
console.log('Manually disconnected from MQTT');
setIsConnected(false);
setConnectionError('Manually disconnected');
});
}
};
const reconnectMqtt = () => {
if (client) {
client.reconnect();
}
};
// Calculate min and max values for datetime inputs
const minEndTime = startTime;
const maxStartTime = endTime;
return (
<div style={{ padding: '24px', minHeight: '100vh', background: '#f0f2f5' }}>
<Card
style={{
maxWidth: 800,
margin: '0 auto',
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
borderRadius: '8px'
}}
>
<Title level={2} style={{ textAlign: 'center', color: '#1890ff' }}>
Lock Management System
</Title>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '16px', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<WifiOutlined
style={{
color: isConnected ? '#52c41a' : '#ff4d4f',
fontSize: '20px',
marginRight: '8px'
}}
/>
<Text strong>
Status: {isConnected ? 'Connected' : 'Disconnected'}
</Text>
</div>
<Space>
<Button size="small" onClick={reconnectMqtt} disabled={isConnected}>
Reconnect
</Button>
<Button size="small" danger onClick={disconnectMqtt} disabled={!isConnected}>
Disconnect
</Button>
</Space>
</div>
{connectionError && (
<Alert
message="Connection Status"
description={connectionError}
type={isConnected ? 'success' : 'warning'}
showIcon
style={{ marginBottom: '16px' }}
/>
)}
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Row gutter={16}>
<Col span={8}>
<Text strong>Lock Number:</Text>
<Input
type="number"
min="1"
value={lockNo}
onChange={(e) => setLockNo(e.target.value)}
size="large"
style={{ marginTop: '8px' }}
placeholder="Enter lock number"
/>
</Col>
<Col span={8}>
<Text strong>Start Time:</Text>
<Input
type="datetime-local"
value={startTime}
onChange={handleStartTimeChange}
size="large"
style={{ marginTop: '8px' }}
max={maxStartTime}
/>
<Text type="secondary" style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}>
Will be formatted as: {formatDateTime(startTime)}
</Text>
</Col>
<Col span={8}>
<Text strong>End Time:</Text>
<Input
type="datetime-local"
value={endTime}
onChange={handleEndTimeChange}
size="large"
style={{ marginTop: '8px' }}
min={minEndTime}
/>
<Text type="secondary" style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}>
Will be formatted as: {formatDateTime(endTime)}
</Text>
</Col>
</Row>
<Space direction="horizontal" style={{ width: '100%', justifyContent: 'center' }}>
<Button
type="primary"
icon={<ReadOutlined />}
size="large"
onClick={handleRead}
style={{ background: '#52c41a', borderColor: '#52c41a' }}
>
Read
</Button>
<Button
type="primary"
icon={<EditOutlined />}
size="large"
onClick={handleWrite}
>
Write
</Button>
<Button
type="primary"
icon={<DeleteOutlined />}
size="large"
onClick={handleDelete}
danger
>
Delete
</Button>
</Space>
<div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
<Text strong>Received Messages:</Text>
<Button
size="small"
onClick={clearMessages}
style={{ marginLeft: '8px' }}
>
Clear
</Button>
</div>
<TextArea
rows={10}
value={receivedMessages}
readOnly
style={{
backgroundColor: '#f9f9f9',
fontFamily: 'monospace',
fontSize: '14px'
}}
placeholder="Messages from MQTT will appear here..."
/>
</div>
</Space>
<div style={{ marginTop: '24px' }}>
<Title level={5}>Connection Information</Title>
<Text type="secondary">
Broker: {mqttOptions.host}:{mqttOptions.port}<br />
Topic: hotel1/pc1<br />
Username: {mqttOptions.username}<br />
Client ID: {client?.options?.clientId || 'Not connected'}
</Text>
</div>
<div style={{ marginTop: '16px', padding: '12px', backgroundColor: '#f0f9ff', borderRadius: '6px' }}>
<Title level={5}>Usage Instructions</Title>
<Text type="secondary">
1. Enter lock number (integer)<br />
2. Select start time - end time will auto-set to next day at 12:00<br />
3. Adjust end time if needed (cannot be before start time)<br />
4. Click Write to send JSON: {"{"}lock_no: [value], start_time: "YYYY-MM-DD HH:mm:ss", end_time: "YYYY-MM-DD HH:mm:ss"{"}"}<br />
5. Received messages will appear in the text area below
</Text>
</div>
<div style={{ marginTop: '16px', padding: '12px', backgroundColor: '#fff7e6', borderRadius: '6px' }}>
<Title level={5}>Output Format</Title>
<Text type="secondary" style={{ fontFamily: 'monospace' }}>
{`{
"lock_no": ${lockNo},
"start_time": "${formatDateTime(startTime)}",
"end_time": "${formatDateTime(endTime)}"
}`}
</Text>
</div>
</Card>
</div>
);
}
export default App;