“init”
This commit is contained in:
10
src/App.jsx
10
src/App.jsx
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
||||||
import SignIn from './pages/auth/SignIn';
|
import SignIn from './pages/auth/SignIn';
|
||||||
import { ProtectedRoute } from './ProtectedRoute';
|
import { ProtectedRoute } from './ProtectedRoute';
|
||||||
|
import { UnprotectedRoute } from './UnprotectedRoute';
|
||||||
import NotFound from './pages/blank/NotFound';
|
import NotFound from './pages/blank/NotFound';
|
||||||
import { getSessionData } from './components/Global/Formatter';
|
import { getSessionData } from './components/Global/Formatter';
|
||||||
|
|
||||||
@@ -11,6 +12,10 @@ import Blank from './pages/blank/Blank';
|
|||||||
|
|
||||||
// master
|
// master
|
||||||
import IndexDevice from './pages/master/device/IndexDevice';
|
import IndexDevice from './pages/master/device/IndexDevice';
|
||||||
|
import Room from './pages/master/room/RoomManagement';
|
||||||
|
import RoomBook from './pages/RoomBook';
|
||||||
|
|
||||||
|
// import MainLayout from './layout/MainLayout';
|
||||||
|
|
||||||
// Setting
|
// Setting
|
||||||
|
|
||||||
@@ -38,6 +43,11 @@ const App = () => {
|
|||||||
<Route path="/" element={<Navigate to="/dashboard/home-vendor" />} />
|
<Route path="/" element={<Navigate to="/dashboard/home-vendor" />} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Route path="/new" element={<UnprotectedRoute />}>
|
||||||
|
<Route path="room" element={<Room />} />
|
||||||
|
<Route path="frontdesk" element={<RoomBook />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route path="/signin" element={<SignIn />} />
|
<Route path="/signin" element={<SignIn />} />
|
||||||
<Route path="/dashboard" element={<ProtectedRoute />}>
|
<Route path="/dashboard" element={<ProtectedRoute />}>
|
||||||
<Route path="home" element={<Home />} />
|
<Route path="home" element={<Home />} />
|
||||||
|
|||||||
20
src/UnprotectedRoute.jsx
Normal file
20
src/UnprotectedRoute.jsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Navigate, Outlet } from 'react-router-dom';
|
||||||
|
import MainLayout from './layout/MainLayout';
|
||||||
|
|
||||||
|
// import { getSessionData } from './components/Global/Formatter';
|
||||||
|
|
||||||
|
export const UnprotectedRoute = () => {
|
||||||
|
// const session = getSessionData();
|
||||||
|
// console.log(session);
|
||||||
|
|
||||||
|
// const isAuthenticated = session?.auth ?? false;
|
||||||
|
// if (!isAuthenticated) {
|
||||||
|
// return <Navigate to="/signin" replace />;
|
||||||
|
// }
|
||||||
|
return (
|
||||||
|
<MainLayout>
|
||||||
|
<Outlet />
|
||||||
|
</MainLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Image } from 'antd';
|
import { Image } from 'antd';
|
||||||
import logoPiu from '../assets/freepik/LOGOPIU.png';
|
// import logoPiu from '../assets/freepik/LOGOPIU.png';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const LayoutLogo = () => {
|
const LayoutLogo = () => {
|
||||||
@@ -50,7 +50,7 @@ const LayoutLogo = () => {
|
|||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
{/* <Image
|
||||||
src={logoPiu}
|
src={logoPiu}
|
||||||
alt="logo"
|
alt="logo"
|
||||||
width={140}
|
width={140}
|
||||||
@@ -59,7 +59,7 @@ const LayoutLogo = () => {
|
|||||||
style={{
|
style={{
|
||||||
filter: 'drop-shadow(0 0 3px rgba(0, 0, 0, 0.2))',
|
filter: 'drop-shadow(0 0 3px rgba(0, 0, 0, 0.2))',
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,16 +5,23 @@ import LayoutMenu from './LayoutMenu';
|
|||||||
|
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
const LayoutSidebar = () => {
|
const LayoutSidebar = () => {
|
||||||
|
const [collapsed, setCollapsed] = React.useState(false);
|
||||||
return (
|
return (
|
||||||
<Sider width={300}
|
<Sider
|
||||||
|
width={300}
|
||||||
|
// width={0}
|
||||||
breakpoint="lg"
|
breakpoint="lg"
|
||||||
collapsedWidth="0"
|
collapsedWidth="0"
|
||||||
onBreakpoint={(broken) => {
|
onBreakpoint={(broken) => {
|
||||||
// console.log(broken);
|
// console.log(broken);
|
||||||
}}
|
}}
|
||||||
onCollapse={(collapsed, type) => {
|
// trigger={null}
|
||||||
// console.log(collapsed, type);
|
collapsible
|
||||||
}}
|
collapsed={collapsed}
|
||||||
|
onCollapse={value => setCollapsed(value)}
|
||||||
|
// onCollapse={(collapsed, type) => {
|
||||||
|
// console.log(collapsed, type);
|
||||||
|
// }}
|
||||||
>
|
>
|
||||||
<LayoutLogo />
|
<LayoutLogo />
|
||||||
<LayoutMenu />
|
<LayoutMenu />
|
||||||
|
|||||||
@@ -12,14 +12,17 @@ const MainLayout = ({ children }) => {
|
|||||||
token: { colorBgContainer, borderRadiusLG },
|
token: { colorBgContainer, borderRadiusLG },
|
||||||
} = theme.useToken();
|
} = theme.useToken();
|
||||||
|
|
||||||
|
console.log("children", children)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ height: '100vh' }}>
|
<Layout style={{ height: '100vh' }}>
|
||||||
<LayoutSidebar />
|
<LayoutSidebar />
|
||||||
<Layout
|
<Layout
|
||||||
style={{
|
style={{
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
|
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
<LayoutHeader />
|
<LayoutHeader />
|
||||||
<Content
|
<Content
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
486
src/pages/RoomBook.jsx
Normal file
486
src/pages/RoomBook.jsx
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
import React, { memo, useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Card,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Divider,
|
||||||
|
message,
|
||||||
|
Typography,
|
||||||
|
DatePicker,
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
EyeOutlined,
|
||||||
|
ReloadOutlined,
|
||||||
|
CreditCardOutlined, // 👈 Card icon
|
||||||
|
BookOutlined, // 👈 Booking icon
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
|
||||||
|
// Mock data for room types
|
||||||
|
const roomTypes = [
|
||||||
|
{ id: 1, name: 'Standard' },
|
||||||
|
{ id: 2, name: 'Deluxe' },
|
||||||
|
{ id: 3, name: 'Suite' },
|
||||||
|
{ id: 4, name: 'Executive' },
|
||||||
|
{ id: 5, name: 'Presidential' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Initial room data (your existing initialRooms stays the same)
|
||||||
|
const initialRooms = [
|
||||||
|
{
|
||||||
|
no_kamar: '101',
|
||||||
|
id_tp_kamar: 1,
|
||||||
|
tarif: 250000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 1,
|
||||||
|
diskripsi: 'Standard Room with 1 bed',
|
||||||
|
total_bed: '1',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '1',
|
||||||
|
tp_kamar: 'Standard',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 25000,
|
||||||
|
service: 30000,
|
||||||
|
base_tarif: 195000,
|
||||||
|
tarif_traveloka: 220000,
|
||||||
|
tarif_tiket_com: 225000,
|
||||||
|
tarif_pegi_pegi: 230000,
|
||||||
|
tarif_booking_com: 240000,
|
||||||
|
tarif_phone: 250000,
|
||||||
|
status: 'ready' // ready, occupied, maintenance, cleaning
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '102',
|
||||||
|
id_tp_kamar: 1,
|
||||||
|
tarif: 250000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 1,
|
||||||
|
diskripsi: 'Standard Room with 2 beds',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '1',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Standard',
|
||||||
|
jns_kartu: 'RFID',
|
||||||
|
tax: 25000,
|
||||||
|
service: 30000,
|
||||||
|
base_tarif: 195000,
|
||||||
|
tarif_traveloka: 220000,
|
||||||
|
tarif_tiket_com: 225000,
|
||||||
|
tarif_pegi_pegi: 230000,
|
||||||
|
tarif_booking_com: 240000,
|
||||||
|
tarif_phone: 250000,
|
||||||
|
status: 'occupied'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '201',
|
||||||
|
id_tp_kamar: 2,
|
||||||
|
tarif: 400000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 2,
|
||||||
|
diskripsi: 'Deluxe Room with king size bed',
|
||||||
|
total_bed: '1',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '1',
|
||||||
|
tp_kamar: 'Deluxe',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 40000,
|
||||||
|
service: 50000,
|
||||||
|
base_tarif: 310000,
|
||||||
|
tarif_traveloka: 350000,
|
||||||
|
tarif_tiket_com: 360000,
|
||||||
|
tarif_pegi_pegi: 370000,
|
||||||
|
tarif_booking_com: 380000,
|
||||||
|
tarif_phone: 400000,
|
||||||
|
status: 'maintenance'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '202',
|
||||||
|
id_tp_kamar: 2,
|
||||||
|
tarif: 450000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 2,
|
||||||
|
diskripsi: 'Deluxe Room with ocean view',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Deluxe',
|
||||||
|
jns_kartu: 'NFC',
|
||||||
|
tax: 45000,
|
||||||
|
service: 55000,
|
||||||
|
base_tarif: 350000,
|
||||||
|
tarif_traveloka: 400000,
|
||||||
|
tarif_tiket_com: 410000,
|
||||||
|
tarif_pegi_pegi: 420000,
|
||||||
|
tarif_booking_com: 430000,
|
||||||
|
tarif_phone: 450000,
|
||||||
|
status: 'cleaning'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '301',
|
||||||
|
id_tp_kamar: 3,
|
||||||
|
tarif: 750000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 3,
|
||||||
|
diskripsi: 'Suite with living area',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '1',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Suite',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 75000,
|
||||||
|
service: 90000,
|
||||||
|
base_tarif: 585000,
|
||||||
|
tarif_traveloka: 650000,
|
||||||
|
tarif_tiket_com: 670000,
|
||||||
|
tarif_pegi_pegi: 690000,
|
||||||
|
tarif_booking_com: 710000,
|
||||||
|
tarif_phone: 750000,
|
||||||
|
status: 'ready'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const RoomBook = memo(function RoomBook(props) {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
roomNumber: "000",
|
||||||
|
name: "",
|
||||||
|
noId: "",
|
||||||
|
jenisId: "",
|
||||||
|
bookingDari: "",
|
||||||
|
bookingCode: "",
|
||||||
|
dateStart: "",
|
||||||
|
dateEnd: "",
|
||||||
|
status: "ready",
|
||||||
|
roomPrice: "500000",
|
||||||
|
});
|
||||||
|
const [rooms, setRooms] = useState(initialRooms);
|
||||||
|
const [filteredRooms, setFilteredRooms] = useState(initialRooms);
|
||||||
|
const [filterStatus, setFilterStatus] = useState('all');
|
||||||
|
|
||||||
|
const [isBookingModalVisible, setIsBookingModalVisible] = useState(false);
|
||||||
|
const [bookingRoom, setBookingRoom] = useState(null);
|
||||||
|
const [bookingForm] = Form.useForm();
|
||||||
|
|
||||||
|
// Handle filter
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterStatus === 'all') {
|
||||||
|
setFilteredRooms(rooms);
|
||||||
|
} else {
|
||||||
|
setFilteredRooms(rooms.filter(room => room.status === filterStatus));
|
||||||
|
}
|
||||||
|
}, [filterStatus, rooms]);
|
||||||
|
|
||||||
|
const handleFilterChange = (status) => {
|
||||||
|
setFilterStatus(status);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Booking handler
|
||||||
|
const handleBookingRoom = (room) => {
|
||||||
|
setBookingRoom(room);
|
||||||
|
bookingForm.setFieldsValue({
|
||||||
|
no_kamar: room.no_kamar,
|
||||||
|
room_price: room.tarif,
|
||||||
|
});
|
||||||
|
setIsBookingModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBookingSubmit = (values) => {
|
||||||
|
console.log("Booking Data:", values);
|
||||||
|
message.success(`Booking for room ${values.no_kamar} saved!`);
|
||||||
|
setIsBookingModalVisible(false);
|
||||||
|
bookingForm.resetFields();
|
||||||
|
setBookingRoom(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBookingCancel = () => {
|
||||||
|
setIsBookingModalVisible(false);
|
||||||
|
bookingForm.resetFields();
|
||||||
|
setBookingRoom(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get status color
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'ready': return 'green';
|
||||||
|
case 'occupied': return 'red';
|
||||||
|
case 'maintenance': return 'orange';
|
||||||
|
case 'cleaning': return 'purple';
|
||||||
|
default: return 'blue';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'ready': return 'Ready';
|
||||||
|
case 'occupied': return 'Occupied';
|
||||||
|
case 'maintenance': return 'Maintenance';
|
||||||
|
case 'cleaning': return 'Cleaning';
|
||||||
|
default: return 'Unknown';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<Title level={2}>Hotel Room Management</Title>
|
||||||
|
|
||||||
|
{/* Filter */}
|
||||||
|
<Card style={{ marginBottom: '24px' }}>
|
||||||
|
<Space wrap>
|
||||||
|
<Text strong>Filter by Status:</Text>
|
||||||
|
{['all', 'ready', 'occupied', 'maintenance', 'cleaning'].map(status => (
|
||||||
|
<Button
|
||||||
|
key={status}
|
||||||
|
type={filterStatus === status ? 'primary' : 'default'}
|
||||||
|
onClick={() => handleFilterChange(status)}
|
||||||
|
>
|
||||||
|
{status.charAt(0).toUpperCase() + status.slice(1)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
<Button icon={<ReloadOutlined />} onClick={() => handleFilterChange('all')}>
|
||||||
|
Reset Filter
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Room grid */}
|
||||||
|
<Row gutter={[16, 16]}>
|
||||||
|
{filteredRooms.map(room => (
|
||||||
|
<Col key={room.no_kamar} xs={12} sm={8} md={6} lg={4}>
|
||||||
|
<Card
|
||||||
|
size="small"
|
||||||
|
style={{
|
||||||
|
border: `2px solid ${getStatusColor(room.status)}`,
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
actions={[
|
||||||
|
<EyeOutlined key="view" />,
|
||||||
|
<EditOutlined key="edit" />,
|
||||||
|
<BookOutlined key="book" onClick={() => handleBookingRoom(room)} />, // 👈 Booking button
|
||||||
|
<CreditCardOutlined key="card" onClick={() => message.info('Card tapped!')} /> // 👈 Card button
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<Title level={3} style={{ margin: 0 }}>{room.no_kamar}</Title>
|
||||||
|
<Tag color={getStatusColor(room.status)}>
|
||||||
|
{getStatusText(room.status)}
|
||||||
|
</Tag>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">{room.tp_kamar}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text>Rp {room.tarif.toLocaleString('id-ID')}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{/* Booking Modal */}
|
||||||
|
<Modal
|
||||||
|
title={`Booking Room - ${bookingRoom?.no_kamar}`}
|
||||||
|
open={isBookingModalVisible}
|
||||||
|
onCancel={handleBookingCancel}
|
||||||
|
footer={null}
|
||||||
|
width={600}
|
||||||
|
>
|
||||||
|
<Form form={bookingForm} layout="vertical" onFinish={handleBookingSubmit}>
|
||||||
|
<Form.Item label="Room Number" name="no_kamar">
|
||||||
|
<Input
|
||||||
|
value={formData.roomNumber}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Guest Name"
|
||||||
|
name="name"
|
||||||
|
rules={[{ required: true, message: 'Please enter guest name!' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Jenis ID"
|
||||||
|
name="jenis_id"
|
||||||
|
rules={[{ required: true, message: 'Please enter ID type!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={formData.jenisId}
|
||||||
|
onChange={(e) => setFormData({ ...formData, jenisId: e })}
|
||||||
|
>
|
||||||
|
<Option value="">Pilih Jenis ID</Option>
|
||||||
|
<Option value="ktp">KTP</Option>
|
||||||
|
<Option value="passport">Passport</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="No ID"
|
||||||
|
name="no_id"
|
||||||
|
rules={[{ required: true, message: 'Please enter ID number!' }]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
value={formData.noId}
|
||||||
|
onChange={(e) => setFormData({ ...formData, noId: e.target.value })}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Booking Dari"
|
||||||
|
name="booking_dari"
|
||||||
|
rules={[{ required: true, message: 'Please select booking source!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={formData.bookingDari}
|
||||||
|
onChange={(e) => setFormData({ ...formData, bookingDari: e })}
|
||||||
|
>
|
||||||
|
<Option value="traveloka">Traveloka</Option>
|
||||||
|
<Option value="agoda">Agoda</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Booking Code"
|
||||||
|
name="booking_code"
|
||||||
|
rules={[{ required: true, message: 'Please enter booking code!' }]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Date Start"
|
||||||
|
name="date_start"
|
||||||
|
rules={[{ required: true, message: 'Please enter date start!' }]}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
value={formData.dateStart ? dayjs(formData.dateStart) : null}
|
||||||
|
onChange={(value) =>
|
||||||
|
setFormData({ ...formData, dateStart: value ? value.toISOString() : "" })
|
||||||
|
}
|
||||||
|
className="w-1/2"
|
||||||
|
placeholder="Date Start"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Date End"
|
||||||
|
name="date_end"
|
||||||
|
rules={[{ required: true, message: 'Please enter date end!' }]}
|
||||||
|
>
|
||||||
|
<DatePicker
|
||||||
|
showTime
|
||||||
|
value={formData.dateEnd ? dayjs(formData.dateEnd) : null}
|
||||||
|
onChange={(value) =>
|
||||||
|
setFormData({ ...formData, dateEnd: value ? value.toISOString() : "" })
|
||||||
|
}
|
||||||
|
className="w-1/2"
|
||||||
|
placeholder="Date End"
|
||||||
|
disabledDate={(current) =>
|
||||||
|
formData.dateStart ? current && current < dayjs(formData.dateStart) : false
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Form.Item
|
||||||
|
label="Status"
|
||||||
|
name="status"
|
||||||
|
rules={[{ required: true, message: 'Please select status!' }]}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
value={formData.status}
|
||||||
|
onChange={(e) => setFormData({ ...formData, status: e })}
|
||||||
|
>
|
||||||
|
<Option value="ready">Ready</Option>
|
||||||
|
<Option value="occupied">Occupied</Option>
|
||||||
|
<Option value="maintenance">Maintenance</Option>
|
||||||
|
<Option value="cleaning">Cleaning</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Room Price"
|
||||||
|
name="room_price"
|
||||||
|
rules={[{ required: true, message: 'Please enter room price!' }]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
readOnly
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" block>
|
||||||
|
Submit Booking
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default RoomBook;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Flex, Input, Form, Button, Card, Space, Image } from 'antd';
|
import { Flex, Input, Form, Button, Card, Space, Image } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import handleSignIn from '../../Utils/Auth/SignIn';
|
import handleSignIn from '../../Utils/Auth/SignIn';
|
||||||
import sypiu_ggcp from 'assets/sypiu_ggcp.jpg';
|
// import sypiu_ggcp from 'assets/sypiu_ggcp.jpg';
|
||||||
import logo from 'assets/freepik/LOGOPIU.png';
|
// import logo from 'assets/freepik/LOGOPIU.png';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { NotifAlert } from '../../components/Global/ToastNotif';
|
import { NotifAlert } from '../../components/Global/ToastNotif';
|
||||||
import { decryptData } from '../../components/Global/Formatter';
|
import { decryptData } from '../../components/Global/Formatter';
|
||||||
@@ -101,7 +101,7 @@ const SignIn = () => {
|
|||||||
height: '100vh',
|
height: '100vh',
|
||||||
// marginTop: '10vh',
|
// marginTop: '10vh',
|
||||||
// backgroundImage: `url('https://via.placeholder.com/300')`,
|
// backgroundImage: `url('https://via.placeholder.com/300')`,
|
||||||
backgroundImage: `url(${sypiu_ggcp})`,
|
// backgroundImage: `url(${sypiu_ggcp})`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
backgroundPosition: 'center',
|
backgroundPosition: 'center',
|
||||||
}}
|
}}
|
||||||
@@ -112,14 +112,14 @@ const SignIn = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex align="center" justify="center">
|
<Flex align="center" justify="center">
|
||||||
<Image
|
{/* <Image
|
||||||
// src="/src/assets/freepik/LOGOPIU.png"
|
// src="/src/assets/freepik/LOGOPIU.png"
|
||||||
src={logo}
|
src={logo}
|
||||||
height={150}
|
height={150}
|
||||||
width={220}
|
width={220}
|
||||||
preview={false}
|
preview={false}
|
||||||
alt="signin"
|
alt="signin"
|
||||||
/>
|
/> */}
|
||||||
</Flex>
|
</Flex>
|
||||||
<br />
|
<br />
|
||||||
<Form onFinish={handleOnSubmit} layout="vertical" style={{ width: '250px' }}>
|
<Form onFinish={handleOnSubmit} layout="vertical" style={{ width: '250px' }}>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Button, Typography } from 'antd';
|
import { Button, Typography } from 'antd';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import ImgRobot from '../../assets/freepik/404.png';
|
// import ImgRobot from '../../assets/freepik/404.png';
|
||||||
|
|
||||||
const { Title, Paragraph, Text } = Typography;
|
const { Title, Paragraph, Text } = Typography;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ const NotFound = () => {
|
|||||||
page.
|
page.
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
|
|
||||||
<img
|
{/* <img
|
||||||
src={ImgRobot}
|
src={ImgRobot}
|
||||||
alt="404 Not Found"
|
alt="404 Not Found"
|
||||||
style={{
|
style={{
|
||||||
@@ -50,7 +50,7 @@ const NotFound = () => {
|
|||||||
width: '480px',
|
width: '480px',
|
||||||
marginBottom: '4vh',
|
marginBottom: '4vh',
|
||||||
}}
|
}}
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<Link to="/">
|
<Link to="/">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, {useEffect, useState } from 'react';
|
import React, {useEffect, useState } from 'react';
|
||||||
import { Modal, Button, ConfigProvider } from 'antd';
|
import { Modal, Button, ConfigProvider } from 'antd';
|
||||||
import { jsPDF } from 'jspdf';
|
import { jsPDF } from 'jspdf';
|
||||||
import logoPiEnergi from '../../../../assets/images/logo/pi-energi.png';
|
// import logoPiEnergi from '../../../../assets/images/logo/pi-energi.png';
|
||||||
import { kopReportPdf } from '../../../../components/Global/KopReport';
|
import { kopReportPdf } from '../../../../components/Global/KopReport';
|
||||||
|
|
||||||
const GeneratePdf = (props) => {
|
const GeneratePdf = (props) => {
|
||||||
@@ -10,7 +10,7 @@ const GeneratePdf = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
if (token) {
|
if (token) {
|
||||||
generatePdf();
|
// generatePdf();
|
||||||
} else {
|
} else {
|
||||||
navigate('/signin');
|
navigate('/signin');
|
||||||
}
|
}
|
||||||
@@ -21,66 +21,66 @@ const GeneratePdf = (props) => {
|
|||||||
props.setActionMode('list');
|
props.setActionMode('list');
|
||||||
};
|
};
|
||||||
|
|
||||||
const generatePdf = async () => {
|
// const generatePdf = async () => {
|
||||||
const {images, title} = await kopReportPdf(logoPiEnergi, 'COLD WORK PERMIT');
|
// const {images, title} = await kopReportPdf(logoPiEnergi, 'COLD WORK PERMIT');
|
||||||
|
|
||||||
const doc = new jsPDF({
|
// const doc = new jsPDF({
|
||||||
orientation: "portrait",
|
// orientation: "portrait",
|
||||||
unit: "mm",
|
// unit: "mm",
|
||||||
format: "a4"
|
// format: "a4"
|
||||||
});
|
// });
|
||||||
|
|
||||||
const width = 45;
|
// const width = 45;
|
||||||
const height = 23;
|
// const height = 23;
|
||||||
const marginTop = 6;
|
// const marginTop = 6;
|
||||||
const marginLeft = 10;
|
// const marginLeft = 10;
|
||||||
doc.addImage(images, 'PNG', marginLeft, marginTop, width, height);
|
// doc.addImage(images, 'PNG', marginLeft, marginTop, width, height);
|
||||||
|
|
||||||
doc.setFont('helvetica', 'bold');
|
// doc.setFont('helvetica', 'bold');
|
||||||
doc.setFontSize(25);
|
// doc.setFontSize(25);
|
||||||
doc.setTextColor(35, 165, 90);
|
// doc.setTextColor(35, 165, 90);
|
||||||
doc.setTextColor('#00b0f0');
|
// doc.setTextColor('#00b0f0');
|
||||||
doc.text(title, 100, 25);
|
// doc.text(title, 100, 25);
|
||||||
doc.setTextColor('#000000');
|
// doc.setTextColor('#000000');
|
||||||
doc.setFontSize(11);
|
// doc.setFontSize(11);
|
||||||
doc.setFont('helvetica', 'normal');
|
// doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
doc.setLineWidth(0.2);
|
// doc.setLineWidth(0.2);
|
||||||
doc.line(10, 32, 200, 32);
|
// doc.line(10, 32, 200, 32);
|
||||||
doc.setLineWidth(0.6);
|
// doc.setLineWidth(0.6);
|
||||||
doc.line(10, 32.8, 200, 32.8);
|
// doc.line(10, 32.8, 200, 32.8);
|
||||||
|
|
||||||
doc.text("Tanggal Pengajuan", 10, 42);
|
// doc.text("Tanggal Pengajuan", 10, 42);
|
||||||
doc.text(":", 59, 42);
|
// doc.text(":", 59, 42);
|
||||||
|
|
||||||
doc.text("Deskripsi Pekerjaan", 10, 48);
|
// doc.text("Deskripsi Pekerjaan", 10, 48);
|
||||||
doc.text(":", 59, 48);
|
// doc.text(":", 59, 48);
|
||||||
|
|
||||||
doc.text("No. Permit", 10, 54);
|
// doc.text("No. Permit", 10, 54);
|
||||||
doc.text(":", 59, 54);
|
// doc.text(":", 59, 54);
|
||||||
doc.text("Spesifik Lokasi", 120, 54);
|
// doc.text("Spesifik Lokasi", 120, 54);
|
||||||
doc.text(":", 160, 54);
|
// doc.text(":", 160, 54);
|
||||||
|
|
||||||
doc.text("No. Order", 10, 60);
|
// doc.text("No. Order", 10, 60);
|
||||||
doc.text(":", 59, 60);
|
// doc.text(":", 59, 60);
|
||||||
doc.text("Jum. Personil Terlihat", 120, 60);
|
// doc.text("Jum. Personil Terlihat", 120, 60);
|
||||||
doc.text(":", 160, 60);
|
// doc.text(":", 160, 60);
|
||||||
|
|
||||||
doc.text("Peralatan yang digunakan", 10, 66);
|
// doc.text("Peralatan yang digunakan", 10, 66);
|
||||||
doc.text(":", 59, 66);
|
// doc.text(":", 59, 66);
|
||||||
|
|
||||||
doc.text("Jenis APD yang digunakan", 10, 72);
|
// doc.text("Jenis APD yang digunakan", 10, 72);
|
||||||
doc.text(":", 59, 72);
|
// doc.text(":", 59, 72);
|
||||||
|
|
||||||
const blob = doc.output('blob');
|
// const blob = doc.output('blob');
|
||||||
const url = URL.createObjectURL(blob);
|
// const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
setPdfUrl(url);
|
// setPdfUrl(url);
|
||||||
|
|
||||||
setTimeout(() => {
|
// setTimeout(() => {
|
||||||
URL.revokeObjectURL(url);
|
// URL.revokeObjectURL(url);
|
||||||
}, 1000);
|
// }, 1000);
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import TableList from '../../../../components/Global/TableList';
|
|||||||
import { getFilterData } from '../../../../components/Global/DataFilter';
|
import { getFilterData } from '../../../../components/Global/DataFilter';
|
||||||
import ExcelJS from 'exceljs';
|
import ExcelJS from 'exceljs';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import logoPiEnergi from '../../../../assets/images/logo/pi-energi.png';
|
// import logoPiEnergi from '../../../../assets/images/logo/pi-energi.png';
|
||||||
|
|
||||||
const columns = (items, handleClickMenu) => [
|
const columns = (items, handleClickMenu) => [
|
||||||
{
|
{
|
||||||
@@ -174,25 +174,25 @@ const ListDevice = memo(function ListDevice(props) {
|
|||||||
const sheet = workbook.addWorksheet('Data APD');
|
const sheet = workbook.addWorksheet('Data APD');
|
||||||
let rowCursor = 1;
|
let rowCursor = 1;
|
||||||
// Kop Logo PIE
|
// Kop Logo PIE
|
||||||
if (logoPiEnergi) {
|
// if (logoPiEnergi) {
|
||||||
const response = await fetch(logoPiEnergi);
|
// const response = await fetch(logoPiEnergi);
|
||||||
const blob = await response.blob();
|
// const blob = await response.blob();
|
||||||
const buffer = await blob.arrayBuffer();
|
// const buffer = await blob.arrayBuffer();
|
||||||
|
|
||||||
const imageId = workbook.addImage({
|
// const imageId = workbook.addImage({
|
||||||
buffer,
|
// buffer,
|
||||||
extension: 'png',
|
// extension: 'png',
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Tempatkan gambar di pojok atas
|
// // Tempatkan gambar di pojok atas
|
||||||
sheet.addImage(imageId, {
|
// sheet.addImage(imageId, {
|
||||||
tl: { col: 0.2, row: 0.8 },
|
// tl: { col: 0.2, row: 0.8 },
|
||||||
ext: { width: 163, height: 80 },
|
// ext: { width: 163, height: 80 },
|
||||||
});
|
// });
|
||||||
|
|
||||||
sheet.getRow(5).height = 15; // biar ada jarak ke tabel
|
// sheet.getRow(5).height = 15; // biar ada jarak ke tabel
|
||||||
rowCursor = 3;
|
// rowCursor = 3;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Tambah Judul
|
// Tambah Judul
|
||||||
const titleCell = sheet.getCell(`C${rowCursor}`);
|
const titleCell = sheet.getCell(`C${rowCursor}`);
|
||||||
|
|||||||
778
src/pages/master/room/RoomManagement.jsx
Normal file
778
src/pages/master/room/RoomManagement.jsx
Normal file
@@ -0,0 +1,778 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
Button,
|
||||||
|
Modal,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Select,
|
||||||
|
Tag,
|
||||||
|
Space,
|
||||||
|
Divider,
|
||||||
|
message,
|
||||||
|
Typography,
|
||||||
|
Card,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Popconfirm
|
||||||
|
} from 'antd';
|
||||||
|
import {
|
||||||
|
PlusOutlined,
|
||||||
|
EditOutlined,
|
||||||
|
DeleteOutlined,
|
||||||
|
EyeOutlined
|
||||||
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
const { Option } = Select;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
// Mock data for room types
|
||||||
|
const roomTypes = [
|
||||||
|
{ id: 1, name: 'Standard' },
|
||||||
|
{ id: 2, name: 'Deluxe' },
|
||||||
|
{ id: 3, name: 'Suite' },
|
||||||
|
{ id: 4, name: 'Executive' },
|
||||||
|
{ id: 5, name: 'Presidential' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Mock data for card types
|
||||||
|
const cardTypes = [
|
||||||
|
{ id: 'MIF', name: 'MIFARE' },
|
||||||
|
{ id: 'RFID', name: 'RFID' },
|
||||||
|
{ id: 'NFC', name: 'NFC' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Initial room data
|
||||||
|
const initialRooms = [
|
||||||
|
{
|
||||||
|
no_kamar: '101',
|
||||||
|
id_tp_kamar: 1,
|
||||||
|
tarif: 250000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 1,
|
||||||
|
diskripsi: 'Standard Room with 1 bed',
|
||||||
|
total_bed: '1',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '1',
|
||||||
|
tp_kamar: 'Standard',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 25000,
|
||||||
|
service: 30000,
|
||||||
|
base_tarif: 195000,
|
||||||
|
tarif_traveloka: 220000,
|
||||||
|
tarif_tiket_com: 225000,
|
||||||
|
tarif_pegi_pegi: 230000,
|
||||||
|
tarif_booking_com: 240000,
|
||||||
|
tarif_phone: 250000,
|
||||||
|
status: 'ready'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '102',
|
||||||
|
id_tp_kamar: 1,
|
||||||
|
tarif: 250000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 1,
|
||||||
|
diskripsi: 'Standard Room with 2 beds',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '1',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Standard',
|
||||||
|
jns_kartu: 'RFID',
|
||||||
|
tax: 25000,
|
||||||
|
service: 30000,
|
||||||
|
base_tarif: 195000,
|
||||||
|
tarif_traveloka: 220000,
|
||||||
|
tarif_tiket_com: 225000,
|
||||||
|
tarif_pegi_pegi: 230000,
|
||||||
|
tarif_booking_com: 240000,
|
||||||
|
tarif_phone: 250000,
|
||||||
|
status: 'occupied'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '201',
|
||||||
|
id_tp_kamar: 2,
|
||||||
|
tarif: 400000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 2,
|
||||||
|
diskripsi: 'Deluxe Room with king size bed',
|
||||||
|
total_bed: '1',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '1',
|
||||||
|
tp_kamar: 'Deluxe',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 40000,
|
||||||
|
service: 50000,
|
||||||
|
base_tarif: 310000,
|
||||||
|
tarif_traveloka: 350000,
|
||||||
|
tarif_tiket_com: 360000,
|
||||||
|
tarif_pegi_pegi: 370000,
|
||||||
|
tarif_booking_com: 380000,
|
||||||
|
tarif_phone: 400000,
|
||||||
|
status: 'maintenance'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '202',
|
||||||
|
id_tp_kamar: 2,
|
||||||
|
tarif: 450000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 2,
|
||||||
|
diskripsi: 'Deluxe Room with ocean view',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '0',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Deluxe',
|
||||||
|
jns_kartu: 'NFC',
|
||||||
|
tax: 45000,
|
||||||
|
service: 55000,
|
||||||
|
base_tarif: 350000,
|
||||||
|
tarif_traveloka: 400000,
|
||||||
|
tarif_tiket_com: 410000,
|
||||||
|
tarif_pegi_pegi: 420000,
|
||||||
|
tarif_booking_com: 430000,
|
||||||
|
tarif_phone: 450000,
|
||||||
|
status: 'cleaning'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
no_kamar: '301',
|
||||||
|
id_tp_kamar: 3,
|
||||||
|
tarif: 750000,
|
||||||
|
isdetail: '1',
|
||||||
|
lvl: 3,
|
||||||
|
diskripsi: 'Suite with living area',
|
||||||
|
total_bed: '2',
|
||||||
|
dt_ins: '2023-05-10 10:00:00',
|
||||||
|
dt_upd: '2023-05-10 10:00:00',
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin',
|
||||||
|
is_isi: '1',
|
||||||
|
id_parent: null,
|
||||||
|
no_bed: '2',
|
||||||
|
tp_kamar: 'Suite',
|
||||||
|
jns_kartu: 'MIF',
|
||||||
|
tax: 75000,
|
||||||
|
service: 90000,
|
||||||
|
base_tarif: 585000,
|
||||||
|
tarif_traveloka: 650000,
|
||||||
|
tarif_tiket_com: 670000,
|
||||||
|
tarif_pegi_pegi: 690000,
|
||||||
|
tarif_booking_com: 710000,
|
||||||
|
tarif_phone: 750000,
|
||||||
|
status: 'ready'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const RoomManagement = () => {
|
||||||
|
const [rooms, setRooms] = useState(initialRooms);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [editingRoom, setEditingRoom] = useState(null);
|
||||||
|
const [viewingRoom, setViewingRoom] = useState(null);
|
||||||
|
const [isViewModalVisible, setIsViewModalVisible] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
// Status options
|
||||||
|
const statusOptions = [
|
||||||
|
{ value: 'ready', label: 'Ready', color: 'green' },
|
||||||
|
{ value: 'occupied', label: 'Occupied', color: 'red' },
|
||||||
|
{ value: 'maintenance', label: 'Maintenance', color: 'orange' },
|
||||||
|
{ value: 'cleaning', label: 'Cleaning', color: 'purple' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
const handleFormSubmit = (values) => {
|
||||||
|
if (editingRoom) {
|
||||||
|
// Update existing room
|
||||||
|
setRooms(rooms.map(room =>
|
||||||
|
room.no_kamar === editingRoom.no_kamar
|
||||||
|
? { ...room, ...values, dt_upd: new Date().toISOString(), usr_upd: 'admin' }
|
||||||
|
: room
|
||||||
|
));
|
||||||
|
message.success('Room updated successfully');
|
||||||
|
} else {
|
||||||
|
// Add new room - check if room number already exists
|
||||||
|
if (rooms.some(room => room.no_kamar === values.no_kamar)) {
|
||||||
|
message.error('Room number already exists');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRoom = {
|
||||||
|
...values,
|
||||||
|
dt_ins: new Date().toISOString(),
|
||||||
|
dt_upd: new Date().toISOString(),
|
||||||
|
usr_ins: 'admin',
|
||||||
|
usr_upd: 'admin'
|
||||||
|
};
|
||||||
|
setRooms([...rooms, newRoom]);
|
||||||
|
message.success('Room added successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsModalVisible(false);
|
||||||
|
setEditingRoom(null);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle delete room
|
||||||
|
const handleDeleteRoom = (roomNumber) => {
|
||||||
|
setRooms(rooms.filter(room => room.no_kamar !== roomNumber));
|
||||||
|
message.success('Room deleted successfully');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle modal cancel
|
||||||
|
const handleModalCancel = () => {
|
||||||
|
setIsModalVisible(false);
|
||||||
|
setEditingRoom(null);
|
||||||
|
form.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle view modal cancel
|
||||||
|
const handleViewModalCancel = () => {
|
||||||
|
setIsViewModalVisible(false);
|
||||||
|
setViewingRoom(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get status tag color
|
||||||
|
const getStatusColor = (status) => {
|
||||||
|
const statusObj = statusOptions.find(opt => opt.value === status);
|
||||||
|
return statusObj ? statusObj.color : 'default';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get status text
|
||||||
|
const getStatusText = (status) => {
|
||||||
|
const statusObj = statusOptions.find(opt => opt.value === status);
|
||||||
|
return statusObj ? statusObj.label : 'Unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Table columns
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: 'Room Number',
|
||||||
|
dataIndex: 'no_kamar',
|
||||||
|
key: 'no_kamar',
|
||||||
|
sorter: (a, b) => a.no_kamar.localeCompare(b.no_kamar),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Type',
|
||||||
|
dataIndex: 'tp_kamar',
|
||||||
|
key: 'tp_kamar',
|
||||||
|
filters: roomTypes.map(type => ({ text: type.name, value: type.name })),
|
||||||
|
onFilter: (value, record) => record.tp_kamar === value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Description',
|
||||||
|
dataIndex: 'diskripsi',
|
||||||
|
key: 'diskripsi',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Beds',
|
||||||
|
dataIndex: 'total_bed',
|
||||||
|
key: 'total_bed',
|
||||||
|
sorter: (a, b) => a.total_bed - b.total_bed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Level',
|
||||||
|
dataIndex: 'lvl',
|
||||||
|
key: 'lvl',
|
||||||
|
sorter: (a, b) => a.lvl - b.lvl,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Tariff',
|
||||||
|
dataIndex: 'tarif',
|
||||||
|
key: 'tarif',
|
||||||
|
render: (tarif) => `Rp ${tarif.toLocaleString('id-ID')}`,
|
||||||
|
sorter: (a, b) => a.tarif - b.tarif,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Status',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status) => (
|
||||||
|
<Tag color={getStatusColor(status)}>
|
||||||
|
{getStatusText(status)}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
filters: statusOptions.map(opt => ({ text: opt.label, value: opt.value })),
|
||||||
|
onFilter: (value, record) => record.status === value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Action',
|
||||||
|
key: 'action',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space size="0">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setViewingRoom(record);
|
||||||
|
setIsViewModalVisible(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* View
|
||||||
|
</Button> */}
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingRoom(record);
|
||||||
|
form.setFieldsValue(record);
|
||||||
|
setIsModalVisible(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Edit
|
||||||
|
</Button> */}
|
||||||
|
<Popconfirm
|
||||||
|
title="Delete this room?"
|
||||||
|
description="Are you sure you want to delete this room?"
|
||||||
|
onConfirm={() => handleDeleteRoom(record.no_kamar)}
|
||||||
|
okText="Yes"
|
||||||
|
cancelText="No"
|
||||||
|
>
|
||||||
|
<Button type="link" danger icon={<DeleteOutlined />} />
|
||||||
|
{/* Delete
|
||||||
|
</Button> */}
|
||||||
|
</Popconfirm>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
||||||
|
<Title level={2}>Room Management</Title>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
setEditingRoom(null);
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalVisible(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add New Room
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={rooms}
|
||||||
|
rowKey="no_kamar"
|
||||||
|
scroll={{ x: 1000 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Add/Edit Room Modal */}
|
||||||
|
<Modal
|
||||||
|
title={editingRoom ? 'Edit Room' : 'Add New Room'}
|
||||||
|
open={isModalVisible}
|
||||||
|
onCancel={handleModalCancel}
|
||||||
|
footer={null}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
onFinish={handleFormSubmit}
|
||||||
|
>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Room Number"
|
||||||
|
name="no_kamar"
|
||||||
|
rules={[{ required: true, message: 'Please input room number!' }]}
|
||||||
|
>
|
||||||
|
<Input disabled={!!editingRoom} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Room Type"
|
||||||
|
name="id_tp_kamar"
|
||||||
|
rules={[{ required: true, message: 'Please select room type!' }]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{roomTypes.map(type => (
|
||||||
|
<Option key={type.id} value={type.id}>{type.name}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Base Tarif"
|
||||||
|
name="base_tarif"
|
||||||
|
rules={[{ required: true, message: 'Please input base tariff!' }]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Final Tarif"
|
||||||
|
name="tarif"
|
||||||
|
rules={[{ required: true, message: 'Please input final tariff!' }]}
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Tax"
|
||||||
|
name="tax"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Service Charge"
|
||||||
|
name="service"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Description"
|
||||||
|
name="diskripsi"
|
||||||
|
>
|
||||||
|
<TextArea />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label="Total Bed"
|
||||||
|
name="total_bed"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label="Bed Number"
|
||||||
|
name="no_bed"
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item
|
||||||
|
label="Level"
|
||||||
|
name="lvl"
|
||||||
|
>
|
||||||
|
<InputNumber style={{ width: '100%' }} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Card Type"
|
||||||
|
name="jns_kartu"
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
{cardTypes.map(type => (
|
||||||
|
<Option key={type.id} value={type.id}>{type.name}</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Status"
|
||||||
|
name="status"
|
||||||
|
rules={[{ required: true, message: 'Please select status!' }]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Option value="ready">Ready</Option>
|
||||||
|
<Option value="occupied">Occupied</Option>
|
||||||
|
<Option value="maintenance">Maintenance</Option>
|
||||||
|
<Option value="cleaning">Cleaning</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Is Detail"
|
||||||
|
name="isdetail"
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Option value="1">Yes</Option>
|
||||||
|
<Option value="0">No</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Is Occupied"
|
||||||
|
name="is_isi"
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Option value="1">Yes</Option>
|
||||||
|
<Option value="0">No</Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider>Online Travel Agency Rates</Divider>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Traveloka Rate"
|
||||||
|
name="tarif_traveloka"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Tiket.com Rate"
|
||||||
|
name="tarif_tiket_com"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Pegipegi Rate"
|
||||||
|
name="tarif_pegi_pegi"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Form.Item
|
||||||
|
label="Booking.com Rate"
|
||||||
|
name="tarif_booking_com"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="Phone Rate"
|
||||||
|
name="tarif_phone"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
formatter={value => `Rp ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')}
|
||||||
|
parser={value => value.replace(/Rp\s?|(,*)/g, '')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item>
|
||||||
|
<Button type="primary" htmlType="submit" style={{ marginRight: '8px' }}>
|
||||||
|
{editingRoom ? 'Update' : 'Add'} Room
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleModalCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* View Room Details Modal */}
|
||||||
|
<Modal
|
||||||
|
title={`Room Details - ${viewingRoom?.no_kamar}`}
|
||||||
|
open={isViewModalVisible}
|
||||||
|
onCancel={handleViewModalCancel}
|
||||||
|
footer={[
|
||||||
|
<Button key="close" onClick={handleViewModalCancel}>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
]}
|
||||||
|
width={700}
|
||||||
|
>
|
||||||
|
{viewingRoom && (
|
||||||
|
<div>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Room Number: </Text>
|
||||||
|
<Text>{viewingRoom.no_kamar}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Room Type: </Text>
|
||||||
|
<Text>{viewingRoom.tp_kamar}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Status: </Text>
|
||||||
|
<Tag color={getStatusColor(viewingRoom.status)}>
|
||||||
|
{getStatusText(viewingRoom.status)}
|
||||||
|
</Tag>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Level: </Text>
|
||||||
|
<Text>{viewingRoom.lvl}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Beds: </Text>
|
||||||
|
<Text>{viewingRoom.total_bed} ({viewingRoom.no_bed})</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Card Type: </Text>
|
||||||
|
<Text>{viewingRoom.jns_kartu}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Is Detail: </Text>
|
||||||
|
<Text>{viewingRoom.isdetail === '1' ? 'Yes' : 'No'}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Is Occupied: </Text>
|
||||||
|
<Text>{viewingRoom.is_isi === '1' ? 'Yes' : 'No'}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text strong>Description: </Text>
|
||||||
|
<Text>{viewingRoom.diskripsi}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider>Rates Information</Divider>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Base Tariff: </Text>
|
||||||
|
<Text>Rp {viewingRoom.base_tarif?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Final Tariff: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Tax: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tax?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Service: </Text>
|
||||||
|
<Text>Rp {viewingRoom.service?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider>Online Travel Agency Rates</Divider>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Traveloka: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif_traveloka?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Tiket.com: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif_tiket_com?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Pegipegi: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif_pegi_pegi?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Booking.com: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif_booking_com?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Phone Rate: </Text>
|
||||||
|
<Text>Rp {viewingRoom.tarif_phone?.toLocaleString('id-ID')}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Divider>Audit Information</Divider>
|
||||||
|
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Created: </Text>
|
||||||
|
<Text>{viewingRoom.dt_ins} by {viewingRoom.usr_ins}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Text strong>Last Updated: </Text>
|
||||||
|
<Text>{viewingRoom.dt_upd} by {viewingRoom.usr_upd}</Text>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoomManagement;
|
||||||
Reference in New Issue
Block a user