Compare commits

...

2 Commits

Author SHA1 Message Date
05f3e019a8 added location element
All checks were successful
backend-action / build-image (push) Successful in 1m50s
2024-05-16 12:50:14 +07:00
6d6bef8f50 added nextjs 2024-05-16 10:40:04 +07:00
15 changed files with 1059 additions and 34 deletions

1
.gitignore vendored
View File

@@ -8,3 +8,4 @@ caddy/data/caddy
caddy/config/caddy
testaction.secret
caddy/logs
.next

3
app/global.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

14
app/grouping/page.tsx Normal file
View File

@@ -0,0 +1,14 @@
import { db } from "@/src/db";
import LocationSelector from "../../components/LocationSelector";
import LocationContextProvider from "@/components/locationContenxt";
export default async function Page() {
let provinces = await db.query.province
.findMany({ with: { zones: true } })
.execute();
return (
<LocationContextProvider>
<LocationSelector provinces={provinces} />
</LocationContextProvider>
);
}

View File

@@ -0,0 +1,88 @@
import { useEffect, useState } from "react";
type Props = {
updateIdList: (cids: string[]) => void;
};
export default function IdComponent({ updateIdList }: Props) {
let [idSet, setIdSet] = useState<Set<string>>(new Set());
let onValidId = (id: string) => {
setIdSet((prev) => new Set(prev.add(id)));
};
let removeCid = (id: string) => {
setIdSet((prev) => new Set([...prev].filter((x) => x !== id)));
};
useEffect(() => {
updateIdList([...idSet]);
}, [idSet]);
return (
<div className="flex justify-center">
<div className="flex flex-col gap-2">
{Array.from(idSet).map((v) => (
<FixedId cid={v} removeCid={removeCid} key={v} />
))}
<SingleIdComponent onValidId={onValidId} />
</div>
</div>
);
}
type FixedIdProps = {
cid: string;
removeCid: (cid: string) => void;
};
function FixedId({ cid, removeCid }: FixedIdProps) {
return (
<div className="flex gap-2">
<input type="text" className="border-2" disabled value={cid} />
<button
className="bg-red-300 p-2 rounded-md"
onClick={() => removeCid(cid)}
>
</button>
</div>
);
}
type SingleIdProps = {
onValidId: (cid: string) => void;
};
function SingleIdComponent({ onValidId }: SingleIdProps) {
let [isValid, setIsValid] = useState(false);
let [cid, setCid] = useState("");
useEffect(() => {
let isValidId = isValidThaiID(cid);
setIsValid(isValidId);
if (isValidId) {
onValidId(cid);
setCid("");
}
}, [cid]);
return (
<div className="flex gap-2">
<input
type="text"
className="border-2"
onChange={(v) => setCid(v.target.value)}
value={cid}
/>
{isValid ? <p>OK</p> : null}
</div>
);
}
function isValidThaiID(id: string) {
if (!/^\d{13}$/.test(id)) {
return false;
}
let sum = 0;
for (let i = 0; i < 12; i++) {
sum += Number(id[i]) * (13 - i);
}
const checkSum = (11 - (sum % 11)) % 10;
return checkSum === Number(id[12]);
}

21
app/inside/page.tsx Normal file
View File

@@ -0,0 +1,21 @@
"use client";
import { useState } from "react";
import IdComponent from "./IdComponent";
export default function Page() {
let [idList, setIdList] = useState<string[]>([]);
function submit() {
console.log(idList);
}
return (
<div>
<IdComponent updateIdList={(cids) => setIdList(cids)} />
<p className="flex justify-center gap-4 mt-2 items-center">
Total: {idList.length}{" "}
<button className="bg-green-200 p-2 rounded-md" onClick={submit}>
Submit
</button>
</p>
</div>
);
}

14
app/layout.tsx Normal file
View File

@@ -0,0 +1,14 @@
import "./global.css";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<div className="container mx-auto mt-2">{children}</div>
</body>
</html>
);
}

3
app/page.tsx Normal file
View File

@@ -0,0 +1,3 @@
export default function Page() {
return <h1>Hello, Next.js!</h1>;
}

View File

@@ -0,0 +1,76 @@
"use client";
import { useContext, useEffect, useState } from "react";
import { LocationContext } from "../locationContenxt";
type Props = {
provinces: Province[];
};
type Province = {
id: number;
name: string;
zones: Zone[];
};
type Zone = {
id: number;
name: string;
province: number;
};
export default function LocationSelector({ provinces }: Props) {
let [provinceId, setProvinceId] = useState<number | undefined>(undefined);
let [amphurList, setAmphurList] = useState<Zone[] | undefined>(undefined);
let [amphurId, setAmphurId] = useState<number | undefined>(undefined);
const locationContext = useContext(LocationContext);
function setProvince(_id: string) {
let id = parseInt(_id);
setProvinceId(id);
let province = provinces.find((p) => p.id == id);
if (province == undefined) return;
setAmphurList(province.zones);
setAmphurId(undefined);
}
function setAmphur(_id: string) {
let id = parseInt(_id);
setAmphurId(id);
}
useEffect(() => {
if (locationContext == undefined) return;
if (amphurId == undefined || provinceId == undefined) {
locationContext.zone[1](undefined);
locationContext.province[1](undefined);
return;
}
locationContext.zone[1](amphurId);
locationContext.province[1](provinceId);
}, [amphurId]);
return (
<div className="flex flex-col gap-2">
<select
value={provinceId}
onChange={(e) => setProvince(e.currentTarget.value)}
>
<option value={undefined}>None</option>
{provinces.map((p) => (
<option key={p.id} value={p.id}>
{p.name}
</option>
))}
</select>
{amphurList && (
<select
value={amphurId}
onChange={(e) => setAmphur(e.currentTarget.value)}
>
<option value={undefined}>None</option>
{amphurList.map((a) => (
<option key={a.id} value={a.id}>
{a.name}
</option>
))}
</select>
)}
</div>
);
}

View File

@@ -0,0 +1,32 @@
"use client";
import {
Dispatch,
ReactNode,
SetStateAction,
createContext,
useState,
} from "react";
type LocationContextType = {
zone: [number | undefined, Dispatch<SetStateAction<number | undefined>>];
province: [number | undefined, Dispatch<SetStateAction<number | undefined>>];
};
export const LocationContext = createContext<LocationContextType | undefined>(
undefined
);
export default function LocationContextProvider({
children,
}: {
children?: ReactNode;
}) {
let zone = useState<number | undefined>(undefined);
let province = useState<number | undefined>(undefined);
return (
<LocationContext.Provider value={{ zone, province }}>
{children}
</LocationContext.Provider>
);
}

5
next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@@ -6,6 +6,7 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon --exec ts-node --swc src/app.ts",
"next-dev": "next dev",
"start": "node dist/src/app.js",
"build": "swc src -d dist",
"initialize_data": "node -r @swc-node/register addMetadata.ts"
@@ -16,6 +17,7 @@
"dependencies": {
"@trpc/client": "^10.45.2",
"@trpc/server": "^10.45.2",
"@types/react": "^18.3.2",
"better-sqlite3": "^9.5.0",
"cors": "^2.8.5",
"drizzle-orm": "^0.30.8",
@@ -23,6 +25,9 @@
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"minio": "^7.1.3",
"next": "^14.2.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"trpc-playground": "^1.0.4",
"zod": "^3.22.4"
},
@@ -34,8 +39,11 @@
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"autoprefixer": "^10.4.19",
"drizzle-kit": "^0.20.14",
"nodemon": "^3.1.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
}

762
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

11
tailwind.config.js Normal file
View File

@@ -0,0 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {},
},
plugins: [],
};

View File

@@ -1,7 +1,6 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
@@ -9,10 +8,8 @@
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
@@ -23,13 +20,14 @@
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
"module": "commonjs" /* Specify what module code is generated. */, // "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
"baseUrl": "." /* Specify the base directory to resolve non-relative module names. */,
"paths": {
"@/components/*": ["components/*"],
"@/src/*": ["src/*"]
},
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
@@ -42,12 +40,10 @@
// "resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
@@ -72,18 +68,13 @@
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */ /* Type Checking */,
"strict": true /* Enable all strict type-checking options. */, // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
@@ -101,9 +92,23 @@
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,
"incremental": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}