Compare commits
9 Commits
b35066835b
...
ci/docker-
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f1c7d9c9c | |||
| 2f9f05d638 | |||
| 78cc39d869 | |||
| 1d505b8639 | |||
| ec6f0871b4 | |||
| b282a71381 | |||
| b73ec9ffd8 | |||
| ddba65ed5c | |||
| adbe09d074 |
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@@ -0,0 +1,3 @@
|
||||
.git
|
||||
.next
|
||||
node_modules
|
||||
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
# Stage 1: Build
|
||||
FROM node:18-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy dependencies first to cache better
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy all source code
|
||||
COPY . .
|
||||
|
||||
# Build the Next.js app
|
||||
RUN npm run build
|
||||
|
||||
|
||||
# Stage 2: Run production image
|
||||
FROM node:18-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/package.json /app/package-lock.json ./
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY --from=builder /app/.next .next
|
||||
COPY --from=builder /app/public public
|
||||
COPY --from=builder /app/next.config.ts ./
|
||||
COPY --from=builder /app/src src
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npx", "next", "start"]
|
||||
48
README.md
48
README.md
@@ -2,35 +2,37 @@ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-
|
||||
|
||||
## Getting Started
|
||||
|
||||
First, run the development server:
|
||||
Environment requirements
|
||||
- NodeJS version 22 or latest
|
||||
- npm version 10.9.2 or latest
|
||||
|
||||
Environment variable
|
||||
- Make .env.local file in `root` folder
|
||||
```env
|
||||
JWT_SECRET=your secret key
|
||||
```
|
||||
|
||||
Install package
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||
## Application features
|
||||
- Login
|
||||
- username: `admin`
|
||||
- password: `admin123`
|
||||
|
||||
## Learn More
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||
|
||||
## Deploy on Vercel
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||
- Customer features
|
||||
- User featues
|
||||
- Mail template features
|
||||
- Logout
|
||||
12
docker-compose.yaml
Normal file
12
docker-compose.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: cosmos-crm
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- JWT_SECRET=8f2e9c4a7b1d6e3f8a5b2c9e6f1a4d7b8e3f6a9c2d5e8b1f4a7c0e3d6f9b2a5c8
|
||||
61
middleware.ts
Normal file
61
middleware.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
// Define protected routes
|
||||
const protectedRoutes = ['/modules'];
|
||||
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Check if the current path is a protected route
|
||||
const isProtectedRoute = protectedRoutes.some(route =>
|
||||
pathname.startsWith(route)
|
||||
);
|
||||
|
||||
|
||||
|
||||
// Get token from cookies
|
||||
const token = request.cookies.get('auth-token')?.value;
|
||||
|
||||
// If accessing a protected route without a token, redirect to login
|
||||
if (isProtectedRoute && !token) {
|
||||
return NextResponse.redirect(new URL('/auth/login', request.url));
|
||||
}
|
||||
|
||||
// Verify token if it exists
|
||||
if (token) {
|
||||
try {
|
||||
jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// If user is authenticated and trying to access login page, redirect to modules
|
||||
if (pathname === '/auth/login') {
|
||||
return NextResponse.redirect(new URL('/modules/user', request.url));
|
||||
}
|
||||
} catch {
|
||||
// Invalid token - remove it and redirect to login if accessing protected route
|
||||
const response = NextResponse.redirect(new URL('/auth/login', request.url));
|
||||
response.cookies.delete('auth-token');
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow the request to continue
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except for the ones starting with:
|
||||
* - api (API routes)
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
*/
|
||||
'/((?!api|_next/static|_next/image|favicon.ico).*)',
|
||||
],
|
||||
};
|
||||
159
package-lock.json
generated
159
package-lock.json
generated
@@ -22,10 +22,14 @@
|
||||
"@radix-ui/react-tabs": "^1.1.12",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"axios": "^1.10.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lowdb": "^7.0.1",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "15.3.5",
|
||||
@@ -2297,6 +2301,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/js-cookie": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
|
||||
"integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/json-schema": {
|
||||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
@@ -2311,6 +2321,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsonwebtoken": {
|
||||
"version": "9.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
||||
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
|
||||
@@ -2328,11 +2348,16 @@
|
||||
"@types/lodash": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.4.tgz",
|
||||
"integrity": "sha512-OP+We5WV8Xnbuvw0zC2m4qfB/BJvjyCwtNjhHdJxV1639SGSKrLmJkc3fMnp2Qy8nJyHp8RO6umxELN/dS1/EA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
@@ -3260,6 +3285,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-equal-constant-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/busboy": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
|
||||
@@ -3680,6 +3711,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
@@ -5269,6 +5309,15 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/js-cookie": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -5323,6 +5372,28 @@
|
||||
"json5": "lib/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonwebtoken": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
|
||||
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jws": "^3.2.2",
|
||||
"lodash.includes": "^4.3.0",
|
||||
"lodash.isboolean": "^3.0.3",
|
||||
"lodash.isinteger": "^4.0.4",
|
||||
"lodash.isnumber": "^3.0.3",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
"lodash.once": "^4.0.0",
|
||||
"ms": "^2.1.1",
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsx-ast-utils": {
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
|
||||
@@ -5339,6 +5410,27 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jwa": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
|
||||
"integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-equal-constant-time": "^1.0.1",
|
||||
"ecdsa-sig-formatter": "1.0.11",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jws": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jwa": "^1.4.1",
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -5638,6 +5730,42 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.includes": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isboolean": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isinteger": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
|
||||
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isnumber": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
|
||||
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isstring": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
|
||||
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@@ -5645,6 +5773,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.once": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@@ -5812,7 +5946,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
@@ -6570,6 +6703,26 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safe-push-apply": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
|
||||
@@ -6615,7 +6768,6 @@
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -7342,7 +7494,6 @@
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unrs-resolver": {
|
||||
|
||||
@@ -23,10 +23,14 @@
|
||||
"@radix-ui/react-tabs": "^1.1.12",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"axios": "^1.10.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lowdb": "^7.0.1",
|
||||
"lucide-react": "^0.525.0",
|
||||
"next": "15.3.5",
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db, initDB, resetDB } from '@/database/database';
|
||||
import { db, initDB } from '@/database/database';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
await initDB();
|
||||
@@ -8,8 +11,30 @@ export async function POST(req: NextRequest) {
|
||||
(u) => u.username === username && u.password === password && !u.isDeleted
|
||||
);
|
||||
if (user) {
|
||||
// In a real app, return a JWT or session token
|
||||
return NextResponse.json({ success: true, user: { id: user.id, username: user.username, email: user.email, firstName: user.firstName, lastName: user.lastName } });
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user.id.toString(),
|
||||
username: user.username,
|
||||
email: user.email
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '1h' }
|
||||
);
|
||||
|
||||
const userData = {
|
||||
id: user.id.toString(),
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
token,
|
||||
user: userData
|
||||
});
|
||||
} else {
|
||||
return NextResponse.json({ success: false, message: 'Invalid credentials' }, { status: 401 });
|
||||
}
|
||||
|
||||
43
src/app/api/auth/verify/route.ts
Normal file
43
src/app/api/auth/verify/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db, initDB } from '@/database/database';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
await initDB();
|
||||
const { token } = await req.json();
|
||||
|
||||
if (!token) {
|
||||
return NextResponse.json({ success: false, message: 'No token provided' }, { status: 401 });
|
||||
}
|
||||
|
||||
// Verify the token
|
||||
const decoded = jwt.verify(token, JWT_SECRET) as { id: string; username: string; email: string };
|
||||
|
||||
// Find the user in the database
|
||||
const user = db.data?.users.find(
|
||||
(u) => u.id.toString() === decoded.id && !u.isDeleted
|
||||
);
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ success: false, message: 'User not found' }, { status: 401 });
|
||||
}
|
||||
|
||||
const userData = {
|
||||
id: user.id.toString(),
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName
|
||||
};
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: userData
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ success: false, message: 'Invalid token' }, { status: 401 });
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import { db } from "@/database/database";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// Initialize database
|
||||
await db.read();
|
||||
|
||||
// Parse dependant ID from params
|
||||
const dependantId = parseInt(params.id);
|
||||
const dependantId = parseInt((await params).id);
|
||||
|
||||
if (isNaN(dependantId)) {
|
||||
return NextResponse.json(
|
||||
@@ -61,14 +61,14 @@ export async function GET(
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// Initialize database
|
||||
await db.read();
|
||||
|
||||
// Parse dependant ID from params
|
||||
const dependantId = parseInt(params.id);
|
||||
const dependantId = parseInt((await params).id);
|
||||
|
||||
if (isNaN(dependantId)) {
|
||||
return NextResponse.json(
|
||||
@@ -104,8 +104,8 @@ export async function PUT(
|
||||
...db.data!.customerDependants[dependantIndex],
|
||||
firstNameEn: body.dependantInfo.firstNameEn,
|
||||
lastNameEn: body.dependantInfo.lastNameEn,
|
||||
originAdd1: body.dependantInfo.originAdd1,
|
||||
localAdd1: body.dependantInfo.localAdd1,
|
||||
originAdd1: body.dependantContact.originAdd1,
|
||||
localAdd1: body.dependantContact.localAdd1,
|
||||
email: body.dependantContact.email,
|
||||
mobile: body.dependantContact.mobile,
|
||||
};
|
||||
@@ -141,14 +141,14 @@ export async function PUT(
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// Initialize database
|
||||
await db.read();
|
||||
|
||||
// Parse dependant ID from params
|
||||
const dependantId = parseInt(params.id);
|
||||
const dependantId = parseInt((await params).id);
|
||||
|
||||
if (isNaN(dependantId)) {
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -116,10 +116,17 @@ export async function PUT(
|
||||
...db.data!.customers[customerIndex],
|
||||
firstNameEn: body.customerInfo.firstNameEn,
|
||||
lastNameEn: body.customerInfo.lastNameEn,
|
||||
originAdd1: body.customerInfo.originAdd1,
|
||||
localAdd1: body.customerInfo.localAdd1,
|
||||
firstNameJp: body.customerInfo.firstNameJp,
|
||||
lastNameJp: body.customerInfo.lastNameJp,
|
||||
firstNameJpKana: body.customerInfo.firstNameJpKana,
|
||||
lastNameJpKana: body.customerInfo.lastNameJpKana,
|
||||
gender: body.customerInfo.gender,
|
||||
martial: body.customerInfo.martial,
|
||||
status: body.customerInfo.status,
|
||||
email: body.customerContact.email,
|
||||
mobile: body.customerContact.mobile,
|
||||
originAdd1: body.customerContact.originAdd1,
|
||||
localAdd1: body.customerContact.localAdd1,
|
||||
};
|
||||
|
||||
db.data!.customers[customerIndex] = updatedCustomer;
|
||||
@@ -140,10 +147,14 @@ export async function PUT(
|
||||
custId: customerId,
|
||||
firstNameEn: dependant.dependantInfo.firstNameEn,
|
||||
lastNameEn: dependant.dependantInfo.lastNameEn,
|
||||
originAdd1: dependant.dependantInfo.originAdd1,
|
||||
localAdd1: dependant.dependantInfo.localAdd1,
|
||||
firstNameJp: dependant.dependantInfo.firstNameJp,
|
||||
lastNameJp: dependant.dependantInfo.lastNameJp,
|
||||
firstNameJpKana: dependant.dependantInfo.firstNameJpKana,
|
||||
lastNameJpKana: dependant.dependantInfo.lastNameJpKana,
|
||||
email: dependant.dependantContact.email,
|
||||
mobile: dependant.dependantContact.mobile,
|
||||
originAdd1: dependant.dependantContact.originAdd1,
|
||||
localAdd1: dependant.dependantContact.localAdd1,
|
||||
};
|
||||
newDependants.push(newDependant);
|
||||
db.data!.customerDependants.push(newDependant);
|
||||
|
||||
@@ -19,10 +19,17 @@ export async function POST(request: NextRequest) {
|
||||
id: getNextId(db.data!.customers),
|
||||
firstNameEn: validatedData.customerInfo.firstNameEn,
|
||||
lastNameEn: validatedData.customerInfo.lastNameEn,
|
||||
firstNameJp: validatedData.customerInfo.firstNameJp,
|
||||
lastNameJp: validatedData.customerInfo.lastNameJp,
|
||||
firstNameJpKana: validatedData.customerInfo.firstNameJpKana,
|
||||
lastNameJpKana: validatedData.customerInfo.lastNameJpKana,
|
||||
gender: validatedData.customerInfo.gender,
|
||||
martial: validatedData.customerInfo.martial,
|
||||
status: validatedData.customerInfo.status,
|
||||
email: validatedData.customerContact.email,
|
||||
mobile: validatedData.customerContact.mobile,
|
||||
originAdd1: validatedData.customerInfo.originAdd1,
|
||||
localAdd1: validatedData.customerInfo.localAdd1,
|
||||
originAdd1: validatedData.customerContact.originAdd1,
|
||||
localAdd1: validatedData.customerContact.localAdd1,
|
||||
};
|
||||
|
||||
// Add customer to database
|
||||
@@ -38,10 +45,14 @@ export async function POST(request: NextRequest) {
|
||||
custId: newCustomer.id,
|
||||
firstNameEn: dependant.dependantInfo.firstNameEn,
|
||||
lastNameEn: dependant.dependantInfo.lastNameEn,
|
||||
firstNameJp: dependant.dependantInfo.firstNameJp,
|
||||
lastNameJp: dependant.dependantInfo.lastNameJp,
|
||||
firstNameJpKana: dependant.dependantInfo.firstNameJpKana,
|
||||
lastNameJpKana: dependant.dependantInfo.lastNameJpKana,
|
||||
email: dependant.dependantContact.email,
|
||||
mobile: dependant.dependantContact.mobile,
|
||||
originAdd1: dependant.dependantInfo.originAdd1,
|
||||
localAdd1: dependant.dependantInfo.localAdd1,
|
||||
originAdd1: dependant.dependantContact.originAdd1,
|
||||
localAdd1: dependant.dependantContact.localAdd1,
|
||||
};
|
||||
customerDependants.push(newDependant);
|
||||
db.data!.customerDependants.push(newDependant);
|
||||
@@ -102,6 +113,9 @@ export async function GET(request: NextRequest) {
|
||||
const page = parseInt(searchParams.get("page") || "1");
|
||||
const pageSize = parseInt(searchParams.get("pageSize") || "10");
|
||||
const search = searchParams.get("search") || "";
|
||||
const gender = searchParams.get("gender") || "";
|
||||
const martial = searchParams.get("martial") || "";
|
||||
const status = searchParams.get("status") || "";
|
||||
const sortBy = searchParams.get("sortBy") || "id";
|
||||
const sortOrder = searchParams.get("sortOrder") || "asc";
|
||||
|
||||
@@ -129,6 +143,27 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
// Apply gender filter
|
||||
if (gender) {
|
||||
customersWithDependants = customersWithDependants.filter((customer) =>
|
||||
customer.gender.toLowerCase() === gender.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Apply martial status filter
|
||||
if (martial) {
|
||||
customersWithDependants = customersWithDependants.filter((customer) =>
|
||||
customer.martial.toLowerCase() === martial.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Apply status filter
|
||||
if (status) {
|
||||
customersWithDependants = customersWithDependants.filter((customer) =>
|
||||
customer.status.toLowerCase() === status.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
// Apply sorting
|
||||
customersWithDependants.sort((a, b) => {
|
||||
let aValue: string | number = a[sortBy as keyof typeof a] as string | number;
|
||||
|
||||
@@ -13,10 +13,11 @@ function writeDB() {
|
||||
// GET /api/user/[id]/permissions - Get all permissions for a user
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const userId = parseInt(params.id);
|
||||
const { id } = await params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json({ error: "Invalid user ID" }, { status: 400 });
|
||||
@@ -51,10 +52,11 @@ export async function GET(
|
||||
// POST /api/user/[id]/permissions - Add permissions to a user
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const userId = parseInt(params.id);
|
||||
const { id } = await params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json({ error: "Invalid user ID" }, { status: 400 });
|
||||
@@ -126,10 +128,11 @@ export async function POST(
|
||||
// PUT /api/user/[id]/permissions - Update permissions for a user
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const userId = parseInt(params.id);
|
||||
const { id } = await params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json({ error: "Invalid user ID" }, { status: 400 });
|
||||
@@ -201,10 +204,11 @@ export async function PUT(
|
||||
// DELETE /api/user/[id]/permissions - Remove all permissions from a user
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const userId = parseInt(params.id);
|
||||
const { id } = await params;
|
||||
const userId = parseInt(id);
|
||||
|
||||
if (isNaN(userId)) {
|
||||
return NextResponse.json({ error: "Invalid user ID" }, { status: 400 });
|
||||
|
||||
@@ -4,8 +4,10 @@ import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { loginSchema } from "@/schemas/auth.schema";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -19,6 +21,15 @@ export default function LoginPage() {
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { login, isAuthenticated } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
// Redirect if already authenticated
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
router.push('/modules/user');
|
||||
}
|
||||
}, [isAuthenticated, router]);
|
||||
|
||||
const onSubmit = async (data: LoginForm) => {
|
||||
setError("");
|
||||
@@ -26,13 +37,15 @@ export default function LoginPage() {
|
||||
try {
|
||||
const res = await axios.post("/api/auth", data);
|
||||
if (res.data.success) {
|
||||
// You can redirect or set session here
|
||||
window.location.href = "/modules/user"; // Redirect to user page on successful login
|
||||
// Use the authentication context to login
|
||||
login(res.data.token, res.data.user);
|
||||
router.push("/modules/user");
|
||||
} else {
|
||||
setError(res.data.message || "Login failed");
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e.response?.data?.message || "Login failed");
|
||||
} catch (e: unknown) {
|
||||
const error = e as { response?: { data?: { message?: string } } };
|
||||
setError(error.response?.data?.message || "Login failed");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { AuthProvider } from "@/contexts/AuthContext";
|
||||
import "./globals.css";
|
||||
|
||||
const geistSans = Geist({
|
||||
@@ -28,8 +29,10 @@ export default function RootLayout({
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
{children}
|
||||
<Toaster />
|
||||
<AuthProvider>
|
||||
{children}
|
||||
<Toaster />
|
||||
</AuthProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader } from "@/components/ui/card";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { Plus, Trash2, ArrowLeft } from "lucide-react";
|
||||
@@ -35,12 +36,19 @@ export default function CustomerAddPage() {
|
||||
customerInfo: {
|
||||
firstNameEn: "",
|
||||
lastNameEn: "",
|
||||
originAdd1: "",
|
||||
localAdd1: "",
|
||||
firstNameJp: "",
|
||||
lastNameJp: "",
|
||||
firstNameJpKana: "",
|
||||
lastNameJpKana: "",
|
||||
gender: "",
|
||||
martial: "",
|
||||
status: "",
|
||||
},
|
||||
customerContact: {
|
||||
email: "",
|
||||
mobile: "",
|
||||
originAdd1: "",
|
||||
localAdd1: "",
|
||||
},
|
||||
customerDependants: [],
|
||||
},
|
||||
@@ -53,12 +61,16 @@ export default function CustomerAddPage() {
|
||||
dependantInfo: {
|
||||
firstNameEn: "",
|
||||
lastNameEn: "",
|
||||
originAdd1: "",
|
||||
localAdd1: "",
|
||||
firstNameJp: "",
|
||||
lastNameJp: "",
|
||||
firstNameJpKana: "",
|
||||
lastNameJpKana: "",
|
||||
},
|
||||
dependantContact: {
|
||||
email: "",
|
||||
mobile: "",
|
||||
originAdd1: "",
|
||||
localAdd1: "",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -102,12 +114,16 @@ export default function CustomerAddPage() {
|
||||
dependantInfo: {
|
||||
firstNameEn: data.dependantInfo.firstNameEn,
|
||||
lastNameEn: data.dependantInfo.lastNameEn,
|
||||
originAdd1: data.dependantInfo.originAdd1,
|
||||
localAdd1: data.dependantInfo.localAdd1,
|
||||
firstNameJp: data.dependantInfo.firstNameJp,
|
||||
lastNameJp: data.dependantInfo.lastNameJp,
|
||||
firstNameJpKana: data.dependantInfo.firstNameJpKana,
|
||||
lastNameJpKana: data.dependantInfo.lastNameJpKana,
|
||||
},
|
||||
dependantContact: {
|
||||
email: data.dependantContact.email,
|
||||
mobile: data.dependantContact.mobile,
|
||||
originAdd1: data.dependantContact.originAdd1,
|
||||
localAdd1: data.dependantContact.localAdd1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -139,8 +155,13 @@ export default function CustomerAddPage() {
|
||||
const isValid = await form.trigger([
|
||||
"customerInfo.firstNameEn",
|
||||
"customerInfo.lastNameEn",
|
||||
"customerInfo.originAdd1",
|
||||
"customerInfo.localAdd1"
|
||||
"customerInfo.firstNameJp",
|
||||
"customerInfo.lastNameJp",
|
||||
"customerInfo.firstNameJpKana",
|
||||
"customerInfo.lastNameJpKana",
|
||||
"customerInfo.gender",
|
||||
"customerInfo.martial",
|
||||
"customerInfo.status"
|
||||
]);
|
||||
if (isValid) {
|
||||
setActiveTab("contact");
|
||||
@@ -149,7 +170,12 @@ export default function CustomerAddPage() {
|
||||
}
|
||||
} else if (activeTab === "contact") {
|
||||
// Validate customer contact fields before proceeding
|
||||
const isValid = await form.trigger(["customerContact.email", "customerContact.mobile"]);
|
||||
const isValid = await form.trigger([
|
||||
"customerContact.email",
|
||||
"customerContact.mobile",
|
||||
"customerContact.originAdd1",
|
||||
"customerContact.localAdd1"
|
||||
]);
|
||||
if (isValid) {
|
||||
setActiveTab("dependants");
|
||||
} else {
|
||||
@@ -174,19 +200,33 @@ export default function CustomerAddPage() {
|
||||
return (
|
||||
values.customerInfo?.firstNameEn?.trim() !== "" &&
|
||||
values.customerInfo?.lastNameEn?.trim() !== "" &&
|
||||
values.customerInfo?.originAdd1?.trim() !== "" &&
|
||||
values.customerInfo?.localAdd1?.trim() !== "" &&
|
||||
values.customerInfo?.firstNameJp?.trim() !== "" &&
|
||||
values.customerInfo?.lastNameJp?.trim() !== "" &&
|
||||
values.customerInfo?.firstNameJpKana?.trim() !== "" &&
|
||||
values.customerInfo?.lastNameJpKana?.trim() !== "" &&
|
||||
values.customerInfo?.gender?.trim() !== "" &&
|
||||
values.customerInfo?.martial?.trim() !== "" &&
|
||||
values.customerInfo?.status?.trim() !== "" &&
|
||||
!errors.customerInfo?.firstNameEn &&
|
||||
!errors.customerInfo?.lastNameEn &&
|
||||
!errors.customerInfo?.originAdd1 &&
|
||||
!errors.customerInfo?.localAdd1
|
||||
!errors.customerInfo?.firstNameJp &&
|
||||
!errors.customerInfo?.lastNameJp &&
|
||||
!errors.customerInfo?.firstNameJpKana &&
|
||||
!errors.customerInfo?.lastNameJpKana &&
|
||||
!errors.customerInfo?.gender &&
|
||||
!errors.customerInfo?.martial &&
|
||||
!errors.customerInfo?.status
|
||||
);
|
||||
} else if (tabName === "contact") {
|
||||
return (
|
||||
values.customerContact?.email?.trim() !== "" &&
|
||||
values.customerContact?.mobile?.trim() !== "" &&
|
||||
values.customerContact?.originAdd1?.trim() !== "" &&
|
||||
values.customerContact?.localAdd1?.trim() !== "" &&
|
||||
!errors.customerContact?.email &&
|
||||
!errors.customerContact?.mobile
|
||||
!errors.customerContact?.mobile &&
|
||||
!errors.customerContact?.originAdd1 &&
|
||||
!errors.customerContact?.localAdd1
|
||||
);
|
||||
}
|
||||
return true;
|
||||
@@ -276,16 +316,14 @@ export default function CustomerAddPage() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.originAdd1"
|
||||
name="customerInfo.firstNameJp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Origin Address</FormLabel>
|
||||
<FormLabel>First Name (Japanese)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter origin address" {...field} />
|
||||
<Input placeholder="名前を入力してください" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -293,17 +331,110 @@ export default function CustomerAddPage() {
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.localAdd1"
|
||||
name="customerInfo.lastNameJp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local Address</FormLabel>
|
||||
<FormLabel>Last Name (Japanese)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter local address" {...field} />
|
||||
<Input placeholder="姓を入力してください" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.firstNameJpKana"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>First Name (Japanese Kana)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="ナマエ" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.lastNameJpKana"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name (Japanese Kana)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="セイ" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.gender"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Gender</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select gender" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="male">Male</SelectItem>
|
||||
<SelectItem value="female">Female</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.martial"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Marital Status</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select marital status" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="single">Single</SelectItem>
|
||||
<SelectItem value="married">Married</SelectItem>
|
||||
<SelectItem value="divorced">Divorced</SelectItem>
|
||||
<SelectItem value="widowed">Widowed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerInfo.status"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Status</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="inactive">Inactive</SelectItem>
|
||||
<SelectItem value="pending">Pending</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button type="button" onClick={handleNextTab}>
|
||||
@@ -342,6 +473,34 @@ export default function CustomerAddPage() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerContact.originAdd1"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Origin Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter origin address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="customerContact.localAdd1"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter local address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Button type="button" variant="outline" onClick={handlePreviousTab}>
|
||||
Previous
|
||||
@@ -403,16 +562,14 @@ export default function CustomerAddPage() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantInfo.originAdd1"
|
||||
name="dependantInfo.firstNameJp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Origin Address</FormLabel>
|
||||
<FormLabel>First Name (Japanese)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter origin address" {...field} />
|
||||
<Input placeholder="名前を入力してください" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -420,12 +577,38 @@ export default function CustomerAddPage() {
|
||||
/>
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantInfo.localAdd1"
|
||||
name="dependantInfo.lastNameJp"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local Address</FormLabel>
|
||||
<FormLabel>Last Name (Japanese)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter local address" {...field} />
|
||||
<Input placeholder="姓を入力してください" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantInfo.firstNameJpKana"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>First Name (Japanese Kana)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="ナマエ" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantInfo.lastNameJpKana"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Last Name (Japanese Kana)</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="セイ" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -439,8 +622,10 @@ export default function CustomerAddPage() {
|
||||
const isValid = await dependantForm.trigger([
|
||||
"dependantInfo.firstNameEn",
|
||||
"dependantInfo.lastNameEn",
|
||||
"dependantInfo.originAdd1",
|
||||
"dependantInfo.localAdd1"
|
||||
"dependantInfo.firstNameJp",
|
||||
"dependantInfo.lastNameJp",
|
||||
"dependantInfo.firstNameJpKana",
|
||||
"dependantInfo.lastNameJpKana"
|
||||
]);
|
||||
if (isValid) {
|
||||
setDependantDialogTab("contact");
|
||||
@@ -483,6 +668,34 @@ export default function CustomerAddPage() {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantContact.originAdd1"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Origin Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter origin address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={dependantForm.control}
|
||||
name="dependantContact.localAdd1"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Local Address</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Enter local address" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -544,8 +757,8 @@ export default function CustomerAddPage() {
|
||||
<TableCell>{dependant.dependantInfo.lastNameEn}</TableCell>
|
||||
<TableCell>{dependant.dependantContact.email}</TableCell>
|
||||
<TableCell>{dependant.dependantContact.mobile}</TableCell>
|
||||
<TableCell>{dependant.dependantInfo.originAdd1}</TableCell>
|
||||
<TableCell>{dependant.dependantInfo.localAdd1}</TableCell>
|
||||
<TableCell>{dependant.dependantContact.originAdd1}</TableCell>
|
||||
<TableCell>{dependant.dependantContact.localAdd1}</TableCell>
|
||||
<TableCell>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -4,7 +4,10 @@ import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Plus, Loader2, Users, RefreshCw } from "lucide-react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Plus, Loader2, Users, FileText, Download, FileSpreadsheet, Filter, X } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
import axios from "axios";
|
||||
import { ServerDataTable } from "@/components/ui/server-data-table";
|
||||
@@ -31,11 +34,13 @@ export default function CustomerPage() {
|
||||
const router = useRouter();
|
||||
const [customers, setCustomers] = useState<Customer[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [query, setQuery] = useState({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
search: "",
|
||||
gender: "",
|
||||
martial: "",
|
||||
status: "",
|
||||
sortBy: "id",
|
||||
sortOrder: "asc" as "asc" | "desc",
|
||||
});
|
||||
@@ -47,8 +52,12 @@ export default function CustomerPage() {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
});
|
||||
// Initialize search and sorting state to match query
|
||||
const [searchValue, setSearchValue] = useState(query.search);
|
||||
// Individual filter states
|
||||
const [nameSearch, setNameSearch] = useState("");
|
||||
const [genderFilter, setGenderFilter] = useState("all");
|
||||
const [martialFilter, setMartialFilter] = useState("all");
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
// Initialize sorting state to match query
|
||||
const [sorting, setSorting] = useState<{ id: string; desc: boolean }[]>(
|
||||
query.sortBy ? [{ id: query.sortBy, desc: query.sortOrder === "desc" }] : []
|
||||
);
|
||||
@@ -60,6 +69,9 @@ export default function CustomerPage() {
|
||||
page: query.page.toString(),
|
||||
pageSize: query.pageSize.toString(),
|
||||
search: query.search,
|
||||
gender: query.gender,
|
||||
martial: query.martial,
|
||||
status: query.status,
|
||||
sortBy: query.sortBy,
|
||||
sortOrder: query.sortOrder,
|
||||
});
|
||||
@@ -75,7 +87,6 @@ export default function CustomerPage() {
|
||||
toast.error("Failed to load customers");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
@@ -116,18 +127,50 @@ export default function CustomerPage() {
|
||||
}
|
||||
}, [fetchCustomers, customers]);
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
setIsRefreshing(true);
|
||||
await fetchCustomers();
|
||||
toast.success("Customer list refreshed");
|
||||
}, [fetchCustomers]);
|
||||
|
||||
const handlePaginationChange = useCallback((page: number, pageSize: number) => {
|
||||
setQuery((prev) => ({ ...prev, page, pageSize }));
|
||||
}, []);
|
||||
|
||||
// Search handler - will be called from ServerDataTable after debounce
|
||||
// Filter handlers - only update local state, don't trigger search
|
||||
const handleNameSearchChange = useCallback((value: string) => {
|
||||
setNameSearch(value);
|
||||
}, []);
|
||||
|
||||
const handleGenderFilterChange = useCallback((value: string) => {
|
||||
setGenderFilter(value);
|
||||
}, []);
|
||||
|
||||
const handleMartialFilterChange = useCallback((value: string) => {
|
||||
setMartialFilter(value);
|
||||
}, []);
|
||||
|
||||
const handleStatusFilterChange = useCallback((value: string) => {
|
||||
setStatusFilter(value);
|
||||
}, []);
|
||||
|
||||
// Apply filters when filter button is clicked
|
||||
const handleApplyFilters = useCallback(() => {
|
||||
setQuery((prev) => ({
|
||||
...prev,
|
||||
search: nameSearch,
|
||||
gender: genderFilter === "all" ? "" : genderFilter,
|
||||
martial: martialFilter === "all" ? "" : martialFilter,
|
||||
status: statusFilter === "all" ? "" : statusFilter,
|
||||
page: 1
|
||||
}));
|
||||
}, [nameSearch, genderFilter, martialFilter, statusFilter]);
|
||||
|
||||
const handleClearFilters = useCallback(() => {
|
||||
setNameSearch("");
|
||||
setGenderFilter("all");
|
||||
setMartialFilter("all");
|
||||
setStatusFilter("all");
|
||||
setQuery((prev) => ({ ...prev, search: "", gender: "", martial: "", status: "", page: 1 }));
|
||||
}, []);
|
||||
|
||||
// Legacy search handler for ServerDataTable compatibility
|
||||
const handleSearchChange = useCallback((search: string) => {
|
||||
setNameSearch(search);
|
||||
setQuery((prev) => ({ ...prev, search, page: 1 }));
|
||||
}, []);
|
||||
|
||||
@@ -169,19 +212,47 @@ export default function CustomerPage() {
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
onClick={handleRefresh}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isRefreshing}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? "animate-spin" : ""}`} />
|
||||
<span>Refresh</span>
|
||||
<FileText className="h-4 w-4" />
|
||||
<span>PDF Export</span>
|
||||
</Button>
|
||||
<Button onClick={handleAddCustomer} className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
<span>CSV Download</span>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<FileSpreadsheet className="h-4 w-4" />
|
||||
<span>Excel Download</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleAddCustomer}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
<span>Add Customer</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleApplyFilters}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<Filter className="h-4 w-4" />
|
||||
<span>Filter</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,20 +264,95 @@ export default function CustomerPage() {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* Filter Section */}
|
||||
<div className="mb-6 p-4 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold">Search & Filter</h3>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleClearFilters}
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span>Clear All</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Name Search */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="nameSearch">Search by Name</Label>
|
||||
<Input
|
||||
id="nameSearch"
|
||||
placeholder="Enter customer name..."
|
||||
value={nameSearch}
|
||||
onChange={(e) => handleNameSearchChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Gender Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="genderFilter">Gender</Label>
|
||||
<Select value={genderFilter} onValueChange={handleGenderFilterChange}>
|
||||
<SelectTrigger id="genderFilter">
|
||||
<SelectValue placeholder="Select gender" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Genders</SelectItem>
|
||||
<SelectItem value="male">Male</SelectItem>
|
||||
<SelectItem value="female">Female</SelectItem>
|
||||
<SelectItem value="other">Other</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Marital Status Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="martialFilter">Marital Status</Label>
|
||||
<Select value={martialFilter} onValueChange={handleMartialFilterChange}>
|
||||
<SelectTrigger id="martialFilter">
|
||||
<SelectValue placeholder="Select marital status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Statuses</SelectItem>
|
||||
<SelectItem value="single">Single</SelectItem>
|
||||
<SelectItem value="married">Married</SelectItem>
|
||||
<SelectItem value="divorced">Divorced</SelectItem>
|
||||
<SelectItem value="widowed">Widowed</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="statusFilter">Status</Label>
|
||||
<Select value={statusFilter} onValueChange={handleStatusFilterChange}>
|
||||
<SelectTrigger id="statusFilter">
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Statuses</SelectItem>
|
||||
<SelectItem value="active">Active</SelectItem>
|
||||
<SelectItem value="inactive">Inactive</SelectItem>
|
||||
<SelectItem value="pending">Pending</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{pagination.total > 0 ? (
|
||||
<ServerDataTable
|
||||
columns={columns}
|
||||
data={customers}
|
||||
pagination={pagination}
|
||||
searchKey="name"
|
||||
searchPlaceholder="Search customers by name..."
|
||||
isLoading={isLoading}
|
||||
onPaginationChange={handlePaginationChange}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSortingChange={handleSortingChange}
|
||||
searchValue={searchValue}
|
||||
searchValue={nameSearch}
|
||||
sorting={sorting}
|
||||
setSearchValue={setSearchValue}
|
||||
setSearchValue={setNameSearch}
|
||||
setSorting={setSorting}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AppSidebar } from "@/components/sidebar/app-sidebar";
|
||||
import { ProtectedRoute } from "@/components/common/ProtectedRoute";
|
||||
import {
|
||||
SidebarInset,
|
||||
SidebarProvider,
|
||||
@@ -10,6 +11,7 @@ export default function ModulesLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<main>
|
||||
<SidebarProvider>
|
||||
<AppSidebar />
|
||||
@@ -18,5 +20,6 @@ export default function ModulesLayout({
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</main>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
||||
125
src/app/page.tsx
125
src/app/page.tsx
@@ -1,103 +1,30 @@
|
||||
import Image from "next/image";
|
||||
"use client";
|
||||
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm/6 text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-[family-name:var(--font-geist-mono)] font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li className="tracking-[-.01em]">
|
||||
Save and see your changes instantly.
|
||||
</li>
|
||||
</ol>
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:w-auto"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent font-medium text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 w-full sm:w-auto md:w-[158px]"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-[24px] flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
if (isAuthenticated) {
|
||||
router.push('/modules/user');
|
||||
} else {
|
||||
router.push('/auth/login');
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated, isLoading, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
34
src/components/common/ProtectedRoute.tsx
Normal file
34
src/components/common/ProtectedRoute.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
interface ProtectedRouteProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
||||
const { isAuthenticated, isLoading } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !isAuthenticated) {
|
||||
router.push('/auth/login');
|
||||
}
|
||||
}, [isAuthenticated, isLoading, router]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-gray-900"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,6 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb";
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar";
|
||||
import { Separator } from "@radix-ui/react-select";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { LogOut, User } from "lucide-react";
|
||||
|
||||
interface BreadcrumbItemData {
|
||||
title: string;
|
||||
@@ -23,6 +29,8 @@ export function Header({
|
||||
showParent = true,
|
||||
breadcrumbs
|
||||
}: HeaderProps) {
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
// Use breadcrumbs prop if provided, otherwise fall back to legacy props
|
||||
const breadcrumbItems = breadcrumbs || [
|
||||
...(showParent ? [{ title: parentTitle, href: parentHref }] : []),
|
||||
@@ -55,6 +63,36 @@ export function Header({
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
|
||||
{/* User Dropdown */}
|
||||
<div className="ml-auto">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200">
|
||||
<User className="h-4 w-4" />
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||
<DropdownMenuLabel className="font-normal">
|
||||
<div className="flex flex-col space-y-1">
|
||||
<p className="text-sm font-medium leading-none">
|
||||
{user?.firstName} {user?.lastName}
|
||||
</p>
|
||||
<p className="text-xs leading-none text-muted-foreground">
|
||||
{user?.email}
|
||||
</p>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut className="mr-2 h-4 w-4" />
|
||||
<span>Log out</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ export interface Customer {
|
||||
mobile: string;
|
||||
originAdd1: string;
|
||||
localAdd1: string;
|
||||
gender: string;
|
||||
martial: string;
|
||||
status: string;
|
||||
dependants?: Array<{
|
||||
id: number;
|
||||
custId: number;
|
||||
@@ -109,6 +112,87 @@ export const createCustomerColumns = ({
|
||||
return <div className="font-mono">{row.getValue("mobile")}</div>;
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "gender",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Gender
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const gender = row.getValue("gender") as string;
|
||||
return (
|
||||
<Badge variant="outline" className="capitalize">
|
||||
{gender || "Not specified"}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "martial",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Marital Status
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const martial = row.getValue("martial") as string;
|
||||
return (
|
||||
<Badge variant="outline" className="capitalize">
|
||||
{martial || "Not specified"}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "status",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
|
||||
className="h-8 px-2 lg:px-3"
|
||||
>
|
||||
Status
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const status = row.getValue("status") as string;
|
||||
const getStatusVariant = (status: string) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case "active":
|
||||
return "default";
|
||||
case "inactive":
|
||||
return "secondary";
|
||||
case "pending":
|
||||
return "outline";
|
||||
default:
|
||||
return "outline";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Badge variant={getStatusVariant(status)} className="capitalize">
|
||||
{status || "Not specified"}
|
||||
</Badge>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "dependants",
|
||||
header: "Dependants",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Key, ArrowUpDown } from "lucide-react";
|
||||
import { Key, ArrowUpDown, Trash2 } from "lucide-react";
|
||||
import { Column } from "@tanstack/react-table";
|
||||
|
||||
export interface User {
|
||||
@@ -102,7 +102,13 @@ export function createUserColumns({ onEdit, onDelete, onPermissions }: {
|
||||
</Button>
|
||||
)}
|
||||
{onDelete && (
|
||||
<Button size="sm" variant="destructive" onClick={() => onDelete(row.row.original.id)}>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onDelete(row.row.original.id)}
|
||||
className="text-red-600 hover:text-red-800 hover:bg-red-50"
|
||||
>
|
||||
<Trash2 className="h-4 w-4 mr-2" />
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
|
||||
101
src/contexts/AuthContext.tsx
Normal file
101
src/contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import Cookies from 'js-cookie';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
||||
interface AuthContextType {
|
||||
user: User | null;
|
||||
login: (token: string, userData: User) => void;
|
||||
logout: () => void;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const useAuth = () => {
|
||||
const context = useContext(AuthContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
||||
const login = (token: string, userData: User) => {
|
||||
Cookies.set('auth-token', token, { expires: 7 }); // 7 days
|
||||
setUser(userData);
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
Cookies.remove('auth-token');
|
||||
setUser(null);
|
||||
router.push('/auth/login');
|
||||
};
|
||||
|
||||
const verifyToken = async (token: string) => {
|
||||
try {
|
||||
const response = await fetch('/api/auth/verify', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ token }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setUser(data.user);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
console.error('Token verification failed:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initAuth = async () => {
|
||||
const token = Cookies.get('auth-token');
|
||||
if (token) {
|
||||
const isValid = await verifyToken(token);
|
||||
if (!isValid) {
|
||||
Cookies.remove('auth-token');
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
initAuth();
|
||||
}, []);
|
||||
|
||||
const value = {
|
||||
user,
|
||||
login,
|
||||
logout,
|
||||
isAuthenticated: !!user,
|
||||
isLoading,
|
||||
};
|
||||
|
||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
@@ -4,10 +4,10 @@ export interface Customer {
|
||||
id: number;
|
||||
firstNameEn: string;
|
||||
lastNameEn: string;
|
||||
// firstNameJp: string;
|
||||
// lastNameJp: string;
|
||||
// firstNameJpKana: string;
|
||||
// lastNameJpKana: string;
|
||||
firstNameJp: string;
|
||||
lastNameJp: string;
|
||||
firstNameJpKana: string;
|
||||
lastNameJpKana: string;
|
||||
email: string;
|
||||
// email2?: string;
|
||||
mobile: string;
|
||||
@@ -27,8 +27,8 @@ export interface Customer {
|
||||
// localCountry: string;
|
||||
// preferLang: string;
|
||||
// dob: string; // ISO string
|
||||
// gender: string;
|
||||
// martial: string;
|
||||
gender: string;
|
||||
martial: string;
|
||||
// occupation: string;
|
||||
// ecName: string;
|
||||
// ecRelation: string;
|
||||
@@ -38,7 +38,7 @@ export interface Customer {
|
||||
// initialOutReachRemark?: string;
|
||||
// consentGiven?: string;
|
||||
// contentDate?: string;
|
||||
// status: string;
|
||||
status: string;
|
||||
// remarks: string;
|
||||
// potcustId?: string;
|
||||
// isDeleted: boolean;
|
||||
@@ -73,10 +73,10 @@ export interface CustomerDependant {
|
||||
// deptSeq: number;
|
||||
firstNameEn: string;
|
||||
lastNameEn: string;
|
||||
// firstNameJp: string;
|
||||
// lastNameJp: string;
|
||||
// firstNameJpKana: string;
|
||||
// lastNameJpKana: string;
|
||||
firstNameJp: string;
|
||||
lastNameJp: string;
|
||||
firstNameJpKana: string;
|
||||
lastNameJpKana: string;
|
||||
email: string;
|
||||
// email2?: string;
|
||||
mobile: string;
|
||||
|
||||
@@ -4,46 +4,257 @@
|
||||
"id": 1,
|
||||
"firstNameEn": "John",
|
||||
"lastNameEn": "Smith",
|
||||
"firstNameJp": "ジョン",
|
||||
"lastNameJp": "スミス",
|
||||
"firstNameJpKana": "ジョン",
|
||||
"lastNameJpKana": "スミス",
|
||||
"email": "john.smith@email.com",
|
||||
"mobile": "+1-555-0101",
|
||||
"originAdd1": "123 Main Street, New York, NY 10001, USA",
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001",
|
||||
"gender": "other",
|
||||
"martial": "married",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"firstNameEn": "Sarah",
|
||||
"lastNameEn": "Johnson",
|
||||
"firstNameJp": "サラ",
|
||||
"lastNameJp": "ジョンソン",
|
||||
"firstNameJpKana": "サラ",
|
||||
"lastNameJpKana": "ジョンソン",
|
||||
"email": "sarah.johnson@email.com",
|
||||
"mobile": "+1-555-0102",
|
||||
"originAdd1": "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
"localAdd1": "321 Sakura Street, Osaka, Japan 530-0001"
|
||||
"localAdd1": "321 Sakura Street, Osaka, Japan 530-0001",
|
||||
"gender": "female",
|
||||
"martial": "single",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"firstNameEn": "Michael",
|
||||
"lastNameEn": "Brown",
|
||||
"firstNameJp": "マイケル",
|
||||
"lastNameJp": "ブラウン",
|
||||
"firstNameJpKana": "マイケル",
|
||||
"lastNameJpKana": "ブラウン",
|
||||
"email": "michael.brown@email.com",
|
||||
"mobile": "+44-20-7946-0958",
|
||||
"originAdd1": "42 Victoria Street, London, UK SW1H 0TL",
|
||||
"localAdd1": "789 Shibuya Crossing, Tokyo, Japan 150-0002"
|
||||
"localAdd1": "789 Shibuya Crossing, Tokyo, Japan 150-0002",
|
||||
"gender": "male",
|
||||
"martial": "divorced",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"firstNameEn": "Emily",
|
||||
"lastNameEn": "Davis",
|
||||
"firstNameJp": "エミリー",
|
||||
"lastNameJp": "デイビス",
|
||||
"firstNameJpKana": "エミリー",
|
||||
"lastNameJpKana": "デイビス",
|
||||
"email": "emily.davis@email.com",
|
||||
"mobile": "+61-2-9876-5432",
|
||||
"originAdd1": "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
"localAdd1": "654 Namba District, Osaka, Japan 542-0076"
|
||||
"localAdd1": "654 Namba District, Osaka, Japan 542-0076",
|
||||
"gender": "female",
|
||||
"martial": "married",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"firstNameEn": "David",
|
||||
"lastNameEn": "Wilson",
|
||||
"firstNameJp": "デイビッド",
|
||||
"lastNameJp": "ウィルソン",
|
||||
"firstNameJpKana": "デイビッド",
|
||||
"lastNameJpKana": "ウィルソン",
|
||||
"email": "david.wilson@email.com",
|
||||
"mobile": "+1-416-555-0199",
|
||||
"originAdd1": "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
|
||||
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061"
|
||||
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061",
|
||||
"gender": "male",
|
||||
"martial": "single",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"firstNameEn": "Maria",
|
||||
"lastNameEn": "Garcia",
|
||||
"firstNameJp": "マリア",
|
||||
"lastNameJp": "ガルシア",
|
||||
"firstNameJpKana": "マリア",
|
||||
"lastNameJpKana": "ガルシア",
|
||||
"email": "maria.garcia@email.com",
|
||||
"mobile": "+34-91-555-0123",
|
||||
"originAdd1": "Calle Gran Vía 45, Madrid, Spain 28013",
|
||||
"localAdd1": "246 Harajuku Street, Tokyo, Japan 150-0001",
|
||||
"gender": "female",
|
||||
"martial": "married",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"firstNameEn": "James",
|
||||
"lastNameEn": "Lee",
|
||||
"firstNameJp": "ジェームス",
|
||||
"lastNameJp": "リー",
|
||||
"firstNameJpKana": "ジェームス",
|
||||
"lastNameJpKana": "リー",
|
||||
"email": "james.lee@email.com",
|
||||
"mobile": "+65-6555-0187",
|
||||
"originAdd1": "1 Raffles Place, Singapore 048616",
|
||||
"localAdd1": "135 Roppongi Hills, Tokyo, Japan 106-6108",
|
||||
"gender": "male",
|
||||
"martial": "single",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"firstNameEn": "Anna",
|
||||
"lastNameEn": "Mueller",
|
||||
"firstNameJp": "アンナ",
|
||||
"lastNameJp": "ミュラー",
|
||||
"firstNameJpKana": "アンナ",
|
||||
"lastNameJpKana": "ミュラー",
|
||||
"email": "anna.mueller@email.com",
|
||||
"mobile": "+49-30-555-0234",
|
||||
"originAdd1": "Unter den Linden 77, Berlin, Germany 10117",
|
||||
"localAdd1": "567 Shinjuku District, Tokyo, Japan 160-0022",
|
||||
"gender": "female",
|
||||
"martial": "widowed",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"firstNameEn": "Robert",
|
||||
"lastNameEn": "Taylor",
|
||||
"firstNameJp": "ロバート",
|
||||
"lastNameJp": "テイラー",
|
||||
"firstNameJpKana": "ロバート",
|
||||
"lastNameJpKana": "テイラー",
|
||||
"email": "robert.taylor@email.com",
|
||||
"mobile": "+1-555-0145",
|
||||
"originAdd1": "890 Broadway, New York, NY 10003, USA",
|
||||
"localAdd1": "890 Akasaka District, Tokyo, Japan 107-0052",
|
||||
"gender": "male",
|
||||
"martial": "married",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"firstNameEn": "Lisa",
|
||||
"lastNameEn": "Anderson",
|
||||
"firstNameJp": "リサ",
|
||||
"lastNameJp": "アンダーソン",
|
||||
"firstNameJpKana": "リサ",
|
||||
"lastNameJpKana": "アンダーソン",
|
||||
"email": "lisa.anderson@email.com",
|
||||
"mobile": "+1-555-0167",
|
||||
"originAdd1": "456 Sunset Boulevard, Los Angeles, CA 90028, USA",
|
||||
"localAdd1": "234 Ikebukuro District, Tokyo, Japan 171-0021",
|
||||
"gender": "female",
|
||||
"martial": "divorced",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"firstNameEn": "Alex",
|
||||
"lastNameEn": "Chen",
|
||||
"firstNameJp": "アレックス",
|
||||
"lastNameJp": "チェン",
|
||||
"firstNameJpKana": "アレックス",
|
||||
"lastNameJpKana": "チェン",
|
||||
"email": "alex.chen@email.com",
|
||||
"mobile": "+86-21-555-0189",
|
||||
"originAdd1": "88 Nanjing Road, Shanghai, China 200001",
|
||||
"localAdd1": "678 Aoyama District, Tokyo, Japan 107-0061",
|
||||
"gender": "other",
|
||||
"martial": "single",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"firstNameEn": "Sophie",
|
||||
"lastNameEn": "Martin",
|
||||
"firstNameJp": "ソフィー",
|
||||
"lastNameJp": "マルタン",
|
||||
"firstNameJpKana": "ソフィー",
|
||||
"lastNameJpKana": "マルタン",
|
||||
"email": "sophie.martin@email.com",
|
||||
"mobile": "+33-1-555-0212",
|
||||
"originAdd1": "25 Champs-Élysées, Paris, France 75008",
|
||||
"localAdd1": "345 Ebisu District, Tokyo, Japan 150-0013",
|
||||
"gender": "female",
|
||||
"martial": "married",
|
||||
"status": "pending"
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"firstNameEn": "Thomas",
|
||||
"lastNameEn": "White",
|
||||
"firstNameJp": "トーマス",
|
||||
"lastNameJp": "ホワイト",
|
||||
"firstNameJpKana": "トーマス",
|
||||
"lastNameJpKana": "ホワイト",
|
||||
"email": "thomas.white@email.com",
|
||||
"mobile": "+1-555-0234",
|
||||
"originAdd1": "789 Michigan Avenue, Chicago, IL 60611, USA",
|
||||
"localAdd1": "456 Kanda District, Tokyo, Japan 101-0047",
|
||||
"gender": "male",
|
||||
"martial": "single",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"firstNameEn": "Isabella",
|
||||
"lastNameEn": "Rodriguez",
|
||||
"firstNameJp": "イザベラ",
|
||||
"lastNameJp": "ロドリゲス",
|
||||
"firstNameJpKana": "イザベラ",
|
||||
"lastNameJpKana": "ロドリゲス",
|
||||
"email": "isabella.rodriguez@email.com",
|
||||
"mobile": "+52-55-555-0256",
|
||||
"originAdd1": "Avenida Reforma 123, Mexico City, Mexico 06600",
|
||||
"localAdd1": "789 Ueno District, Tokyo, Japan 110-0005",
|
||||
"gender": "female",
|
||||
"martial": "married",
|
||||
"status": "inactive"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"firstNameEn": "William",
|
||||
"lastNameEn": "Thompson",
|
||||
"firstNameJp": "ウィリアム",
|
||||
"lastNameJp": "トンプソン",
|
||||
"firstNameJpKana": "ウィリアム",
|
||||
"lastNameJpKana": "トンプソン",
|
||||
"email": "william.thompson@email.com",
|
||||
"mobile": "+1-555-0278",
|
||||
"originAdd1": "567 Market Street, San Francisco, CA 94105, USA",
|
||||
"localAdd1": "123 Sumida District, Tokyo, Japan 130-0013",
|
||||
"gender": "male",
|
||||
"martial": "divorced",
|
||||
"status": "active"
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"firstNameEn": "Do",
|
||||
"lastNameEn": "Tung",
|
||||
"firstNameJp": "ádf",
|
||||
"lastNameJp": "ádf",
|
||||
"firstNameJpKana": "ádf",
|
||||
"lastNameJpKana": "ádf",
|
||||
"gender": "female",
|
||||
"martial": "married",
|
||||
"status": "active",
|
||||
"email": "dothanhtung196@gmail.com",
|
||||
"mobile": "0987417491",
|
||||
"originAdd1": "1231231231",
|
||||
"localAdd1": "asdfsdaf"
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
@@ -190,260 +401,160 @@
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"userId": 1,
|
||||
"permissionId": 9
|
||||
"userId": 2,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"userId": 1,
|
||||
"permissionId": 10
|
||||
"userId": 2,
|
||||
"permissionId": 6
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"userId": 1,
|
||||
"permissionId": 11
|
||||
"userId": 2,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"userId": 1,
|
||||
"permissionId": 12
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"userId": 1,
|
||||
"permissionId": 13
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"userId": 1,
|
||||
"permissionId": 14
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"userId": 1,
|
||||
"permissionId": 15
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"userId": 3,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"id": 13,
|
||||
"userId": 3,
|
||||
"permissionId": 2
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"id": 14,
|
||||
"userId": 3,
|
||||
"permissionId": 14
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"userId": 3,
|
||||
"id": 15,
|
||||
"userId": 4,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"userId": 4,
|
||||
"permissionId": 6
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"userId": 4,
|
||||
"permissionId": 7
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"userId": 4,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"userId": 5,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"userId": 5,
|
||||
"permissionId": 6
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"userId": 5,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"userId": 6,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"userId": 6,
|
||||
"permissionId": 2
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"userId": 4,
|
||||
"userId": 6,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"userId": 4,
|
||||
"permissionId": 1
|
||||
"userId": 6,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"userId": 4,
|
||||
"userId": 7,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"userId": 4,
|
||||
"permissionId": 9
|
||||
"userId": 7,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"userId": 4,
|
||||
"permissionId": 15
|
||||
"userId": 8,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"userId": 5,
|
||||
"permissionId": 4
|
||||
"userId": 8,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"userId": 5,
|
||||
"userId": 9,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 31,
|
||||
"userId": 5,
|
||||
"permissionId": 11
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"userId": 5,
|
||||
"permissionId": 12
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"userId": 6,
|
||||
"permissionId": 7
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"userId": 6,
|
||||
"permissionId": 10
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"userId": 6,
|
||||
"permissionId": 15
|
||||
},
|
||||
{
|
||||
"id": 36,
|
||||
"userId": 6,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 37,
|
||||
"userId": 6,
|
||||
"permissionId": 2
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"userId": 7,
|
||||
"permissionId": 11
|
||||
},
|
||||
{
|
||||
"id": 39,
|
||||
"userId": 7,
|
||||
"permissionId": 12
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"userId": 7,
|
||||
"permissionId": 13
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"userId": 7,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"userId": 8,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 43,
|
||||
"userId": 8,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 44,
|
||||
"userId": 8,
|
||||
"permissionId": 11
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"userId": 9,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"userId": 9,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"userId": 9,
|
||||
"permissionId": 6
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"id": 32,
|
||||
"userId": 9,
|
||||
"permissionId": 7
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"userId": 9,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 49,
|
||||
"userId": 9,
|
||||
"permissionId": 11
|
||||
},
|
||||
{
|
||||
"id": 50,
|
||||
"id": 34,
|
||||
"userId": 10,
|
||||
"permissionId": 4
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 51,
|
||||
"id": 35,
|
||||
"userId": 10,
|
||||
"permissionId": 1
|
||||
},
|
||||
{
|
||||
"id": 52,
|
||||
"userId": 10,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 53,
|
||||
"userId": 10,
|
||||
"permissionId": 9
|
||||
},
|
||||
{
|
||||
"id": 54,
|
||||
"userId": 11,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 55,
|
||||
"id": 36,
|
||||
"userId": 11,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"id": 37,
|
||||
"userId": 11,
|
||||
"permissionId": 6
|
||||
},
|
||||
{
|
||||
"id": 38,
|
||||
"userId": 11,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"userId": 11,
|
||||
"permissionId": 11
|
||||
},
|
||||
{
|
||||
"id": 58,
|
||||
"userId": 11,
|
||||
"permissionId": 12
|
||||
},
|
||||
{
|
||||
"id": 59,
|
||||
"userId": 2,
|
||||
"permissionId": 4
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"userId": 2,
|
||||
"permissionId": 5
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"userId": 2,
|
||||
"permissionId": 8
|
||||
},
|
||||
{
|
||||
"id": 62,
|
||||
"userId": 2,
|
||||
"permissionId": 11
|
||||
}
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "user_read",
|
||||
"description": "Read user information",
|
||||
"description": "View user list and user details",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
@@ -460,103 +571,45 @@
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "customer_read",
|
||||
"description": "Read customer information",
|
||||
"name": "user_permissions",
|
||||
"description": "Manage user permissions",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "customer_read",
|
||||
"description": "View customer list and customer details",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "customer_write",
|
||||
"description": "Create and update customer information",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"id": 7,
|
||||
"name": "customer_delete",
|
||||
"description": "Delete customer records",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"name": "admin_panel",
|
||||
"description": "Access administrative panel",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"name": "reports_view",
|
||||
"description": "View system reports",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"name": "reports_export",
|
||||
"description": "Export reports and data",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"name": "system_settings",
|
||||
"description": "Modify system configuration",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"name": "mail_template_read",
|
||||
"description": "Read mail templates",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"name": "mail_template_write",
|
||||
"description": "Create and update mail templates",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"name": "mail_template_delete",
|
||||
"description": "Delete mail templates",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"name": "permission_manage",
|
||||
"description": "Manage user permissions",
|
||||
"isActive": true
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"name": "audit_log",
|
||||
"description": "View audit logs and system activities",
|
||||
"name": "mail_template_access",
|
||||
"description": "Access mail template configuration",
|
||||
"isActive": true
|
||||
}
|
||||
],
|
||||
"customerDependants": [
|
||||
{
|
||||
"id": 1,
|
||||
"custId": 1,
|
||||
"firstNameEn": "Jane",
|
||||
"lastNameEn": "Smith",
|
||||
"email": "jane.smith@email.com",
|
||||
"mobile": "+1-555-0111",
|
||||
"originAdd1": "123 Main Street, New York, NY 10001, USA",
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"custId": 1,
|
||||
"firstNameEn": "Tommy",
|
||||
"lastNameEn": "Smith",
|
||||
"email": "tommy.smith@email.com",
|
||||
"mobile": "+1-555-0112",
|
||||
"originAdd1": "123 Main Street, New York, NY 10001, USA",
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"custId": 2,
|
||||
"firstNameEn": "Mark",
|
||||
"lastNameEn": "Johnson",
|
||||
"firstNameJp": "マーク",
|
||||
"lastNameJp": "ジョンソン",
|
||||
"firstNameJpKana": "マーク",
|
||||
"lastNameJpKana": "ジョンソン",
|
||||
"email": "mark.johnson@email.com",
|
||||
"mobile": "+1-555-0121",
|
||||
"originAdd1": "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
@@ -567,6 +620,10 @@
|
||||
"custId": 2,
|
||||
"firstNameEn": "Lisa",
|
||||
"lastNameEn": "Johnson",
|
||||
"firstNameJp": "リサ",
|
||||
"lastNameJp": "ジョンソン",
|
||||
"firstNameJpKana": "リサ",
|
||||
"lastNameJpKana": "ジョンソン",
|
||||
"email": "lisa.johnson@email.com",
|
||||
"mobile": "+1-555-0122",
|
||||
"originAdd1": "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
@@ -577,6 +634,10 @@
|
||||
"custId": 3,
|
||||
"firstNameEn": "Anna",
|
||||
"lastNameEn": "Brown",
|
||||
"firstNameJp": "アンナ",
|
||||
"lastNameJp": "ブラウン",
|
||||
"firstNameJpKana": "アンナ",
|
||||
"lastNameJpKana": "ブラウン",
|
||||
"email": "anna.brown@email.com",
|
||||
"mobile": "+44-20-7946-0959",
|
||||
"originAdd1": "42 Victoria Street, London, UK SW1H 0TL",
|
||||
@@ -587,6 +648,10 @@
|
||||
"custId": 4,
|
||||
"firstNameEn": "James",
|
||||
"lastNameEn": "Davis",
|
||||
"firstNameJp": "ジェームズ",
|
||||
"lastNameJp": "デイビス",
|
||||
"firstNameJpKana": "ジェームズ",
|
||||
"lastNameJpKana": "デイビス",
|
||||
"email": "james.davis@email.com",
|
||||
"mobile": "+61-2-9876-5433",
|
||||
"originAdd1": "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -597,6 +662,10 @@
|
||||
"custId": 4,
|
||||
"firstNameEn": "Sophie",
|
||||
"lastNameEn": "Davis",
|
||||
"firstNameJp": "ソフィー",
|
||||
"lastNameJp": "デイビス",
|
||||
"firstNameJpKana": "ソフィー",
|
||||
"lastNameJpKana": "デイビス",
|
||||
"email": "sophie.davis@email.com",
|
||||
"mobile": "+61-2-9876-5434",
|
||||
"originAdd1": "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -607,6 +676,10 @@
|
||||
"custId": 4,
|
||||
"firstNameEn": "Oliver",
|
||||
"lastNameEn": "Davis",
|
||||
"firstNameJp": "オリバー",
|
||||
"lastNameJp": "デイビス",
|
||||
"firstNameJpKana": "オリバー",
|
||||
"lastNameJpKana": "デイビス",
|
||||
"email": "oliver.davis@email.com",
|
||||
"mobile": "+61-2-9876-5435",
|
||||
"originAdd1": "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -617,10 +690,42 @@
|
||||
"custId": 5,
|
||||
"firstNameEn": "Rachel",
|
||||
"lastNameEn": "Wilson",
|
||||
"firstNameJp": "レイチェル",
|
||||
"lastNameJp": "ウィルソン",
|
||||
"firstNameJpKana": "レイチェル",
|
||||
"lastNameJpKana": "ウィルソン",
|
||||
"email": "rachel.wilson@email.com",
|
||||
"mobile": "+1-416-555-0191",
|
||||
"originAdd1": "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
|
||||
"localAdd1": "987 Ginza District, Tokyo, Japan 104-0061"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"custId": 1,
|
||||
"firstNameEn": "Jane",
|
||||
"lastNameEn": "Smith",
|
||||
"firstNameJp": "ジェーン",
|
||||
"lastNameJp": "スミス",
|
||||
"firstNameJpKana": "ジェーン",
|
||||
"lastNameJpKana": "スミス",
|
||||
"email": "jane.smith@email.com",
|
||||
"mobile": "+1-555-0111",
|
||||
"originAdd1": "123 Main Street, New York, NY 10001, USA",
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"custId": 1,
|
||||
"firstNameEn": "Tommy",
|
||||
"lastNameEn": "Smith",
|
||||
"firstNameJp": "トミー",
|
||||
"lastNameJp": "スミス",
|
||||
"firstNameJpKana": "トミー",
|
||||
"lastNameJpKana": "スミス",
|
||||
"email": "tommy.smith@email.com",
|
||||
"mobile": "+1-555-0112",
|
||||
"originAdd1": "123 Main Street, New York, NY 10001, USA",
|
||||
"localAdd1": "456 Oak Avenue, Tokyo, Japan 150-0001"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 1,
|
||||
firstNameEn: "Jane",
|
||||
lastNameEn: "Smith",
|
||||
firstNameJp: "ジェーン",
|
||||
lastNameJp: "スミス",
|
||||
firstNameJpKana: "ジェーン",
|
||||
lastNameJpKana: "スミス",
|
||||
email: "jane.smith@email.com",
|
||||
mobile: "+1-555-0111",
|
||||
originAdd1: "123 Main Street, New York, NY 10001, USA",
|
||||
@@ -17,6 +21,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 1,
|
||||
firstNameEn: "Tommy",
|
||||
lastNameEn: "Smith",
|
||||
firstNameJp: "トミー",
|
||||
lastNameJp: "スミス",
|
||||
firstNameJpKana: "トミー",
|
||||
lastNameJpKana: "スミス",
|
||||
email: "tommy.smith@email.com",
|
||||
mobile: "+1-555-0112",
|
||||
originAdd1: "123 Main Street, New York, NY 10001, USA",
|
||||
@@ -28,6 +36,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 2,
|
||||
firstNameEn: "Mark",
|
||||
lastNameEn: "Johnson",
|
||||
firstNameJp: "マーク",
|
||||
lastNameJp: "ジョンソン",
|
||||
firstNameJpKana: "マーク",
|
||||
lastNameJpKana: "ジョンソン",
|
||||
email: "mark.johnson@email.com",
|
||||
mobile: "+1-555-0121",
|
||||
originAdd1: "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
@@ -38,6 +50,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 2,
|
||||
firstNameEn: "Lisa",
|
||||
lastNameEn: "Johnson",
|
||||
firstNameJp: "リサ",
|
||||
lastNameJp: "ジョンソン",
|
||||
firstNameJpKana: "リサ",
|
||||
lastNameJpKana: "ジョンソン",
|
||||
email: "lisa.johnson@email.com",
|
||||
mobile: "+1-555-0122",
|
||||
originAdd1: "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
@@ -49,6 +65,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 3,
|
||||
firstNameEn: "Anna",
|
||||
lastNameEn: "Brown",
|
||||
firstNameJp: "アンナ",
|
||||
lastNameJp: "ブラウン",
|
||||
firstNameJpKana: "アンナ",
|
||||
lastNameJpKana: "ブラウン",
|
||||
email: "anna.brown@email.com",
|
||||
mobile: "+44-20-7946-0959",
|
||||
originAdd1: "42 Victoria Street, London, UK SW1H 0TL",
|
||||
@@ -60,6 +80,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 4,
|
||||
firstNameEn: "James",
|
||||
lastNameEn: "Davis",
|
||||
firstNameJp: "ジェームズ",
|
||||
lastNameJp: "デイビス",
|
||||
firstNameJpKana: "ジェームズ",
|
||||
lastNameJpKana: "デイビス",
|
||||
email: "james.davis@email.com",
|
||||
mobile: "+61-2-9876-5433",
|
||||
originAdd1: "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -70,6 +94,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 4,
|
||||
firstNameEn: "Sophie",
|
||||
lastNameEn: "Davis",
|
||||
firstNameJp: "ソフィー",
|
||||
lastNameJp: "デイビス",
|
||||
firstNameJpKana: "ソフィー",
|
||||
lastNameJpKana: "デイビス",
|
||||
email: "sophie.davis@email.com",
|
||||
mobile: "+61-2-9876-5434",
|
||||
originAdd1: "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -80,6 +108,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 4,
|
||||
firstNameEn: "Oliver",
|
||||
lastNameEn: "Davis",
|
||||
firstNameJp: "オリバー",
|
||||
lastNameJp: "デイビス",
|
||||
firstNameJpKana: "オリバー",
|
||||
lastNameJpKana: "デイビス",
|
||||
email: "oliver.davis@email.com",
|
||||
mobile: "+61-2-9876-5435",
|
||||
originAdd1: "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
@@ -91,6 +123,10 @@ export const sampleCustomerDependants: CustomerDependant[] = [
|
||||
custId: 5,
|
||||
firstNameEn: "Rachel",
|
||||
lastNameEn: "Wilson",
|
||||
firstNameJp: "レイチェル",
|
||||
lastNameJp: "ウィルソン",
|
||||
firstNameJpKana: "レイチェル",
|
||||
lastNameJpKana: "ウィルソン",
|
||||
email: "rachel.wilson@email.com",
|
||||
mobile: "+1-416-555-0191",
|
||||
originAdd1: "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
|
||||
|
||||
@@ -5,45 +5,240 @@ export const sampleCustomers: Customer[] = [
|
||||
id: 1,
|
||||
firstNameEn: "John",
|
||||
lastNameEn: "Smith",
|
||||
firstNameJp: "ジョン",
|
||||
lastNameJp: "スミス",
|
||||
firstNameJpKana: "ジョン",
|
||||
lastNameJpKana: "スミス",
|
||||
email: "john.smith@email.com",
|
||||
mobile: "+1-555-0101",
|
||||
originAdd1: "123 Main Street, New York, NY 10001, USA",
|
||||
localAdd1: "456 Oak Avenue, Tokyo, Japan 150-0001",
|
||||
gender: "male",
|
||||
martial: "married",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
firstNameEn: "Sarah",
|
||||
lastNameEn: "Johnson",
|
||||
firstNameJp: "サラ",
|
||||
lastNameJp: "ジョンソン",
|
||||
firstNameJpKana: "サラ",
|
||||
lastNameJpKana: "ジョンソン",
|
||||
email: "sarah.johnson@email.com",
|
||||
mobile: "+1-555-0102",
|
||||
originAdd1: "789 Pine Road, Los Angeles, CA 90210, USA",
|
||||
localAdd1: "321 Sakura Street, Osaka, Japan 530-0001",
|
||||
gender: "female",
|
||||
martial: "single",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
firstNameEn: "Michael",
|
||||
lastNameEn: "Brown",
|
||||
firstNameJp: "マイケル",
|
||||
lastNameJp: "ブラウン",
|
||||
firstNameJpKana: "マイケル",
|
||||
lastNameJpKana: "ブラウン",
|
||||
email: "michael.brown@email.com",
|
||||
mobile: "+44-20-7946-0958",
|
||||
originAdd1: "42 Victoria Street, London, UK SW1H 0TL",
|
||||
localAdd1: "789 Shibuya Crossing, Tokyo, Japan 150-0002",
|
||||
gender: "male",
|
||||
martial: "divorced",
|
||||
status: "pending",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
firstNameEn: "Emily",
|
||||
lastNameEn: "Davis",
|
||||
firstNameJp: "エミリー",
|
||||
lastNameJp: "デイビス",
|
||||
firstNameJpKana: "エミリー",
|
||||
lastNameJpKana: "デイビス",
|
||||
email: "emily.davis@email.com",
|
||||
mobile: "+61-2-9876-5432",
|
||||
originAdd1: "15 Collins Street, Melbourne, VIC 3000, Australia",
|
||||
localAdd1: "654 Namba District, Osaka, Japan 542-0076",
|
||||
gender: "female",
|
||||
martial: "married",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
firstNameEn: "David",
|
||||
lastNameEn: "Wilson",
|
||||
firstNameJp: "デイビッド",
|
||||
lastNameJp: "ウィルソン",
|
||||
firstNameJpKana: "デイビッド",
|
||||
lastNameJpKana: "ウィルソン",
|
||||
email: "david.wilson@email.com",
|
||||
mobile: "+1-416-555-0199",
|
||||
originAdd1: "100 Queen Street West, Toronto, ON M5H 2N2, Canada",
|
||||
localAdd1: "987 Ginza District, Tokyo, Japan 104-0061",
|
||||
gender: "male",
|
||||
martial: "single",
|
||||
status: "inactive",
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
firstNameEn: "Maria",
|
||||
lastNameEn: "Garcia",
|
||||
firstNameJp: "マリア",
|
||||
lastNameJp: "ガルシア",
|
||||
firstNameJpKana: "マリア",
|
||||
lastNameJpKana: "ガルシア",
|
||||
email: "maria.garcia@email.com",
|
||||
mobile: "+34-91-555-0123",
|
||||
originAdd1: "Calle Gran Vía 45, Madrid, Spain 28013",
|
||||
localAdd1: "246 Harajuku Street, Tokyo, Japan 150-0001",
|
||||
gender: "female",
|
||||
martial: "married",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
firstNameEn: "James",
|
||||
lastNameEn: "Lee",
|
||||
firstNameJp: "ジェームス",
|
||||
lastNameJp: "リー",
|
||||
firstNameJpKana: "ジェームス",
|
||||
lastNameJpKana: "リー",
|
||||
email: "james.lee@email.com",
|
||||
mobile: "+65-6555-0187",
|
||||
originAdd1: "1 Raffles Place, Singapore 048616",
|
||||
localAdd1: "135 Roppongi Hills, Tokyo, Japan 106-6108",
|
||||
gender: "male",
|
||||
martial: "single",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
firstNameEn: "Anna",
|
||||
lastNameEn: "Mueller",
|
||||
firstNameJp: "アンナ",
|
||||
lastNameJp: "ミュラー",
|
||||
firstNameJpKana: "アンナ",
|
||||
lastNameJpKana: "ミュラー",
|
||||
email: "anna.mueller@email.com",
|
||||
mobile: "+49-30-555-0234",
|
||||
originAdd1: "Unter den Linden 77, Berlin, Germany 10117",
|
||||
localAdd1: "567 Shinjuku District, Tokyo, Japan 160-0022",
|
||||
gender: "female",
|
||||
martial: "widowed",
|
||||
status: "pending",
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
firstNameEn: "Robert",
|
||||
lastNameEn: "Taylor",
|
||||
firstNameJp: "ロバート",
|
||||
lastNameJp: "テイラー",
|
||||
firstNameJpKana: "ロバート",
|
||||
lastNameJpKana: "テイラー",
|
||||
email: "robert.taylor@email.com",
|
||||
mobile: "+1-555-0145",
|
||||
originAdd1: "890 Broadway, New York, NY 10003, USA",
|
||||
localAdd1: "890 Akasaka District, Tokyo, Japan 107-0052",
|
||||
gender: "male",
|
||||
martial: "married",
|
||||
status: "inactive",
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
firstNameEn: "Lisa",
|
||||
lastNameEn: "Anderson",
|
||||
firstNameJp: "リサ",
|
||||
lastNameJp: "アンダーソン",
|
||||
firstNameJpKana: "リサ",
|
||||
lastNameJpKana: "アンダーソン",
|
||||
email: "lisa.anderson@email.com",
|
||||
mobile: "+1-555-0167",
|
||||
originAdd1: "456 Sunset Boulevard, Los Angeles, CA 90028, USA",
|
||||
localAdd1: "234 Ikebukuro District, Tokyo, Japan 171-0021",
|
||||
gender: "female",
|
||||
martial: "divorced",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
firstNameEn: "Alex",
|
||||
lastNameEn: "Chen",
|
||||
firstNameJp: "アレックス",
|
||||
lastNameJp: "チェン",
|
||||
firstNameJpKana: "アレックス",
|
||||
lastNameJpKana: "チェン",
|
||||
email: "alex.chen@email.com",
|
||||
mobile: "+86-21-555-0189",
|
||||
originAdd1: "88 Nanjing Road, Shanghai, China 200001",
|
||||
localAdd1: "678 Aoyama District, Tokyo, Japan 107-0061",
|
||||
gender: "other",
|
||||
martial: "single",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
firstNameEn: "Sophie",
|
||||
lastNameEn: "Martin",
|
||||
firstNameJp: "ソフィー",
|
||||
lastNameJp: "マルタン",
|
||||
firstNameJpKana: "ソフィー",
|
||||
lastNameJpKana: "マルタン",
|
||||
email: "sophie.martin@email.com",
|
||||
mobile: "+33-1-555-0212",
|
||||
originAdd1: "25 Champs-Élysées, Paris, France 75008",
|
||||
localAdd1: "345 Ebisu District, Tokyo, Japan 150-0013",
|
||||
gender: "female",
|
||||
martial: "married",
|
||||
status: "pending",
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
firstNameEn: "Thomas",
|
||||
lastNameEn: "White",
|
||||
firstNameJp: "トーマス",
|
||||
lastNameJp: "ホワイト",
|
||||
firstNameJpKana: "トーマス",
|
||||
lastNameJpKana: "ホワイト",
|
||||
email: "thomas.white@email.com",
|
||||
mobile: "+1-555-0234",
|
||||
originAdd1: "789 Michigan Avenue, Chicago, IL 60611, USA",
|
||||
localAdd1: "456 Kanda District, Tokyo, Japan 101-0047",
|
||||
gender: "male",
|
||||
martial: "single",
|
||||
status: "active",
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
firstNameEn: "Isabella",
|
||||
lastNameEn: "Rodriguez",
|
||||
firstNameJp: "イザベラ",
|
||||
lastNameJp: "ロドリゲス",
|
||||
firstNameJpKana: "イザベラ",
|
||||
lastNameJpKana: "ロドリゲス",
|
||||
email: "isabella.rodriguez@email.com",
|
||||
mobile: "+52-55-555-0256",
|
||||
originAdd1: "Avenida Reforma 123, Mexico City, Mexico 06600",
|
||||
localAdd1: "789 Ueno District, Tokyo, Japan 110-0005",
|
||||
gender: "female",
|
||||
martial: "married",
|
||||
status: "inactive",
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
firstNameEn: "William",
|
||||
lastNameEn: "Thompson",
|
||||
firstNameJp: "ウィリアム",
|
||||
lastNameJp: "トンプソン",
|
||||
firstNameJpKana: "ウィリアム",
|
||||
lastNameJpKana: "トンプソン",
|
||||
email: "william.thompson@email.com",
|
||||
mobile: "+1-555-0278",
|
||||
originAdd1: "567 Market Street, San Francisco, CA 94105, USA",
|
||||
localAdd1: "123 Sumida District, Tokyo, Japan 130-0013",
|
||||
gender: "male",
|
||||
martial: "divorced",
|
||||
status: "active",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Permission } from "./database.schema";
|
||||
|
||||
export const samplePermissions: Permission[] = [
|
||||
// User Management Permissions
|
||||
{
|
||||
id: 1,
|
||||
name: "user_read",
|
||||
description: "Read user information",
|
||||
description: "View user list and user details",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
@@ -21,74 +22,36 @@ export const samplePermissions: Permission[] = [
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "user_permissions",
|
||||
description: "Manage user permissions",
|
||||
isActive: true,
|
||||
},
|
||||
|
||||
// Customer Management Permissions
|
||||
{
|
||||
id: 5,
|
||||
name: "customer_read",
|
||||
description: "Read customer information",
|
||||
description: "View customer list and customer details",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
id: 6,
|
||||
name: "customer_write",
|
||||
description: "Create and update customer information",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
id: 7,
|
||||
name: "customer_delete",
|
||||
description: "Delete customer records",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "admin_panel",
|
||||
description: "Access administrative panel",
|
||||
isActive: true,
|
||||
},
|
||||
|
||||
// Mail Template Permissions
|
||||
{
|
||||
id: 8,
|
||||
name: "reports_view",
|
||||
description: "View system reports",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "reports_export",
|
||||
description: "Export reports and data",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "system_settings",
|
||||
description: "Modify system configuration",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: "mail_template_read",
|
||||
description: "Read mail templates",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "mail_template_write",
|
||||
description: "Create and update mail templates",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: "mail_template_delete",
|
||||
description: "Delete mail templates",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 14,
|
||||
name: "permission_manage",
|
||||
description: "Manage user permissions",
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
id: 15,
|
||||
name: "audit_log",
|
||||
description: "View audit logs and system activities",
|
||||
name: "mail_template_access",
|
||||
description: "Access mail template configuration",
|
||||
isActive: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -2,82 +2,62 @@ import { UserPermission } from "./database.schema";
|
||||
|
||||
export const sampleUserPermissions: UserPermission[] = [
|
||||
// Admin user (id: 1) gets all permissions
|
||||
{ id: 1, userId: 1, permissionId: 1 },
|
||||
{ id: 2, userId: 1, permissionId: 2 },
|
||||
{ id: 3, userId: 1, permissionId: 3 },
|
||||
{ id: 4, userId: 1, permissionId: 4 },
|
||||
{ id: 5, userId: 1, permissionId: 5 },
|
||||
{ id: 6, userId: 1, permissionId: 6 },
|
||||
{ id: 7, userId: 1, permissionId: 7 },
|
||||
{ id: 8, userId: 1, permissionId: 8 },
|
||||
{ id: 9, userId: 1, permissionId: 9 },
|
||||
{ id: 10, userId: 1, permissionId: 10 },
|
||||
{ id: 11, userId: 1, permissionId: 11 },
|
||||
{ id: 12, userId: 1, permissionId: 12 },
|
||||
{ id: 13, userId: 1, permissionId: 13 },
|
||||
{ id: 14, userId: 1, permissionId: 14 },
|
||||
{ id: 15, userId: 1, permissionId: 15 },
|
||||
{ id: 1, userId: 1, permissionId: 1 }, // user_read
|
||||
{ id: 2, userId: 1, permissionId: 2 }, // user_write
|
||||
{ id: 3, userId: 1, permissionId: 3 }, // user_delete
|
||||
{ id: 4, userId: 1, permissionId: 4 }, // user_permissions
|
||||
{ id: 5, userId: 1, permissionId: 5 }, // customer_read
|
||||
{ id: 6, userId: 1, permissionId: 6 }, // customer_write
|
||||
{ id: 7, userId: 1, permissionId: 7 }, // customer_delete
|
||||
{ id: 8, userId: 1, permissionId: 8 }, // mail_template_access
|
||||
|
||||
// John Doe (id: 2) - Customer Manager
|
||||
{ id: 16, userId: 2, permissionId: 4 }, // customer_read
|
||||
{ id: 17, userId: 2, permissionId: 5 }, // customer_write
|
||||
{ id: 18, userId: 2, permissionId: 8 }, // reports_view
|
||||
{ id: 19, userId: 2, permissionId: 11 }, // mail_template_read
|
||||
{ id: 9, userId: 2, permissionId: 5 }, // customer_read
|
||||
{ id: 10, userId: 2, permissionId: 6 }, // customer_write
|
||||
{ id: 11, userId: 2, permissionId: 8 }, // mail_template_access
|
||||
|
||||
// Jane Smith (id: 3) - User Manager
|
||||
{ id: 20, userId: 3, permissionId: 1 }, // user_read
|
||||
{ id: 21, userId: 3, permissionId: 2 }, // user_write
|
||||
{ id: 22, userId: 3, permissionId: 14 }, // permission_manage
|
||||
{ id: 23, userId: 3, permissionId: 8 }, // reports_view
|
||||
{ id: 12, userId: 3, permissionId: 1 }, // user_read
|
||||
{ id: 13, userId: 3, permissionId: 2 }, // user_write
|
||||
{ id: 14, userId: 3, permissionId: 4 }, // user_permissions
|
||||
|
||||
// Mike Johnson (id: 4) - Reports Analyst
|
||||
{ id: 24, userId: 4, permissionId: 4 }, // customer_read
|
||||
{ id: 25, userId: 4, permissionId: 1 }, // user_read
|
||||
{ id: 26, userId: 4, permissionId: 8 }, // reports_view
|
||||
{ id: 27, userId: 4, permissionId: 9 }, // reports_export
|
||||
{ id: 28, userId: 4, permissionId: 15 }, // audit_log
|
||||
// Mike Johnson (id: 4) - Customer Support Lead
|
||||
{ id: 15, userId: 4, permissionId: 5 }, // customer_read
|
||||
{ id: 16, userId: 4, permissionId: 6 }, // customer_write
|
||||
{ id: 17, userId: 4, permissionId: 7 }, // customer_delete
|
||||
{ id: 18, userId: 4, permissionId: 1 }, // user_read
|
||||
|
||||
// Sarah Wilson (id: 5) - Customer Support
|
||||
{ id: 29, userId: 5, permissionId: 4 }, // customer_read
|
||||
{ id: 30, userId: 5, permissionId: 5 }, // customer_write
|
||||
{ id: 31, userId: 5, permissionId: 11 }, // mail_template_read
|
||||
{ id: 32, userId: 5, permissionId: 12 }, // mail_template_write
|
||||
{ id: 19, userId: 5, permissionId: 5 }, // customer_read
|
||||
{ id: 20, userId: 5, permissionId: 6 }, // customer_write
|
||||
{ id: 21, userId: 5, permissionId: 8 }, // mail_template_access
|
||||
|
||||
// David Brown (id: 6) - System Administrator
|
||||
{ id: 33, userId: 6, permissionId: 7 }, // admin_panel
|
||||
{ id: 34, userId: 6, permissionId: 10 }, // system_settings
|
||||
{ id: 35, userId: 6, permissionId: 15 }, // audit_log
|
||||
{ id: 36, userId: 6, permissionId: 1 }, // user_read
|
||||
{ id: 37, userId: 6, permissionId: 2 }, // user_write
|
||||
// David Brown (id: 6) - HR Manager
|
||||
{ id: 22, userId: 6, permissionId: 1 }, // user_read
|
||||
{ id: 23, userId: 6, permissionId: 2 }, // user_write
|
||||
{ id: 24, userId: 6, permissionId: 4 }, // user_permissions
|
||||
{ id: 25, userId: 6, permissionId: 8 }, // mail_template_access
|
||||
|
||||
// Emma Davis (id: 7) - Content Manager
|
||||
{ id: 38, userId: 7, permissionId: 11 }, // mail_template_read
|
||||
{ id: 39, userId: 7, permissionId: 12 }, // mail_template_write
|
||||
{ id: 40, userId: 7, permissionId: 13 }, // mail_template_delete
|
||||
{ id: 41, userId: 7, permissionId: 4 }, // customer_read
|
||||
// Emma Davis (id: 7) - Mail Template Manager
|
||||
{ id: 26, userId: 7, permissionId: 8 }, // mail_template_access
|
||||
{ id: 27, userId: 7, permissionId: 5 }, // customer_read
|
||||
|
||||
// Alex Martinez (id: 8) - Junior Developer
|
||||
{ id: 42, userId: 8, permissionId: 4 }, // customer_read
|
||||
{ id: 43, userId: 8, permissionId: 1 }, // user_read
|
||||
{ id: 44, userId: 8, permissionId: 11 }, // mail_template_read
|
||||
// Alex Martinez (id: 8) - Junior Support
|
||||
{ id: 28, userId: 8, permissionId: 5 }, // customer_read
|
||||
{ id: 29, userId: 8, permissionId: 1 }, // user_read
|
||||
|
||||
// Lisa Garcia (id: 9) - Senior Support
|
||||
{ id: 45, userId: 9, permissionId: 4 }, // customer_read
|
||||
{ id: 46, userId: 9, permissionId: 5 }, // customer_write
|
||||
{ id: 47, userId: 9, permissionId: 6 }, // customer_delete
|
||||
{ id: 48, userId: 9, permissionId: 8 }, // reports_view
|
||||
{ id: 49, userId: 9, permissionId: 11 }, // mail_template_read
|
||||
// Lisa Garcia (id: 9) - Senior Customer Manager
|
||||
{ id: 30, userId: 9, permissionId: 5 }, // customer_read
|
||||
{ id: 31, userId: 9, permissionId: 6 }, // customer_write
|
||||
{ id: 32, userId: 9, permissionId: 7 }, // customer_delete
|
||||
{ id: 33, userId: 9, permissionId: 8 }, // mail_template_access
|
||||
|
||||
// Robert Taylor (id: 10) - Data Analyst
|
||||
{ id: 50, userId: 10, permissionId: 4 }, // customer_read
|
||||
{ id: 51, userId: 10, permissionId: 1 }, // user_read
|
||||
{ id: 52, userId: 10, permissionId: 8 }, // reports_view
|
||||
{ id: 53, userId: 10, permissionId: 9 }, // reports_export
|
||||
// Robert Taylor (id: 10) - Read-only User
|
||||
{ id: 34, userId: 10, permissionId: 5 }, // customer_read
|
||||
{ id: 35, userId: 10, permissionId: 1 }, // user_read
|
||||
|
||||
// Maria Rodriguez (id: 11) - Customer Service Lead
|
||||
{ id: 54, userId: 11, permissionId: 4 }, // customer_read
|
||||
{ id: 55, userId: 11, permissionId: 5 }, // customer_write
|
||||
{ id: 56, userId: 11, permissionId: 8 }, // reports_view
|
||||
{ id: 57, userId: 11, permissionId: 11 }, // mail_template_read
|
||||
{ id: 58, userId: 11, permissionId: 12 }, // mail_template_write
|
||||
{ id: 36, userId: 11, permissionId: 5 }, // customer_read
|
||||
{ id: 37, userId: 11, permissionId: 6 }, // customer_write
|
||||
{ id: 38, userId: 11, permissionId: 8 }, // mail_template_access
|
||||
];
|
||||
|
||||
@@ -3,25 +3,36 @@ import { z } from "zod";
|
||||
export const customerInfoSchema = z.object({
|
||||
firstNameEn: z.string().min(1, "First name is required"),
|
||||
lastNameEn: z.string().min(1, "Last name is required"),
|
||||
originAdd1: z.string().min(1, "Origin address is required"),
|
||||
localAdd1: z.string().min(1, "Local address is required"),
|
||||
firstNameJp: z.string().min(1, "First name in Japanese is required"),
|
||||
lastNameJp: z.string().min(1, "Last name in Japanese is required"),
|
||||
firstNameJpKana: z.string().min(1, "First name in Japanese Kana is required"),
|
||||
lastNameJpKana: z.string().min(1, "Last name in Japanese Kana is required"),
|
||||
gender: z.string().min(1, "Gender is required"),
|
||||
martial: z.string().min(1, "Marital status is required"),
|
||||
status: z.string().min(1, "Status is required"),
|
||||
});
|
||||
|
||||
export const customerContactSchema = z.object({
|
||||
email: z.string().email("Invalid email address"),
|
||||
mobile: z.string().min(1, "Mobile number is required"),
|
||||
originAdd1: z.string().min(1, "Origin address is required"),
|
||||
localAdd1: z.string().min(1, "Local address is required"),
|
||||
});
|
||||
|
||||
export const customerDependantInfoSchema = z.object({
|
||||
firstNameEn: z.string().min(1, "First name is required"),
|
||||
lastNameEn: z.string().min(1, "Last name is required"),
|
||||
originAdd1: z.string().min(1, "Origin address is required"),
|
||||
localAdd1: z.string().min(1, "Local address is required"),
|
||||
firstNameJp: z.string().min(1, "First name in Japanese is required"),
|
||||
lastNameJp: z.string().min(1, "Last name in Japanese is required"),
|
||||
firstNameJpKana: z.string().min(1, "First name in Japanese Kana is required"),
|
||||
lastNameJpKana: z.string().min(1, "Last name in Japanese Kana is required"),
|
||||
});
|
||||
|
||||
export const customerDependantContactSchema = z.object({
|
||||
email: z.string().email("Invalid email address"),
|
||||
mobile: z.string().min(1, "Mobile number is required"),
|
||||
originAdd1: z.string().min(1, "Origin address is required"),
|
||||
localAdd1: z.string().min(1, "Local address is required"),
|
||||
});
|
||||
|
||||
export const customerDependantFormSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user