Compare commits
2 Commits
6129f489dd
...
05f3e019a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
05f3e019a8
|
|||
|
6d6bef8f50
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ caddy/data/caddy
|
||||
caddy/config/caddy
|
||||
testaction.secret
|
||||
caddy/logs
|
||||
.next
|
||||
|
||||
3
app/global.css
Normal file
3
app/global.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
14
app/grouping/page.tsx
Normal file
14
app/grouping/page.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
88
app/inside/IdComponent.tsx
Normal file
88
app/inside/IdComponent.tsx
Normal 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
21
app/inside/page.tsx
Normal 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
14
app/layout.tsx
Normal 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
3
app/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function Page() {
|
||||
return <h1>Hello, Next.js!</h1>;
|
||||
}
|
||||
76
components/LocationSelector/index.tsx
Normal file
76
components/LocationSelector/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
32
components/locationContenxt.tsx
Normal file
32
components/locationContenxt.tsx
Normal 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
5
next-env.d.ts
vendored
Normal 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.
|
||||
@@ -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
762
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
11
tailwind.config.js
Normal file
11
tailwind.config.js
Normal 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: [],
|
||||
};
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user