updated select response

This commit is contained in:
Berkay 2025-07-31 22:37:40 +03:00
parent 924b538559
commit b54bbe2db2
16 changed files with 1715 additions and 741 deletions

View File

@ -20,6 +20,9 @@ ENV NODE_ENV=production
COPY --from=builder /usr/src/app/dist ./dist
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/package.json ./package.json
RUN npm prune --production
USER node

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@
"@swc/core": "^1.10.7",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.16.5",
"@types/node": "^22.17.0",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",

View File

@ -11,7 +11,7 @@ export class LoginService {
private readonly redis: RedisHandlers,
private readonly passHandlers: PasswordHandlers,
private readonly prisma: PrismaService,
) {}
) { }
async run(dto: userLoginValidator) {
const foundUser = await this.prisma.users.findFirstOrThrow({
@ -137,14 +137,16 @@ export class LoginService {
});
selectList = employees;
}
let fullName = `${foundPerson.firstname}`;
if (foundPerson.middle_name) fullName += ` ${foundPerson.middle_name}`;
if (foundPerson.birthname) fullName += ` ${foundPerson.birthname}`;
fullName += ` ${foundPerson.surname}`;
const redisData = AuthTokenSchema.parse({
people: foundPerson,
users: foundUser,
credentials: {
person_uu_id: foundPerson.uu_id,
person_name: foundPerson.firstname,
person_full_name: `${foundPerson.firstname} ${foundPerson.middle_name || ''} | ${foundPerson.birthname || ''} | ${foundPerson.surname}`,
uuid: foundPerson.uu_id,
fullName: fullName,
},
selectionList: {
type: foundUser.user_type,

View File

@ -19,7 +19,7 @@ export class SelectService {
constructor(
private readonly redis: RedisHandlers,
private readonly prisma: PrismaService,
) {}
) { }
async run(dto: userSelectValidator, req: Request) {
const accessObject = await this.redis.getLoginFromRedis(req);
if (!accessObject) {
@ -196,8 +196,6 @@ export class SelectService {
company: company,
menu: null,
pages: null,
config: null,
caches: null,
selection: await this.prisma.build_living_space.findFirstOrThrow({
where: { uu_id: dto.uuid },
select: {

View File

@ -9,9 +9,8 @@ export type UserType = (typeof UserType)[keyof typeof UserType];
// Credentials
export const CredentialsSchema = z.object({
person_uu_id: z.string(),
person_name: z.string(),
full_name: z.string(),
uuid: z.string(),
fullName: z.string(),
});
export type Credentials = z.infer<typeof CredentialsSchema>;

View File

@ -25,7 +25,7 @@ export class RedisHandlers {
constructor(
private readonly cacheService: CacheService,
private readonly passwordService: PasswordHandlers,
) {}
) { }
/**
* Validates that a Redis key follows the expected format
@ -125,7 +125,7 @@ export class RedisHandlers {
if (parts[1] === parts[2]) {
const value = await this.cacheService.get(key);
if (value) {
return { key, value };
return { key: value.key, value: value.value };
}
}
}

View File

@ -10,6 +10,7 @@
"dependencies": {
"clsx": "^2.1.1",
"cookies-next": "^6.1.0",
"glob": "^11.0.3",
"ioredis": "^5.6.1",
"lucide-react": "^0.533.0",
"next": "15.4.4",
@ -550,6 +551,41 @@
"integrity": "sha512-M/T6Zewn7sDaBQEqIZ8Rb+i9y8qfGmq+5SDFSf9sA2lUZTmdDLVdOiQaeDp+Q4wElZ9HG1GAX5KhDaidp6LQsQ==",
"license": "MIT"
},
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@ -1057,6 +1093,28 @@
"@types/react": "^19.0.0"
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001727",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
@ -1130,7 +1188,6 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "~1.1.4"
},
@ -1142,8 +1199,7 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT",
"optional": true
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
@ -1178,6 +1234,19 @@
"react": ">= 16.8.0"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@ -1237,6 +1306,16 @@
"node": ">=8"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/enhanced-resolve": {
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
@ -1251,6 +1330,43 @@
"node": ">=10.13.0"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz",
"integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==",
"dependencies": {
"foreground-child": "^3.3.1",
"jackspeak": "^4.1.1",
"minimatch": "^10.0.3",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^2.0.0"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@ -1301,6 +1417,33 @@
"license": "MIT",
"optional": true
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/jackspeak": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/jiti": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
@ -1562,6 +1705,14 @@
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/lucide-react": {
"version": "0.533.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.533.0.tgz",
@ -1581,11 +1732,24 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/minimatch": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz",
"integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==",
"dependencies": {
"@isaacs/brace-expansion": "^5.0.0"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
@ -1766,6 +1930,34 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -1905,6 +2097,36 @@
"@img/sharp-win32-x64": "0.34.3"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@ -1930,6 +2152,94 @@
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
"license": "MIT"
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/styled-jsx": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@ -2029,6 +2339,104 @@
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",

View File

@ -11,6 +11,7 @@
"dependencies": {
"clsx": "^2.1.1",
"cookies-next": "^6.1.0",
"glob": "^11.0.3",
"ioredis": "^5.6.1",
"lucide-react": "^0.533.0",
"next": "15.4.4",

View File

@ -1,36 +1,36 @@
'use client';
import { useEffect, useState } from 'react';
import { useTranslations } from 'next-intl';
import { useRouter } from 'next/navigation';
import { useRouter } from '@/i18n/navigation';
import { apiGetFetcher } from '@/lib/fetcher';
export default function PageSelect() {
const t = useTranslations('Select');
const router = useRouter();
const [selectionList, setSelectionList] = useState<{ type: string, list: any[] } | null>(null);
const [selectedOption, setSelectedOption] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchSelectionList = async () => {
setIsLoading(true);
try {
apiGetFetcher({ url: '/api/auth/selections', isNoCache: true }).then((res) => {
if (res.success) {
if (res.data && typeof res.data === 'object' && 'type' in res.data && 'list' in res.data) {
setSelectionList(res.data as { type: string, list: any[] });
}
const fetchSelectionList = async () => {
setIsLoading(true);
try {
apiGetFetcher({ url: '/api/auth/selections', isNoCache: true }).then((res) => {
if (res.success) {
if (res.data && typeof res.data === 'object' && 'type' in res.data && 'list' in res.data) {
setSelectionList(res.data as { type: string, list: any[] });
}
})
} catch (error) {
console.error('Error fetching selection list:', error);
} finally {
setIsLoading(false);
}
};
fetchSelectionList();
}, []);
const handleSelection = (id: string) => { setSelectedOption(id) };
}
})
} catch (error) {
console.error('Error fetching selection list:', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => { fetchSelectionList() }, []);
const handleSelection = (uuid: string) => { setSelectedOption(uuid) };
const handleContinue = async () => {
if (!selectedOption) return;
setIsLoading(true);
@ -41,7 +41,7 @@ export default function PageSelect() {
const result = await response.json();
if (response.ok && result.status === 200) {
console.log('Selection successful, redirecting to venue page');
router.push('/venue');
if (selectionList?.type === 'employee') { router.push('/office') } else if (selectionList?.type === 'occupant') { router.push('/venue') }
} else {
console.error('Selection failed:', result.message);
alert(`Selection failed: ${result.message || 'Unknown error'}`);
@ -52,231 +52,362 @@ export default function PageSelect() {
} finally { setIsLoading(false) }
};
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-white to-cyan-50 p-4 sm:p-6 md:p-8">
<div className="max-w-6xl mx-auto w-full h-full flex flex-col">
<div className="text-center mb-8 sm:mb-10 mt-4 sm:mt-6">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-800 mb-2">{t('title')}</h1>
<p className="text-base sm:text-lg text-gray-600">{t('description')}</p>
</div>
<div className="flex-grow flex flex-col">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 flex-grow">
{selectionList?.list?.map((item: any) => {
if (selectionList.type === 'employee') {
const staff = item.staff;
const department = staff?.duties?.departments;
const company = department?.companies;
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{staff?.staff_code?.charAt(0) || 'E'}
</div>
<h3 className="text-lg font-bold text-gray-800">{t('staff')}: {staff?.staff_code || t('employee')}</h3>
</div>
<div className="space-y-2">
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-xs text-gray-700">{t('uuid')}:</span>
<span className="ml-2 font-mono text-xs text-gray-600">{item?.uu_id}</span>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('department')}</span>
</div>
<div className="ml-6 mt-1 space-y-1">
<div className="flex items-center">
<span className="text-xs text-gray-500 w-16">{t('name')}:</span>
<span className="text-sm text-gray-600">{department?.department_name || 'N/A'}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-16">{t('code')}:</span>
<span className="text-sm text-gray-600">{department?.department_code || 'N/A'}</span>
</div>
</div>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z"></path>
</svg>
<span className="font-medium text-gray-700">{t('company')}:</span>
<span className="ml-2 text-sm text-gray-600">{company?.public_name || company?.formal_name || 'N/A'}</span>
</div>
</div>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
}
if (selectionList.type === 'occupant') {
const occupantType = item.occupant_types;
const buildPart = item.build_parts;
const build = buildPart?.build;
const enums = buildPart?.api_enum_dropdown_build_parts_part_type_idToapi_enum_dropdown;
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{occupantType?.occupant_code?.charAt(0) || 'O'}
</div>
<h3 className="text-lg font-bold text-gray-800">{t('occupant_type')}: {occupantType?.occupant_type}</h3>
</div>
<div className="space-y-2">
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-xs text-gray-700">{t('uuid')}:</span>
<span className="ml-2 font-mono text-xs text-gray-600">{item?.uu_id}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-gray-700">{t('occupant_code')}:</span>
<span className="ml-2 font-semibold text-indigo-600">{occupantType?.occupant_code}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('building')}:</span>
<span className="ml-2 text-gray-600">{build?.build_name || 'Building'}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg>
<span className="font-medium text-gray-700">{t('type')}:</span>
<span className="ml-2 text-gray-600">{enums?.value}</span>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('part_details')}</span>
</div>
<div className="grid grid-cols-2 gap-2 ml-6 mt-1">
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('code')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_code}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('no')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_no}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('level')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_level}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('status')}:</span>
<span className={`text-sm font-medium ${buildPart?.human_livable ? 'text-green-600' : 'text-red-600'}`}>
{buildPart?.human_livable ? t('livable') : t('not_livable')}
</span>
</div>
</div>
</div>
</div>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
}
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{item.uu_id?.charAt(0) || 'S'}
</div>
<h3 className="text-lg font-bold text-gray-800">{selectionList.type || t('selection')}</h3>
</div>
<p className="text-gray-600 text-sm">{item.uu_id || t('id')}</p>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
})}
if (selectionList) {
return (
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-white to-cyan-50 p-4 sm:p-6 md:p-8">
<div className="max-w-6xl mx-auto w-full h-full flex flex-col">
<div className="text-center mb-8 sm:mb-10 mt-4 sm:mt-6">
<h1 className="text-2xl sm:text-3xl md:text-4xl font-bold text-gray-800 mb-2">{t('title')}</h1>
<p className="text-base sm:text-lg text-gray-600">{t('description')}</p>
</div>
<div className="mt-8 sm:mt-10 flex justify-center">
<button
className={`px-8 py-4 rounded-xl font-bold text-white transition-all duration-300 text-lg ${selectedOption
? 'bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 shadow-lg shadow-indigo-200 hover:shadow-indigo-300 transform hover:scale-105'
: 'bg-gray-400 cursor-not-allowed'}`}
disabled={!selectedOption || isLoading}
onClick={handleContinue}
>
{isLoading ? t('processing') : selectedOption ? t('continue') : t('select_option')}
</button>
<div className="flex-grow flex flex-col">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 flex-grow">
{selectionList?.list?.map((item: any) => {
if (selectionList.type === 'employee') {
const staff = item.staff;
const department = staff?.duties?.departments;
const company = department?.companies;
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{staff?.staff_code?.charAt(0) || 'E'}
</div>
<h3 className="text-lg font-bold text-gray-800">{t('staff')}: {staff?.staff_code || t('employee')}</h3>
</div>
<div className="space-y-2">
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-xs text-gray-700">{t('uuid')}:</span>
<span className="ml-2 font-mono text-xs text-gray-600">{item?.uu_id}</span>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('department')}</span>
</div>
<div className="ml-6 mt-1 space-y-1">
<div className="flex items-center">
<span className="text-xs text-gray-500 w-16">{t('name')}:</span>
<span className="text-sm text-gray-600">{department?.department_name || 'N/A'}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-16">{t('code')}:</span>
<span className="text-sm text-gray-600">{department?.department_code || 'N/A'}</span>
</div>
</div>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M8 14v3m4-3v3m4-3v3M3 21h18M3 10h18M3 7l9-4 9 4M4 10h16v11H4V10z"></path>
</svg>
<span className="font-medium text-gray-700">{t('company')}:</span>
<span className="ml-2 text-sm text-gray-600">{company?.public_name || company?.formal_name || 'N/A'}</span>
</div>
</div>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
}
if (selectionList.type === 'occupant') {
const occupantType = item.occupant_types;
const buildPart = item.build_parts;
const build = buildPart?.build;
const enums = buildPart?.api_enum_dropdown_build_parts_part_type_idToapi_enum_dropdown;
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{occupantType?.occupant_code?.charAt(0) || 'O'}
</div>
<h3 className="text-lg font-bold text-gray-800">{t('occupant_type')}: {occupantType?.occupant_type}</h3>
</div>
<div className="space-y-2">
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-xs text-gray-700">{t('uuid')}:</span>
<span className="ml-2 font-mono text-xs text-gray-600">{item?.uu_id}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<span className="font-medium text-gray-700">{t('occupant_code')}:</span>
<span className="ml-2 font-semibold text-indigo-600">{occupantType?.occupant_code}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('building')}:</span>
<span className="ml-2 text-gray-600">{build?.build_name || 'Building'}</span>
</div>
<div className="flex items-center">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg>
<span className="font-medium text-gray-700">{t('type')}:</span>
<span className="ml-2 text-gray-600">{enums?.value}</span>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<svg className="w-4 h-4 text-indigo-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path>
</svg>
<span className="font-medium text-gray-700">{t('part_details')}</span>
</div>
<div className="grid grid-cols-2 gap-2 ml-6 mt-1">
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('code')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_code}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('no')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_no}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('level')}:</span>
<span className="text-sm text-gray-600">{buildPart?.part_level}</span>
</div>
<div className="flex items-center">
<span className="text-xs text-gray-500 w-12">{t('status')}:</span>
<span className={`text-sm font-medium ${buildPart?.human_livable ? 'text-green-600' : 'text-red-600'}`}>
{buildPart?.human_livable ? t('livable') : t('not_livable')}
</span>
</div>
</div>
</div>
</div>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
}
return (
<div
key={item.uu_id}
className={`rounded-2xl p-6 cursor-pointer transition-all duration-300 flex flex-col justify-between h-full shadow-lg border-2 ${selectedOption === item.uu_id
? 'bg-indigo-100 border-indigo-500 shadow-indigo-200 transform scale-[1.02]'
: 'bg-white/90 border-indigo-100 hover:bg-indigo-50 hover:border-indigo-300 hover:shadow-indigo-100'}`}
onClick={() => handleSelection(item.uu_id)}
>
<div>
<div className="flex items-center mb-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 flex items-center justify-center text-white font-bold text-lg mr-4">
{item.uu_id?.charAt(0) || 'S'}
</div>
<h3 className="text-lg font-bold text-gray-800">{selectionList.type || t('selection')}</h3>
</div>
<p className="text-gray-600 text-sm">{item.uu_id || t('id')}</p>
</div>
{selectedOption === item.uu_id && (
<div className="mt-4 flex justify-end">
<div className="w-6 h-6 rounded-full bg-indigo-500 flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M5 13l4 4L19 7"></path>
</svg>
</div>
</div>
)}
</div>
);
})}
</div>
<div className="mt-8 sm:mt-10 flex justify-center">
<button
className={`px-8 py-4 rounded-xl font-bold text-white transition-all duration-300 text-lg ${selectedOption
? 'bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 shadow-lg shadow-indigo-200 hover:shadow-indigo-300 transform hover:scale-105'
: 'bg-gray-400 cursor-not-allowed'}`}
disabled={!selectedOption || isLoading}
onClick={handleContinue}
>
{isLoading ? t('processing') : selectedOption ? t('continue') : t('select_option')}
</button>
</div>
</div>
</div>
</div>
</div>
);
);
} else {
return (
<>
<div className="min-h-screen bg-gradient-to-br from-indigo-50 via-white to-cyan-50 p-4 sm:p-6 md:p-8">
<div className="max-w-6xl mx-auto w-full h-full flex flex-col">
<div className="text-center mb-8 sm:mb-10 mt-4 sm:mt-6">
<div className="skeleton h-8 w-64 mx-auto mb-2"></div>
<div className="skeleton h-5 w-48 mx-auto"></div>
</div>
<div className="flex-grow flex flex-col">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6 flex-grow">
{/* Skeleton items to match the grid layout */}
<div className="rounded-2xl p-6 shadow-lg border-2 bg-white/90 border-indigo-100">
<div className="flex items-center mb-4">
<div className="skeleton w-12 h-12 rounded-xl mr-4"></div>
<div className="skeleton h-5 w-32"></div>
</div>
<div className="space-y-2">
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-24"></div>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<div className="skeleton h-4 w-20 mr-2"></div>
</div>
<div className="ml-6 mt-1 space-y-1">
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
</div>
</div>
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-32"></div>
</div>
</div>
<div className="mt-4 flex justify-end">
<div className="skeleton w-6 h-6 rounded-full"></div>
</div>
</div>
<div className="rounded-2xl p-6 shadow-lg border-2 bg-white/90 border-indigo-100">
<div className="flex items-center mb-4">
<div className="skeleton w-12 h-12 rounded-xl mr-4"></div>
<div className="skeleton h-5 w-32"></div>
</div>
<div className="space-y-2">
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-24"></div>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<div className="skeleton h-4 w-20 mr-2"></div>
</div>
<div className="ml-6 mt-1 space-y-1">
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
</div>
</div>
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-32"></div>
</div>
</div>
<div className="mt-4 flex justify-end">
<div className="skeleton w-6 h-6 rounded-full"></div>
</div>
</div>
<div className="rounded-2xl p-6 shadow-lg border-2 bg-white/90 border-indigo-100">
<div className="flex items-center mb-4">
<div className="skeleton w-12 h-12 rounded-xl mr-4"></div>
<div className="skeleton h-5 w-32"></div>
</div>
<div className="space-y-2">
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-24"></div>
</div>
<div className="pt-2 border-t border-gray-100 mt-2">
<div className="flex items-center mb-1">
<div className="skeleton h-4 w-20 mr-2"></div>
</div>
<div className="ml-6 mt-1 space-y-1">
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
<div className="flex items-center">
<div className="skeleton h-3 w-12 mr-2"></div>
<div className="skeleton h-3 w-20"></div>
</div>
</div>
</div>
<div className="flex items-center">
<div className="skeleton h-4 w-16 mr-2"></div>
<div className="skeleton h-4 w-32"></div>
</div>
</div>
<div className="mt-4 flex justify-end">
<div className="skeleton w-6 h-6 rounded-full"></div>
</div>
</div>
</div>
<div className="mt-8 sm:mt-10 flex justify-center">
<div className="skeleton h-14 w-48 rounded-xl"></div>
</div>
</div>
</div>
</div>
</>
)
}
}

View File

@ -2,10 +2,19 @@
import { Locale } from 'next-intl';
import { checkAccess, checkSelectionOnSelectPage } from '@/app/api/guards';
import SelectPageClient from './SelectPage';
import LocaleSwitcherServer from '@/components/LocaleSwitcherServer';
export default async function PageSelect({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
await checkAccess(locale as Locale);
await checkSelectionOnSelectPage(locale as Locale);
return <SelectPageClient />;
// await checkSelectionOnSelectPage(locale as Locale);
return (
<div>
<div className='absolute top-2 right-2'>
<LocaleSwitcherServer locale={locale} pathname="/login" />
</div>
<SelectPageClient />
</div>
);
}

View File

@ -6,18 +6,18 @@ export async function POST(req: NextRequest) {
try {
const { uuid } = await req.json();
console.log("Select attempt for UUID:", uuid);
const response = await doSelect({ uuid });
const response = await doSelect({ uuid });
console.log("Select response:", response);
if (response.status !== 200) {
console.log("Select failed with status:", response.status);
return NextResponse.json({ status: 401, message: "Select failed" });
}
const data = response.data as any;
const token = data.token;
console.log("Select token received:", token ? "[PRESENT]" : "[MISSING]");
if (!token) {
console.error("No token received from select response");
return NextResponse.json({ status: 500, message: "No token received" });

View File

@ -1,8 +1,7 @@
'use server';
import { fetchData } from "@/fetchers/fecther";
import { fetchData, fetchDataWithAccessToken } from "@/fetchers/fecther";
import { urlSelectEndpoint, urlLoginEndpoint } from "@/fetchers/urls";
import { LoginViaAccessKeys } from "@/fetchers/types/login/validations";
import { setCookieAccessToken, setCookieSelectToken } from "@/fetchers/token/cookies";
async function doLogin(payload: LoginViaAccessKeys) {
@ -20,7 +19,7 @@ async function doLogin(payload: LoginViaAccessKeys) {
}
async function doSelect(payload: { uuid: string }) {
const response = await fetchData(
const response = await fetchDataWithAccessToken(
urlSelectEndpoint,
payload,
"POST",

View File

@ -111,6 +111,23 @@ async function fetchDataWithToken<T>(
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Fetch data with authentication token
*/
async function fetchDataWithAccessToken<T>(
endpoint: string,
payload?: any,
method: HttpMethod = "POST",
cache: boolean = false,
timeout: number = DEFAULT_TIMEOUT
): Promise<ApiResponse<T>> {
const accessToken = await getPlainAccessToken();
console.log('accessToken', accessToken);
const headers = { ...defaultHeaders, acs: accessToken };
return coreFetch<T>(endpoint, { method, cache, timeout }, headers, payload);
}
/**
* Update data with authentication token and UUID
*/
@ -133,4 +150,4 @@ async function updateDataWithToken<T>(
);
}
export { fetchData, fetchDataWithToken, updateDataWithToken };
export { fetchData, fetchDataWithToken, fetchDataWithAccessToken, updateDataWithToken };

View File

@ -1,9 +1,9 @@
import Redis from "ioredis";
const redis = new Redis({
host: process.env.REDIS_HOST,
host: process.env.REDIS_HOST || "10.10.2.15",
port: parseInt(process.env.REDIS_PORT || "6379", 10),
password: process.env.REDIS_PASSWORD || "",
password: process.env.REDIS_PASSWORD || "your_strong_password_here",
db: parseInt(process.env.REDIS_DB || "0", 10),
connectTimeout: 5000,
maxRetriesPerRequest: 2,

View File

@ -10,7 +10,7 @@ POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=postgres
POSTGRES_HOST=10.10.2.14
POSTGRES_PORT=5432
POSTGRES_PORT=5432+
POSTGRES_ENGINE=postgresql+psycopg2
POSTGRES_POOL_PRE_PING=True
POSTGRES_POOL_SIZE=20
@ -18,7 +18,8 @@ POSTGRES_MAX_OVERFLOW=10
POSTGRES_POOL_RECYCLE=600
POSTGRES_POOL_TIMEOUT=30
POSTGRES_ECHO=True
DATABASE_URL="postgresql://postgres:password@10.10.2.14:5432/sample_db?schema=public"
#DATABASE_URL="postgresql://postgres:password@10.10.2.14:5432/sample_db?schema=public"
#DATABASE_URL="postgresql://postgres:password@10.10.2.14:5432/postgres?schema=public"
REDIS_HOST=10.10.2.15
REDIS_PASSWORD=your_strong_password_here