diff --git a/src/components/layout/navigation.layout.tsx b/src/components/layout/navigation.layout.tsx index fd10b85..20fb793 100644 --- a/src/components/layout/navigation.layout.tsx +++ b/src/components/layout/navigation.layout.tsx @@ -25,35 +25,41 @@ export default function NavigationLayout({ action, mainClassName, bgImage, - backText = 'back' + backText = "back", }: NavigationLayoutProps) { const router = useRouter(); - return (
- { bgImage &&
} + {bgImage && ( +
+ )} {title && (
{title}
)} -
+
{isBack && ( router.history.back()} className={cn( "flex justify-start shrink-0 items-center gap-2 text-base font-semibold cursor-pointer w-fit p-4", - backClassName + backClassName, )} > @@ -66,7 +72,7 @@ export default function NavigationLayout({ className={cn( "h-full relative pt-18", isBottomNav && "overflow-y-auto", - mainClassName + mainClassName, )} > {children} diff --git a/src/components/ui/bottom-navigation/bottom-navigation.tsx b/src/components/ui/bottom-navigation/bottom-navigation.tsx index da41f47..ebdd90e 100644 --- a/src/components/ui/bottom-navigation/bottom-navigation.tsx +++ b/src/components/ui/bottom-navigation/bottom-navigation.tsx @@ -1,19 +1,16 @@ import { Link, useRouterState } from "@tanstack/react-router"; -import { BarChart3, DoorOpen, Home, Settings } from "lucide-react"; +import { BarChart3, Home, ListCheck, Settings } from "lucide-react"; import { cn } from "../../../utils/classname"; - export default function BottomNavigation() { const location = useRouterState({ - select: (state) => state.location, -}) - -console.log(location.pathname) + select: (state) => state.location, + }); const tabs = [ { path: "/", icon: Home, label: "Dashboard", disabled: false }, { path: "/analysis", icon: BarChart3, label: "analysis", disabled: true }, - { path: "/rooms", icon: DoorOpen, label: "Rooms", disabled: true }, + { path: "/topics", icon: ListCheck, label: "Topics", disabled: false }, { path: "/settings", icon: Settings, label: "Settings", disabled: false }, ]; @@ -24,7 +21,11 @@ console.log(location.pathname) {tab.label} diff --git a/src/constants/env.ts b/src/constants/env.ts index 999d7d0..e751f69 100644 --- a/src/constants/env.ts +++ b/src/constants/env.ts @@ -2,4 +2,6 @@ export const ENV = { apiUrl: import.meta.env.VITE_API_URL, apiKey: import.meta.env.VITE_SIGN_KEY, secretKey: import.meta.env.VITE_SIGN_SECRET_KEY, -} \ No newline at end of file + basicUsername: import.meta.env.VITE_BASIC_AUTH_USERNAME, + basicPassword: import.meta.env.VITE_BASIC_AUTH_PASSWORD, +}; diff --git a/src/features/settings/index.tsx b/src/features/settings/index.tsx index 96dea55..25ad484 100644 --- a/src/features/settings/index.tsx +++ b/src/features/settings/index.tsx @@ -11,17 +11,15 @@ export default function SettingsFeature() { tower: "", floor: "", unit: "", + credential: "", }); const [loading, setLoading] = useState(false); -function isObjectValueDifferent( - a: MerchantForm, - b: MerchantForm -): boolean { - return (Object.keys(a) as (keyof MerchantForm)[]).some( - (key) => a[key] !== b[key] - ); -} + function isObjectValueDifferent(a: MerchantForm, b: MerchantForm): boolean { + return (Object.keys(a) as (keyof MerchantForm)[]).some( + (key) => a[key] !== b[key], + ); + } function handleChange(e: React.ChangeEvent) { const { name, value } = e.target; @@ -31,29 +29,26 @@ function isObjectValueDifferent( })); } -function handleSubmit(e: React.FormEvent) { - e.preventDefault(); + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); - const merchant = getMerchant(); + const merchant = getMerchant(); - if(merchant){ - const isDifferent = isObjectValueDifferent(merchant, form); - if (isDifferent) { - clearDevice(); // reset device kalau merchant berubah + if (merchant) { + const isDifferent = isObjectValueDifferent(merchant, form); + if (isDifferent) { + clearDevice(); // reset device kalau merchant berubah + } } + setLoading(true); + saveMerchant(form); + + setTimeout(() => { + setLoading(false); + navigate({ to: "/" }); + }, 200); } - - - - setLoading(true); - saveMerchant(form); - - setTimeout(() => { - setLoading(false); - navigate({ to: "/" }); - }, 200); -} useEffect(() => { const merchant = getMerchant(); @@ -119,6 +114,18 @@ function handleSubmit(e: React.FormEvent) { className="p-2 border border-neutral-200 w-full rounded-md" />
+ +
+ + +
diff --git a/src/features/topics/hooks/queries.ts b/src/features/topics/hooks/queries.ts new file mode 100644 index 0000000..2fb83b5 --- /dev/null +++ b/src/features/topics/hooks/queries.ts @@ -0,0 +1,14 @@ +import { useQuery } from "@tanstack/react-query"; +import { getTopicsState } from "../../../repositories/device"; + +export function useTopics() { + const result = useQuery({ + queryKey: ["topics/state"], + queryFn: () => getTopicsState(), + }); + + return { + ...result, + data: result?.data?.data, + }; +} diff --git a/src/features/topics/index.tsx b/src/features/topics/index.tsx new file mode 100644 index 0000000..baa5389 --- /dev/null +++ b/src/features/topics/index.tsx @@ -0,0 +1,28 @@ +import { Edit, PlusIcon, Trash2 } from "lucide-react"; +import { useTopics } from "./hooks/queries"; + +export default function TopicsFeature() { + const { data } = useTopics(); + return ( +
+
+ +
+ {data?.map((item, key) => ( +
+

{item.topic}

+
+ + +
+
+ ))} +
+ ); +} diff --git a/src/pages/topics/index.tsx b/src/pages/topics/index.tsx new file mode 100644 index 0000000..fd9aecb --- /dev/null +++ b/src/pages/topics/index.tsx @@ -0,0 +1,15 @@ +import NavigationLayout from "../../components/layout/navigation.layout"; +import TopicsFeature from "../../features/topics"; + +export default function SettingsPage() { + return ( + + + + ); +} diff --git a/src/repositories/device/index.ts b/src/repositories/device/index.ts index 4e6d75a..8bfb7c0 100644 --- a/src/repositories/device/index.ts +++ b/src/repositories/device/index.ts @@ -1,11 +1,27 @@ +import { ENV } from "../../constants/env"; import api from "../../utils/axios"; +import { basicAuth } from "../../utils/basic-auth"; -export const getDeviceStatus = async (params: DeviceParams): Promise => { - const res = await api.get('/device/v1/status', { params }); +export const getDeviceStatus = async ( + params: DeviceParams, +): Promise => { + const res = await api.get("/device/v1/status", { params }); return res.data; }; -export const postCommandStatus = async (payload: DevicePayload): Promise => { - const res = await api.post('/device/v1/command', payload); +export const postCommandStatus = async ( + payload: DevicePayload, +): Promise => { + const res = await api.post("/device/v1/command", payload); return res.data; -}; \ No newline at end of file +}; + +export const getTopicsState = async (): Promise => { + const res = await api.get("/topics/v1/commands", { + headers: { + Authorization: basicAuth(ENV.basicUsername, ENV.basicPassword), + }, + }); + + return res.data; +}; diff --git a/src/repositories/device/types.d.ts b/src/repositories/device/types.d.ts index 1fd10ae..e3eb95e 100644 --- a/src/repositories/device/types.d.ts +++ b/src/repositories/device/types.d.ts @@ -5,21 +5,21 @@ type Device = { }; type DeviceData = { - id?: string, - floorName?: string; - unitNumber?: string; - deviceName?: string; - roomName?: string; - deviceType?: string; - code?: string; - towerNumber?: string; - payload?: string; - deviceLabel?: string - deviceName?: string - active?: boolean - status?: boolean - refetch?: () => void - }; + id?: string; + floorName?: string; + unitNumber?: string; + deviceName?: string; + roomName?: string; + deviceType?: string; + code?: string; + towerNumber?: string; + payload?: string; + deviceLabel?: string; + deviceName?: string; + active?: boolean; + status?: boolean; + refetch?: () => void; +}; type DeviceParams = { merchantName?: string; @@ -33,15 +33,21 @@ type DeviceParams = { }; type DevicePayload = { - commandType?: string, - deviceName?: string, - deviceType?: string, - floorName?: string, - merchantName?: string, - payload?: { - action?: string - }, - roomName?: string, - towerNumber?: string, - unitNumber?: string, -} \ No newline at end of file + commandType?: string; + deviceName?: string; + deviceType?: string; + floorName?: string; + merchantName?: string; + payload?: { + action?: string; + }; + roomName?: string; + towerNumber?: string; + unitNumber?: string; +}; + +type TopicData = { + status: string; + message: string; + data: { topic: string }[]; +}; diff --git a/src/router/app-navigation.route.tsx b/src/router/app-navigation.route.tsx index 8236029..339eabe 100644 --- a/src/router/app-navigation.route.tsx +++ b/src/router/app-navigation.route.tsx @@ -5,7 +5,7 @@ export const AppNavigationRoute = createRoute({ getParentRoute: () => RootRoute, id: "app", component: lazyRouteComponent( - () => import("../components/layout/main-navigation.layout") + () => import("../components/layout/main-navigation.layout"), ), }); @@ -20,5 +20,3 @@ export const AnalysisRoute = createRoute({ path: "/analysis", component: lazyRouteComponent(() => import("../pages/analysis")), }); - - diff --git a/src/router/app.route.ts b/src/router/app.route.ts index db7db65..71dbe87 100644 --- a/src/router/app.route.ts +++ b/src/router/app.route.ts @@ -5,7 +5,7 @@ export const AppRoute = createRoute({ getParentRoute: () => RootRoute, id: "general", component: lazyRouteComponent( - () => import("../components/layout/main.layout") + () => import("../components/layout/main.layout"), ), }); @@ -31,4 +31,10 @@ export const SettingsRoute = createRoute({ getParentRoute: () => AppRoute, path: "/settings", component: lazyRouteComponent(() => import("../pages/settings")), -}); \ No newline at end of file +}); + +export const TopicsRoute = createRoute({ + getParentRoute: () => AppRoute, + path: "/topics", + component: lazyRouteComponent(() => import("../pages/topics")), +}); diff --git a/src/router/index.ts b/src/router/index.ts index 3424cc6..04139cb 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -12,6 +12,7 @@ import { DetailDeviceRoute, RoomsRoute, SettingsRoute, + TopicsRoute, } from "./app.route"; const routeTree = RootRoute.addChildren([ @@ -22,6 +23,7 @@ const routeTree = RootRoute.addChildren([ RoomsRoute, DetailDeviceRoute, SettingsRoute, + TopicsRoute, ]), ]); diff --git a/src/types/merchant.d.ts b/src/types/merchant.d.ts index 7fcb9d0..313d2b8 100644 --- a/src/types/merchant.d.ts +++ b/src/types/merchant.d.ts @@ -3,4 +3,5 @@ type MerchantForm = { tower: string; floor: string; unit: string; + credential: string; }; diff --git a/src/utils/axios.ts b/src/utils/axios.ts index b36f510..a26c786 100644 --- a/src/utils/axios.ts +++ b/src/utils/axios.ts @@ -6,8 +6,7 @@ import { ENV } from "../constants/env"; import type { InternalAxiosRequestConfig } from "axios"; -interface SignedAxiosRequestConfig - extends InternalAxiosRequestConfig { +interface SignedAxiosRequestConfig extends InternalAxiosRequestConfig { signed?: boolean; } @@ -16,7 +15,6 @@ const api = axios.create({ timeout: 15000, }); - api.interceptors.request.use( (config: SignedAxiosRequestConfig) => { if (config.signed === false) { @@ -50,10 +48,9 @@ api.interceptors.request.use( return config; }, - (error) => Promise.reject(error) + (error) => Promise.reject(error), ); - // RESPONSE INTERCEPTOR (Optional) api.interceptors.response.use( (response) => response, @@ -62,7 +59,7 @@ api.interceptors.response.use( console.warn("Unauthorized"); } return Promise.reject(error); - } + }, ); export default api; diff --git a/src/utils/basic-auth.ts b/src/utils/basic-auth.ts new file mode 100644 index 0000000..14faf24 --- /dev/null +++ b/src/utils/basic-auth.ts @@ -0,0 +1,3 @@ +export const basicAuth = (username: string, password: string) => { + return "Basic " + btoa(`${username}:${password}`); +};