fix setup form
This commit is contained in:
@@ -7,6 +7,9 @@ const queryClient = new QueryClient({
|
|||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
retry: false,
|
retry: false,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
refetchOnReconnect: false,
|
||||||
|
refetchOnMount: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ export default function BottomNavigation() {
|
|||||||
console.log(location.pathname)
|
console.log(location.pathname)
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ path: "/", icon: Home, label: "Dashboard" },
|
{ path: "/", icon: Home, label: "Dashboard", disabled: false },
|
||||||
{ path: "/analysis", icon: BarChart3, label: "analysis" },
|
{ path: "/analysis", icon: BarChart3, label: "analysis", disabled: true },
|
||||||
{ path: "/rooms", icon: DoorOpen, label: "Rooms" },
|
{ path: "/rooms", icon: DoorOpen, label: "Rooms", disabled: true },
|
||||||
{ path: "/settings", icon: Settings, label: "Settings" },
|
{ path: "/settings", icon: Settings, label: "Settings", disabled: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -24,7 +24,7 @@ console.log(location.pathname)
|
|||||||
<Link
|
<Link
|
||||||
key={key}
|
key={key}
|
||||||
to={tab.path}
|
to={tab.path}
|
||||||
className={cn('flex-1 py-4 flex flex-col items-center gap-1 transition-colors text-neutral-300', tab.path === location.pathname && 'text-neutral-900')}
|
className={cn('flex-1 py-4 flex flex-col items-center gap-1 transition-colors text-neutral-900 disabled:text-neutral-300', tab.path === location.pathname && 'text-orange-500', tab.disabled && 'pointer-events-none text-neutral-300')}
|
||||||
>
|
>
|
||||||
<tab.icon size={24} />
|
<tab.icon size={24} />
|
||||||
<span className="text-xs font-medium">{tab.label}</span>
|
<span className="text-xs font-medium">{tab.label}</span>
|
||||||
|
|||||||
@@ -2,27 +2,30 @@ import { useState } from "react";
|
|||||||
import {
|
import {
|
||||||
getDevices,
|
getDevices,
|
||||||
saveDevices,
|
saveDevices,
|
||||||
isDuplicateDevice,
|
getMerchant,
|
||||||
} from "../../../utils/storage";
|
} from "../../../utils/storage";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
const initialState: DeviceData = {
|
|
||||||
towerNumber: "",
|
|
||||||
floorName: "",
|
|
||||||
unitNumber: "",
|
|
||||||
roomName: "",
|
|
||||||
deviceName: "",
|
|
||||||
deviceType: "",
|
|
||||||
code: "",
|
|
||||||
payload: "",
|
|
||||||
active: true,
|
|
||||||
status: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AddDeviceFeature() {
|
export default function AddDeviceFeature() {
|
||||||
const [form, setForm] = useState<DeviceData>(initialState);
|
const [form, setForm] = useState<DeviceData | null >(null);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const merchant = getMerchant();
|
||||||
|
|
||||||
|
const isMerchantValid =
|
||||||
|
merchant?.merchantName &&
|
||||||
|
merchant?.tower &&
|
||||||
|
merchant?.floor &&
|
||||||
|
merchant?.unit;
|
||||||
|
|
||||||
|
const isFormValid =
|
||||||
|
form?.roomName?.trim() &&
|
||||||
|
form?.deviceName?.trim() &&
|
||||||
|
form?.deviceType?.trim() &&
|
||||||
|
form?.deviceLabel?.trim();
|
||||||
|
|
||||||
function handleChange(
|
function handleChange(
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
||||||
) {
|
) {
|
||||||
@@ -32,116 +35,104 @@ export default function AddDeviceFeature() {
|
|||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!isMerchantValid || !isFormValid) return;
|
||||||
|
|
||||||
const devices = getDevices();
|
const devices = getDevices();
|
||||||
|
|
||||||
const deviceToSave: DeviceData = {
|
const deviceToSave: DeviceData = {
|
||||||
...form,
|
...form,
|
||||||
deviceLabel: form.deviceLabel || `${form.roomName} ${form.deviceName}`,
|
towerNumber: merchant.tower,
|
||||||
|
floorName: merchant.floor,
|
||||||
|
unitNumber: merchant.unit,
|
||||||
|
deviceLabel: form?.deviceLabel,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isDuplicateDevice(devices, deviceToSave)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveDevices([...devices, deviceToSave]);
|
saveDevices([...devices, deviceToSave]);
|
||||||
setForm(initialState);
|
setForm(null);
|
||||||
navigate({ to: '/' });
|
navigate({ to: "/" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="p-4">
|
<form onSubmit={handleSubmit} className="p-4">
|
||||||
<h2 className="text-lg font-semibold mb-4">Add Device</h2>
|
<h2 className="text-lg font-semibold mb-4">Add Device</h2>
|
||||||
|
|
||||||
|
{!isMerchantValid && (
|
||||||
|
<p className="text-sm text-red-500 mb-4">
|
||||||
|
Lengkapi data merchant (tower, floor, unit) terlebih dahulu
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div>
|
{/* ROOM */}
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
|
||||||
Tower
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="towerNumber"
|
|
||||||
value={form.towerNumber}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
|
||||||
Floor
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="floorName"
|
|
||||||
value={form.floorName}
|
|
||||||
onChange={handleChange}
|
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
Room
|
Room
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="roomName"
|
name="roomName"
|
||||||
value={form.roomName}
|
placeholder="Room Name"
|
||||||
onChange={handleChange}
|
value={form?.roomName}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
|
||||||
Unit
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
name="unitNumber"
|
|
||||||
value={form.unitNumber}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* DEVICE NAME */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
Device Name
|
Device Name
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="deviceName"
|
name="deviceName"
|
||||||
value={form.deviceName}
|
placeholder="Device Name"
|
||||||
|
value={form?.deviceName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* DEVICE TYPE */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
Device Type
|
Device Type
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="deviceType"
|
name="deviceType"
|
||||||
value={form.deviceType}
|
placeholder="Device Type"
|
||||||
|
value={form?.deviceType}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* DEVICE LABEL */}
|
||||||
<div>
|
<div>
|
||||||
<label className="mb-1 text-neutral-900 block font-semibold">
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
Device Label
|
Device Label
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="deviceLabel"
|
name="deviceLabel"
|
||||||
value={form.deviceLabel}
|
value={form?.deviceLabel}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
required
|
placeholder={`${form?.roomName || "Room"} ${form?.deviceName || "Device"}`}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-orange-500 text-white py-2 rounded-lg mt-8"
|
disabled={!isMerchantValid || !isFormValid}
|
||||||
|
className="
|
||||||
|
w-full py-2 rounded-lg mt-8 text-white cursor-pointer
|
||||||
|
bg-orange-500
|
||||||
|
disabled:bg-neutral-400
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
"
|
||||||
>
|
>
|
||||||
Save Device
|
Save Device
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,32 +1,36 @@
|
|||||||
import { useQueries } from "@tanstack/react-query";
|
import { useQueries } from "@tanstack/react-query";
|
||||||
import { getDeviceStatus } from "../../../repositories/device";
|
import { getDeviceStatus } from "../../../repositories/device";
|
||||||
import { DEVICES_DATA } from "../../../utils/data";
|
|
||||||
import { getDevices, getMerchant } from "../../../utils/storage";
|
import { getDevices, getMerchant } from "../../../utils/storage";
|
||||||
|
|
||||||
export function useDevices() {
|
export function useDevices() {
|
||||||
|
const merchant = getMerchant();
|
||||||
const dataStorage = getDevices();
|
const dataStorage = getDevices();
|
||||||
|
|
||||||
const datas = [...DEVICES_DATA, ...dataStorage];
|
const datas = [...dataStorage];
|
||||||
|
|
||||||
const queries = useQueries({
|
const queries = useQueries({
|
||||||
queries: datas.map((device) => ({
|
queries: datas.map((device) => ({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
"device-status",
|
"device-status",
|
||||||
device.code,
|
merchant?.merchantName,
|
||||||
|
device.towerNumber,
|
||||||
|
device.floorName,
|
||||||
|
device.unitNumber,
|
||||||
|
device.deviceName,
|
||||||
device.roomName,
|
device.roomName,
|
||||||
device.deviceType,
|
device.deviceType,
|
||||||
|
"S",
|
||||||
],
|
],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const merchant = getMerchant();
|
|
||||||
const params = {
|
const params = {
|
||||||
merchantName: merchant,
|
merchantName: merchant?.merchantName,
|
||||||
|
towerNumber: device.towerNumber,
|
||||||
floorName: device.floorName,
|
floorName: device.floorName,
|
||||||
unitNumber: device.unitNumber,
|
unitNumber: device.unitNumber,
|
||||||
deviceName: device.deviceName,
|
deviceName: device.deviceName,
|
||||||
roomName: device.roomName,
|
roomName: device.roomName,
|
||||||
deviceType: device.deviceType,
|
deviceType: device.deviceType,
|
||||||
commandType: "S",
|
commandType: "S",
|
||||||
towerNumber: device.towerNumber,
|
|
||||||
};
|
};
|
||||||
const data = await getDeviceStatus(params);
|
const data = await getDeviceStatus(params);
|
||||||
|
|
||||||
@@ -34,28 +38,28 @@ export function useDevices() {
|
|||||||
...device,
|
...device,
|
||||||
code: data.data.code,
|
code: data.data.code,
|
||||||
deviceName: device.deviceName,
|
deviceName: device.deviceName,
|
||||||
|
payload: data.data.payload,
|
||||||
status:
|
status:
|
||||||
data.data.payload?.toLowerCase().includes("on") ||
|
data.data.payload?.toLowerCase().includes("on") ||
|
||||||
data.data.payload?.toLowerCase().includes("lock") ||
|
data.data.payload?.toLowerCase().includes("lock") ||
|
||||||
data.data.payload?.toLowerCase().includes("Open")
|
data.data.payload?.toLowerCase().includes("open")
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
} as unknown as Device;
|
} as unknown as Device;
|
||||||
},
|
},
|
||||||
staleTime: 10_000,
|
|
||||||
retry: 1,
|
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
const devices = queries
|
const devices = queries
|
||||||
.map((q, index) => ({
|
.map((q, index) => ({
|
||||||
...q.data,
|
...q.data,
|
||||||
isLoading: q.isLoading || q.isFetching,
|
isLoading: q.isLoading || q.isFetching,
|
||||||
isError: q.isError,
|
isError: q.isError,
|
||||||
refetch: q.refetch, // 👈 per device
|
refetch: q.refetch, // 👈 per device
|
||||||
key: datas[index].code,
|
key: datas[index].code,
|
||||||
}))
|
}))
|
||||||
.filter((d) => !d?.isError);
|
.filter((d) => !d?.isError);
|
||||||
|
console.log(devices);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
data: devices as unknown as DeviceData[],
|
data: devices as unknown as DeviceData[],
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
import { Lightbulb, Minus, Plus, SpeakerIcon, Tv, Wind } from "lucide-react";
|
import {
|
||||||
|
Blinds,
|
||||||
|
ChefHat,
|
||||||
|
DoorClosed,
|
||||||
|
Fan,
|
||||||
|
Lightbulb,
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
Wind,
|
||||||
|
WindArrowDown,
|
||||||
|
} from "lucide-react";
|
||||||
import { Switch } from "../../../components/ui/switch";
|
import { Switch } from "../../../components/ui/switch";
|
||||||
import { useNavigate } from "@tanstack/react-router";
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
import Card from "../../../components/ui/card";
|
import Card from "../../../components/ui/card";
|
||||||
import { useDevices } from "../hooks/queries";
|
import { useDevices } from "../hooks/queries";
|
||||||
import { getMerchant } from "../../../utils/storage";
|
import { getMerchant } from "../../../utils/storage";
|
||||||
import { useDeviceCommand } from "../hooks/mutations";
|
import { useDeviceCommand } from "../hooks/mutations";
|
||||||
|
|
||||||
export default function Devices() {
|
export default function Devices() {
|
||||||
@@ -15,17 +25,16 @@ export default function Devices() {
|
|||||||
const { isPending, mutate } = useDeviceCommand();
|
const { isPending, mutate } = useDeviceCommand();
|
||||||
|
|
||||||
const onSubmit = (payload: DeviceData, action = "") => {
|
const onSubmit = (payload: DeviceData, action = "") => {
|
||||||
|
|
||||||
mutate(
|
mutate(
|
||||||
{
|
{
|
||||||
|
towerNumber: payload.towerNumber,
|
||||||
|
merchantName: merchant?.merchantName,
|
||||||
commandType: "C",
|
commandType: "C",
|
||||||
deviceName: payload.deviceName,
|
deviceName: payload.deviceName,
|
||||||
deviceType: payload.deviceType,
|
deviceType: payload.deviceType,
|
||||||
floorName: payload.floorName,
|
floorName: payload.floorName,
|
||||||
merchantName: merchant,
|
|
||||||
payload: { action },
|
payload: { action },
|
||||||
roomName: payload.roomName,
|
roomName: payload.roomName,
|
||||||
towerNumber: payload.towerNumber,
|
|
||||||
unitNumber: payload.unitNumber,
|
unitNumber: payload.unitNumber,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -35,20 +44,20 @@ export default function Devices() {
|
|||||||
onError: () => {
|
onError: () => {
|
||||||
payload?.refetch?.();
|
payload?.refetch?.();
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const icons = [
|
const icons = {
|
||||||
{ icon: <Lightbulb size={24} />, label: "light" },
|
L: { icon: <Lightbulb size={24} />, label: "Lampu" },
|
||||||
{ icon: <Tv size={24} />, label: "tv" },
|
BL: { icon: <Blinds size={24} />, label: "Blind" },
|
||||||
{ icon: <SpeakerIcon size={24} />, label: "power" },
|
AC: { icon: <Wind size={24} />, label: "AC" },
|
||||||
{ icon: <Wind size={24} />, label: "ac" },
|
DL: { icon: <DoorClosed size={24} />, label: "Doorlock" },
|
||||||
{ icon: <Tv size={24} />, label: "device" },
|
ERV: { icon: <Fan size={24} />, label: "ERV" },
|
||||||
];
|
PM25: { icon: <WindArrowDown size={24} />, label: "Sensor udara" },
|
||||||
|
KC: { icon: <ChefHat size={24} />, label: "Kitchen Appliances" },
|
||||||
|
FT1: { icon: <WindArrowDown size={24} />, label: "IAQ" },
|
||||||
// console.log(data);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -56,7 +65,7 @@ export default function Devices() {
|
|||||||
<h1 className="text-base text-neutral-900 font-bold mb-4">
|
<h1 className="text-base text-neutral-900 font-bold mb-4">
|
||||||
Your Devices
|
Your Devices
|
||||||
</h1>
|
</h1>
|
||||||
<div className="flex items-center gap-3 flex-wrap justify-around">
|
<div className="flex items-center gap-3 flex-wrap justify-start">
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate({
|
navigate({
|
||||||
@@ -67,31 +76,58 @@ export default function Devices() {
|
|||||||
>
|
>
|
||||||
<Plus size={24} />
|
<Plus size={24} />
|
||||||
</button>
|
</button>
|
||||||
{icons.map((item, idx) => (
|
{/* {icons.map((item, idx) => (
|
||||||
<button
|
<button
|
||||||
key={idx}
|
key={idx}
|
||||||
className="shrink-0 w-12 h-12 rounded-full flex items-center justify-center transition-all bg-white text-gray-700 hover:bg-gray-200"
|
className="shrink-0 w-12 h-12 rounded-full flex items-center justify-center transition-all bg-white text-gray-700 hover:bg-gray-200"
|
||||||
>
|
>
|
||||||
{item.icon}
|
{item.icon}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-4 px-6">
|
<div className="py-4 px-6">
|
||||||
<div className="grid grid-cols-2 auto-rows-fr gap-4 grid-flow-dense">
|
<div className="grid grid-cols-2 auto-rows-fr gap-4 grid-flow-dense">
|
||||||
|
{isLoading && (
|
||||||
|
<>
|
||||||
|
{[...Array(4)].map((item) => (
|
||||||
|
<Card className={`animate-pulse `} key={item}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-start justify-between mb-3">
|
||||||
|
<div className="h-12 w-12 rounded-full bg-gray-200" />
|
||||||
|
<div className="h-6 w-10 rounded-full bg-gray-200" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<div className="h-4 w-32 bg-gray-200 rounded mb-2" />
|
||||||
|
|
||||||
|
{/* Device */}
|
||||||
|
<div className="h-3 w-40 bg-gray-200 rounded mb-2" />
|
||||||
|
|
||||||
|
{/* Status */}
|
||||||
|
<div className="h-3 w-24 bg-gray-200 rounded mb-2" />
|
||||||
|
|
||||||
|
{/* Location */}
|
||||||
|
<div className="h-3 w-full bg-gray-200 rounded mb-4" />
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
data?.map((item, index) => (
|
data?.map((item, index) => (
|
||||||
<Card
|
<Card
|
||||||
key={index}
|
key={index}
|
||||||
className={`${
|
className={`${
|
||||||
item.deviceName === "AC" && "row-span-2 col-span-1"
|
item.deviceName === "AC" && "row-span-2 col-span-1"
|
||||||
}`}
|
} ${item.status && "bg-orange-100 border-orange-500"}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-3">
|
<div className="flex items-start justify-between mb-3">
|
||||||
<div className="h-12 w-12 bg-neutral-100 rounded-full flex justify-center items-center">
|
<div
|
||||||
icon
|
className={`h-12 w-12 rounded-full flex justify-center items-center ${item.status ? "bg-orange-500 text-white" : "bg-gray-200 text-gray-700"}`}
|
||||||
|
>
|
||||||
|
{icons[item.deviceName as keyof typeof icons]?.icon}
|
||||||
</div>
|
</div>
|
||||||
{!["FT1"].includes(item.deviceName) && (
|
{!["FT1"].includes(item.deviceName ?? "") && (
|
||||||
<Switch
|
<Switch
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
defaultChecked={item.status}
|
defaultChecked={item.status}
|
||||||
@@ -109,11 +145,20 @@ export default function Devices() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="font-semibold text-gray-800 text-sm mb-1">
|
<h1 className="font-semibold text-gray-800 text-sm">
|
||||||
{item?.deviceLabel}
|
{item?.deviceLabel}
|
||||||
</h1>
|
</h1>
|
||||||
|
<p className="text-xs font-medium text-gray-700 mb-2">
|
||||||
|
Device:{" "}
|
||||||
|
{icons[item.deviceName as keyof typeof icons]?.label ??
|
||||||
|
item.deviceName}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs font-medium text-gray-700 mb-2">
|
||||||
|
Status: {item?.payload?.toString()}
|
||||||
|
</p>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
{item?.towerNumber}/{item?.floorName}/{item?.roomName}
|
{item?.towerNumber}/{item?.floorName}/{item?.unitNumber}/
|
||||||
|
{item?.roomName}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{item?.deviceName === "AC" && (
|
{item?.deviceName === "AC" && (
|
||||||
|
|||||||
@@ -4,32 +4,56 @@ import {
|
|||||||
saveMerchant,
|
saveMerchant,
|
||||||
getMerchant,
|
getMerchant,
|
||||||
} from "../../utils/storage";
|
} from "../../utils/storage";
|
||||||
|
import { useNavigate } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
|
||||||
export default function SettingsFeature() {
|
export default function SettingsFeature() {
|
||||||
const [form, setForm] = useState<string>('');
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [form, setForm] = useState<MerchantForm>({
|
||||||
|
merchantName: "",
|
||||||
|
tower: "",
|
||||||
|
floor: "",
|
||||||
|
unit: "",
|
||||||
|
});
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const merchant = getMerchant();
|
|
||||||
|
|
||||||
function handleChange(
|
function handleChange(
|
||||||
e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
) {
|
) {
|
||||||
const { value } = e.target;
|
const { name, value } = e.target;
|
||||||
setForm(value);
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent) {
|
function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
if (!isFormValid) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
saveMerchant(form)
|
saveMerchant(form);
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
navigate({ to: "/" });
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setForm(merchant || 'SAVY');
|
const merchant = getMerchant();
|
||||||
},[merchant]);
|
if (merchant) {
|
||||||
|
setForm(merchant);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isFormValid =
|
||||||
|
form.merchantName?.trim() &&
|
||||||
|
form.tower?.trim() &&
|
||||||
|
form.floor?.trim() &&
|
||||||
|
form.unit?.trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className="p-4">
|
<form onSubmit={handleSubmit} className="p-4">
|
||||||
@@ -40,21 +64,62 @@ export default function SettingsFeature() {
|
|||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
name="merchantName"
|
name="merchantName"
|
||||||
value={form}
|
value={form.merchantName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="p-2 border border-neutral-200 w-full rounded-md"
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
required
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
|
Tower
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="tower"
|
||||||
|
value={form.tower}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
|
Floor
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="floor"
|
||||||
|
value={form.floor}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="mb-1 text-neutral-900 block font-semibold">
|
||||||
|
Unit
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="unit"
|
||||||
|
value={form.unit}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="p-2 border border-neutral-200 w-full rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="w-full bg-orange-500 text-white py-2 rounded-lg mt-8 disabled:bg-neutral-500"
|
disabled={!isFormValid || loading}
|
||||||
disabled={loading}
|
className="
|
||||||
|
w-full py-2 rounded-lg mt-8 text-white
|
||||||
|
bg-orange-500
|
||||||
|
disabled:bg-neutral-400
|
||||||
|
disabled:cursor-not-allowed
|
||||||
|
"
|
||||||
>
|
>
|
||||||
Save Device
|
{loading ? "Saving..." : "Save Device"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
|||||||
50
src/repositories/device/types.d.ts
vendored
50
src/repositories/device/types.d.ts
vendored
@@ -5,13 +5,13 @@ type Device = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type DeviceData = {
|
type DeviceData = {
|
||||||
floorName: string;
|
floorName?: string;
|
||||||
unitNumber: string;
|
unitNumber?: string;
|
||||||
deviceName: string;
|
deviceName?: string;
|
||||||
roomName: string;
|
roomName?: string;
|
||||||
deviceType: string;
|
deviceType?: string;
|
||||||
code: string;
|
code?: string;
|
||||||
towerNumber: string;
|
towerNumber?: string;
|
||||||
payload?: string;
|
payload?: string;
|
||||||
deviceLabel?: string
|
deviceLabel?: string
|
||||||
deviceName?: string
|
deviceName?: string
|
||||||
@@ -21,26 +21,26 @@ type DeviceData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type DeviceParams = {
|
type DeviceParams = {
|
||||||
merchantName: string;
|
merchantName?: string;
|
||||||
floorName: string;
|
floorName?: string;
|
||||||
unitNumber: string;
|
unitNumber?: string;
|
||||||
deviceName: string;
|
deviceName?: string;
|
||||||
roomName: string;
|
roomName?: string;
|
||||||
deviceType: string;
|
deviceType?: string;
|
||||||
commandType: string;
|
commandType?: string;
|
||||||
towerNumber: string;
|
towerNumber?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DevicePayload = {
|
type DevicePayload = {
|
||||||
commandType: string,
|
commandType?: string,
|
||||||
deviceName: string,
|
deviceName?: string,
|
||||||
deviceType: string,
|
deviceType?: string,
|
||||||
floorName: string,
|
floorName?: string,
|
||||||
merchantName: string,
|
merchantName?: string,
|
||||||
payload: {
|
payload?: {
|
||||||
action: string
|
action?: string
|
||||||
},
|
},
|
||||||
roomName: string,
|
roomName?: string,
|
||||||
towerNumber: string,
|
towerNumber?: string,
|
||||||
unitNumber: string,
|
unitNumber?: string,
|
||||||
}
|
}
|
||||||
6
src/types/merchant.d.ts
vendored
Normal file
6
src/types/merchant.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
type MerchantForm = {
|
||||||
|
merchantName: string;
|
||||||
|
tower: string;
|
||||||
|
floor: string;
|
||||||
|
unit: string;
|
||||||
|
};
|
||||||
@@ -28,14 +28,14 @@ export function isDuplicateDevice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getMerchant(): string {
|
export function getMerchant(): MerchantForm | null {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(localStorage.getItem(MERCHANT_KEY) || "");
|
return JSON.parse(localStorage.getItem(MERCHANT_KEY) || "");
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveMerchant(val: string) {
|
export function saveMerchant(val: MerchantForm) {
|
||||||
localStorage.setItem(MERCHANT_KEY, JSON.stringify(val));
|
localStorage.setItem(MERCHANT_KEY, JSON.stringify(val));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user