summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to 'src/common')
-rw-r--r--src/common/components/Authenticated/index.tsx14
-rw-r--r--src/common/components/Scanner/index.tsx39
-rw-r--r--src/common/components/Scanner/scanner.module.css16
-rw-r--r--src/common/components/ScreenContainer/index.tsx15
-rw-r--r--src/common/components/ScreenContainer/screen-container.module.css3
-rw-r--r--src/common/constants/team.ts13
-rw-r--r--src/common/contexts/QueryProvider.tsx20
-rw-r--r--src/common/contexts/UIProvider.tsx17
-rw-r--r--src/common/libs/authenticate.ts22
-rw-r--r--src/common/libs/clsxm.ts6
-rw-r--r--src/common/libs/toast.tsx23
-rw-r--r--src/common/stores/useAuthStore.ts15
-rw-r--r--src/common/stores/useLoginStore.ts26
-rw-r--r--src/common/stores/useResultStore.ts26
-rw-r--r--src/common/stores/useStockOpnameStore.ts48
-rw-r--r--src/common/styles/fonts.ts8
-rw-r--r--src/common/styles/globals.css24
-rw-r--r--src/common/types/auth.ts6
-rw-r--r--src/common/types/select.ts4
-rw-r--r--src/common/types/stockOpname.ts33
-rw-r--r--src/common/types/team.ts7
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;
+ };
+};