diff options
Diffstat (limited to 'src/common')
| -rw-r--r-- | src/common/components/Authenticated/index.tsx | 14 | ||||
| -rw-r--r-- | src/common/components/Scanner/index.tsx | 39 | ||||
| -rw-r--r-- | src/common/components/Scanner/scanner.module.css | 16 | ||||
| -rw-r--r-- | src/common/components/ScreenContainer/index.tsx | 15 | ||||
| -rw-r--r-- | src/common/components/ScreenContainer/screen-container.module.css | 3 | ||||
| -rw-r--r-- | src/common/constants/team.ts | 13 | ||||
| -rw-r--r-- | src/common/contexts/QueryProvider.tsx | 20 | ||||
| -rw-r--r-- | src/common/contexts/UIProvider.tsx | 17 | ||||
| -rw-r--r-- | src/common/libs/authenticate.ts | 22 | ||||
| -rw-r--r-- | src/common/libs/clsxm.ts | 6 | ||||
| -rw-r--r-- | src/common/libs/toast.tsx | 23 | ||||
| -rw-r--r-- | src/common/stores/useAuthStore.ts | 15 | ||||
| -rw-r--r-- | src/common/stores/useLoginStore.ts | 26 | ||||
| -rw-r--r-- | src/common/stores/useResultStore.ts | 26 | ||||
| -rw-r--r-- | src/common/stores/useStockOpnameStore.ts | 48 | ||||
| -rw-r--r-- | src/common/styles/fonts.ts | 8 | ||||
| -rw-r--r-- | src/common/styles/globals.css | 24 | ||||
| -rw-r--r-- | src/common/types/auth.ts | 6 | ||||
| -rw-r--r-- | src/common/types/select.ts | 4 | ||||
| -rw-r--r-- | src/common/types/stockOpname.ts | 33 | ||||
| -rw-r--r-- | src/common/types/team.ts | 7 |
21 files changed, 385 insertions, 0 deletions
diff --git a/src/common/components/Authenticated/index.tsx b/src/common/components/Authenticated/index.tsx new file mode 100644 index 0000000..cf7086e --- /dev/null +++ b/src/common/components/Authenticated/index.tsx @@ -0,0 +1,14 @@ +import { cookies } from 'next/headers' +import { redirect } from 'next/navigation' +import React from 'react' + +const Authenticated = ({ children }: { children: React.ReactNode }) => { + const credentialStr = cookies().get('credential')?.value + const credential: Credential | null = credentialStr ? JSON.parse(credentialStr) : null + + if (!credential) redirect('/login') + + return children +} + +export default Authenticated
\ No newline at end of file diff --git a/src/common/components/Scanner/index.tsx b/src/common/components/Scanner/index.tsx new file mode 100644 index 0000000..56d2495 --- /dev/null +++ b/src/common/components/Scanner/index.tsx @@ -0,0 +1,39 @@ +import { useZxing } from "react-zxing"; +import styles from "./scanner.module.css" + +type Props = { + paused: boolean, + onScan: (string: string) => void +} + +const Scanner = (props: Props) => { + const { ref } = useZxing({ + constraints: { + video: { + facingMode: 'environment', + width: { ideal: 1280 }, + height: { ideal: 720 }, + frameRate: { ideal: 30, max: 60 } + } + }, + timeBetweenDecodingAttempts: 100, + paused: props.paused, + onDecodeResult(result) { + props.onScan(result.getText()); + }, + }) + + const restartCam = () => { + ref.current?.pause() + ref.current?.play() + } + + return ( + <div className={styles.wrapper}> + <video ref={ref} onClick={restartCam} className={styles.video} /> + <div className={styles.videoFrame} /> + </div> + ) +} + +export default Scanner
\ No newline at end of file diff --git a/src/common/components/Scanner/scanner.module.css b/src/common/components/Scanner/scanner.module.css new file mode 100644 index 0000000..3528172 --- /dev/null +++ b/src/common/components/Scanner/scanner.module.css @@ -0,0 +1,16 @@ +.wrapper { + @apply relative w-auto h-auto rounded-lg border border-default-300; +} + +.video { + @apply rounded-lg; +} + +.videoFrame { + @apply absolute + border-dashed border-2 border-default-50 + w-3/5 h-1/5 + top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 + pointer-events-none + rounded-md; +} diff --git a/src/common/components/ScreenContainer/index.tsx b/src/common/components/ScreenContainer/index.tsx new file mode 100644 index 0000000..60676ea --- /dev/null +++ b/src/common/components/ScreenContainer/index.tsx @@ -0,0 +1,15 @@ +import styles from "./screen-container.module.css" + +interface Props { + children: React.ReactNode +} + +const ScreenContainer = ({ children }: Props) => { + return ( + <div className={styles.screen}> + {children} + </div> + ) +} + +export default ScreenContainer
\ No newline at end of file diff --git a/src/common/components/ScreenContainer/screen-container.module.css b/src/common/components/ScreenContainer/screen-container.module.css new file mode 100644 index 0000000..0d055d3 --- /dev/null +++ b/src/common/components/ScreenContainer/screen-container.module.css @@ -0,0 +1,3 @@ +.screen { + @apply container max-w-[480px] h-screen bg-white; +}
\ No newline at end of file diff --git a/src/common/constants/team.ts b/src/common/constants/team.ts new file mode 100644 index 0000000..cfb895b --- /dev/null +++ b/src/common/constants/team.ts @@ -0,0 +1,13 @@ +import { TeamAliases } from "../types/team"; + +export const teamAliases: TeamAliases = { + COUNT1: { + name: "Hitung 1", + }, + COUNT2: { + name: "Hitung 2", + }, + VERIFICATION: { + name: "Verifikasi", + }, +}; diff --git a/src/common/contexts/QueryProvider.tsx b/src/common/contexts/QueryProvider.tsx new file mode 100644 index 0000000..1d6a948 --- /dev/null +++ b/src/common/contexts/QueryProvider.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import React from 'react' + +type Props = { + children: React.ReactNode +} + +const queryClient = new QueryClient() + +const QueryProvider = ({ children }: Props) => { + return ( + <QueryClientProvider client={queryClient}> + {children} + </QueryClientProvider> + ) +} + +export default QueryProvider
\ No newline at end of file diff --git a/src/common/contexts/UIProvider.tsx b/src/common/contexts/UIProvider.tsx new file mode 100644 index 0000000..683e39e --- /dev/null +++ b/src/common/contexts/UIProvider.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { NextUIProvider } from "@nextui-org/react"; + +type Props = { + children: React.ReactNode +} + +const UIProvider = ({ children }: Props) => { + return ( + <NextUIProvider> + {children} + </NextUIProvider> + ) +} + +export default UIProvider
\ No newline at end of file diff --git a/src/common/libs/authenticate.ts b/src/common/libs/authenticate.ts new file mode 100644 index 0000000..48d0314 --- /dev/null +++ b/src/common/libs/authenticate.ts @@ -0,0 +1,22 @@ +const authenticate = async ({ + username, + password, +}: { + username: string; + password: string; +}) => { + const res = await fetch("/api/authenticate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + username, + password, + }), + }); + + return res; +}; + +export default authenticate; diff --git a/src/common/libs/clsxm.ts b/src/common/libs/clsxm.ts new file mode 100644 index 0000000..0aeffa4 --- /dev/null +++ b/src/common/libs/clsxm.ts @@ -0,0 +1,6 @@ +import clsx, { ClassValue } from "clsx"; +import { twMerge } from "tw-merge"; + +export default function clsxm(...classes: ClassValue[]) { + return twMerge(clsx(...classes)); +} diff --git a/src/common/libs/toast.tsx b/src/common/libs/toast.tsx new file mode 100644 index 0000000..2047a27 --- /dev/null +++ b/src/common/libs/toast.tsx @@ -0,0 +1,23 @@ +import ReactHotToast, { Toast } from "react-hot-toast" +import clsxm from "./clsxm" +import { ReactNode } from "react" +import { XIcon } from "lucide-react" + +type Options = Partial<Pick<Toast, "style" | "className" | "id" | "icon" | "duration" | "ariaProps" | "position" | "iconTheme">> | undefined + +const toast = (children: ReactNode, options: Options = undefined) => { + return ReactHotToast.custom((t) => ( + <div className={clsxm("bg-neutral-100 border border-neutral-200 text-neutral-800 text-sm rounded-lg flex", { + "animate-appearance-in": t.visible, + "animate-appearance-out": !t.visible + })}> + <span className="py-2 px-3">{children}</span> + <div className="w-[1px] h-full bg-neutral-300" /> + <button type="button" className="px-2 text-neutral-800" onClick={() => ReactHotToast.dismiss(t.id)}> + <XIcon size={18} /> + </button> + </div> + ), options) +} + +export default toast
\ No newline at end of file diff --git a/src/common/stores/useAuthStore.ts b/src/common/stores/useAuthStore.ts new file mode 100644 index 0000000..b88cf0b --- /dev/null +++ b/src/common/stores/useAuthStore.ts @@ -0,0 +1,15 @@ +import { create } from "zustand"; +import { Credential } from "../types/auth"; + +type State = { + credentials: Credential | null | undefined; +}; + +type Action = { + setCredentials: (credentials: Credential | null) => void; +}; + +export const useAuthStore = create<State & Action>((set) => ({ + credentials: null, + setCredentials: (credentials) => set({ credentials }), +})); diff --git a/src/common/stores/useLoginStore.ts b/src/common/stores/useLoginStore.ts new file mode 100644 index 0000000..7b4551c --- /dev/null +++ b/src/common/stores/useLoginStore.ts @@ -0,0 +1,26 @@ +import { create } from "zustand"; + +type State = { + form: { + username: string; + password: string; + }; +}; + +type Action = { + updateForm: (name: string, value: string) => void; +}; + +export const useLoginStore = create<State & Action>((set) => ({ + form: { + username: "", + password: "", + }, + updateForm: (name, value) => + set((state) => ({ + form: { + ...state.form, + [name]: value, + }, + })), +})); diff --git a/src/common/stores/useResultStore.ts b/src/common/stores/useResultStore.ts new file mode 100644 index 0000000..d8da56c --- /dev/null +++ b/src/common/stores/useResultStore.ts @@ -0,0 +1,26 @@ +import { create } from "zustand"; + +type State = { + filter: { + search: string; + company: string; + }; +}; + +type Action = { + updateFilter: (name: string, value: string) => void; +}; + +export const useResultStore = create<State & Action>((set) => ({ + filter: { + search: "", + company: "", + }, + updateFilter: (name, value) => + set((state) => ({ + filter: { + ...state.filter, + [name]: value, + }, + })), +})); diff --git a/src/common/stores/useStockOpnameStore.ts b/src/common/stores/useStockOpnameStore.ts new file mode 100644 index 0000000..8aeff77 --- /dev/null +++ b/src/common/stores/useStockOpnameStore.ts @@ -0,0 +1,48 @@ +import { create } from "zustand"; +import { SelectOption } from "../types/select"; +import { SingleValue } from "react-select"; +import { User } from "@prisma/client"; + +type State = { + form: { + location: SingleValue<SelectOption> | null; + product: SingleValue<SelectOption> | null; + quantity: string; + }; + oldOpname: { + id: number; + quantity: number; + user: User; + } | null; +}; + +type Action = { + updateForm: ( + name: keyof State["form"], + value: SingleValue<SelectOption> | string + ) => void; + setOldOpname: (value: State["oldOpname"]) => void; + resetForm: () => void; +}; + +export const useStockOpnameStore = create<State & Action>((set) => ({ + form: { + location: null, + product: null, + quantity: "", + }, + oldOpname: null, + updateForm: (name, value) => + set((state) => ({ + form: { + ...state.form, + [name]: value, + }, + })), + setOldOpname: (value) => set(() => ({ oldOpname: value })), + resetForm: () => + set((state) => ({ + form: { ...state.form, product: null, quantity: "" }, + oldOpname: null, + })), +})); diff --git a/src/common/styles/fonts.ts b/src/common/styles/fonts.ts new file mode 100644 index 0000000..bf78bf7 --- /dev/null +++ b/src/common/styles/fonts.ts @@ -0,0 +1,8 @@ +import { Inter } from "next/font/google"; + +export const inter = Inter({ + subsets: ["latin"], + display: "fallback", + weight: ["400", "500", "600", "700"], + variable: "--font-inter", +}); diff --git a/src/common/styles/globals.css b/src/common/styles/globals.css new file mode 100644 index 0000000..861f1c0 --- /dev/null +++ b/src/common/styles/globals.css @@ -0,0 +1,24 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body { + @apply bg-neutral-100 font-primary; +} + +.react-select__control { + @apply h-auto !py-0.5 !rounded-medium !bg-default-100 !border-transparent; +} + +.react-select__single-value { + @apply !whitespace-normal text-sm leading-6; +} + +.react-select__menu { + @apply text-sm; +} + +.react-select__placeholder { + @apply text-sm; +} diff --git a/src/common/types/auth.ts b/src/common/types/auth.ts new file mode 100644 index 0000000..50d176f --- /dev/null +++ b/src/common/types/auth.ts @@ -0,0 +1,6 @@ +import { Company, User } from "@prisma/client"; + +export type Credential = User & { + company: Company; + token: string; +}; diff --git a/src/common/types/select.ts b/src/common/types/select.ts new file mode 100644 index 0000000..f61dc08 --- /dev/null +++ b/src/common/types/select.ts @@ -0,0 +1,4 @@ +export type SelectOption = { + value: string | number; + label: string; +}; diff --git a/src/common/types/stockOpname.ts b/src/common/types/stockOpname.ts new file mode 100644 index 0000000..762722a --- /dev/null +++ b/src/common/types/stockOpname.ts @@ -0,0 +1,33 @@ +import { Location, Team, User } from "@prisma/client"; + +export type DetailTeam = { + [key in keyof typeof Team]: { + quantity?: number; + user?: User; + }; +}; + +export type StockOpnameRequest = { + location: number; + product: number; + quantity: number; +}; + +export type StockOpnameRes = { + result: { + id: number; + name: string; + itemCode: string; + barcode: string; + onhandQty: number; + differenceQty: number; + isDifferent: boolean; + quantity: { + [key in keyof typeof Team]: number | null; + }; + }; + page: number; + totalPage: number; +}; + +export type StockOpnameLocationRes = Location & DetailTeam; diff --git a/src/common/types/team.ts b/src/common/types/team.ts new file mode 100644 index 0000000..0989337 --- /dev/null +++ b/src/common/types/team.ts @@ -0,0 +1,7 @@ +import { Team } from "@prisma/client"; + +export type TeamAliases = { + [key in keyof typeof Team]: { + name: string; + }; +}; |
