diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index f7c55709..131b9e1a 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -65,6 +65,7 @@ import workHistoryRoutes from "./routes/workHistoryRoutes"; // 작업 이력 관 import tableHistoryRoutes from "./routes/tableHistoryRoutes"; // 테이블 변경 이력 조회 import roleRoutes from "./routes/roleRoutes"; // 권한 그룹 관리 import numberingRuleController from "./controllers/numberingRuleController"; // 채번 규칙 관리 +import departmentRoutes from "./routes/departmentRoutes"; // 부서 관리 import { BatchSchedulerService } from "./services/batchSchedulerService"; // import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석 // import batchRoutes from "./routes/batchRoutes"; // 임시 주석 @@ -224,6 +225,7 @@ app.use("/api/work-history", workHistoryRoutes); // 작업 이력 관리 app.use("/api/table-history", tableHistoryRoutes); // 테이블 변경 이력 조회 app.use("/api/roles", roleRoutes); // 권한 그룹 관리 app.use("/api/numbering-rules", numberingRuleController); // 채번 규칙 관리 +app.use("/api/departments", departmentRoutes); // 부서 관리 // app.use("/api/collections", collectionRoutes); // 임시 주석 // app.use("/api/batch", batchRoutes); // 임시 주석 // app.use('/api/users', userRoutes); diff --git a/backend-node/src/controllers/adminController.ts b/backend-node/src/controllers/adminController.ts index b1638403..f79aec69 100644 --- a/backend-node/src/controllers/adminController.ts +++ b/backend-node/src/controllers/adminController.ts @@ -8,6 +8,7 @@ import config from "../config/environment"; import { AdminService } from "../services/adminService"; import { EncryptUtil } from "../utils/encryptUtil"; import { FileSystemManager } from "../utils/fileSystemManager"; +import { validateBusinessNumber } from "../utils/businessNumberValidator"; /** * 관리자 메뉴 목록 조회 @@ -609,9 +610,15 @@ export const getCompanyList = async ( // Raw Query로 회사 목록 조회 const companies = await query( - `SELECT - company_code, + ` SELECT + company_code, company_name, + business_registration_number, + representative_name, + representative_phone, + email, + website, + address, status, writer, regdate @@ -1659,9 +1666,15 @@ export async function getCompanyListFromDB( // Raw Query로 회사 목록 조회 const companies = await query( - `SELECT - company_code, + ` SELECT + company_code, company_name, + business_registration_number, + representative_name, + representative_phone, + email, + website, + address, writer, regdate, status @@ -2440,6 +2453,25 @@ export const createCompany = async ( [company_name.trim()] ); + // 사업자등록번호 유효성 검증 + const businessNumberValidation = validateBusinessNumber( + req.body.business_registration_number?.trim() || "" + ); + if (!businessNumberValidation.isValid) { + res.status(400).json({ + success: false, + message: businessNumberValidation.message, + errorCode: "INVALID_BUSINESS_NUMBER", + }); + return; + } + + // Raw Query로 사업자등록번호 중복 체크 + const existingBusinessNumber = await queryOne( + `SELECT company_code FROM company_mng WHERE business_registration_number = $1`, + [req.body.business_registration_number?.trim()] + ); + if (existingCompany) { res.status(400).json({ success: false, @@ -2449,6 +2481,15 @@ export const createCompany = async ( return; } + if (existingBusinessNumber) { + res.status(400).json({ + success: false, + message: "이미 등록된 사업자등록번호입니다.", + errorCode: "DUPLICATE_BUSINESS_NUMBER", + }); + return; + } + // PostgreSQL 클라이언트 생성 (복잡한 코드 생성 쿼리용) const client = new Client({ connectionString: @@ -2474,11 +2515,17 @@ export const createCompany = async ( const insertQuery = ` INSERT INTO company_mng ( company_code, - company_name, + company_name, + business_registration_number, + representative_name, + representative_phone, + email, + website, + address, writer, regdate, status - ) VALUES ($1, $2, $3, $4, $5) + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING * `; @@ -2488,6 +2535,12 @@ export const createCompany = async ( const insertValues = [ companyCode, company_name.trim(), + req.body.business_registration_number?.trim() || null, + req.body.representative_name?.trim() || null, + req.body.representative_phone?.trim() || null, + req.body.email?.trim() || null, + req.body.website?.trim() || null, + req.body.address?.trim() || null, writer, new Date(), "active", @@ -2552,7 +2605,16 @@ export const updateCompany = async ( ): Promise => { try { const { companyCode } = req.params; - const { company_name, status } = req.body; + const { + company_name, + business_registration_number, + representative_name, + representative_phone, + email, + website, + address, + status, + } = req.body; logger.info("회사 정보 수정 요청", { companyCode, @@ -2586,13 +2648,61 @@ export const updateCompany = async ( return; } + // 사업자등록번호 중복 체크 및 유효성 검증 (자기 자신 제외) + if (business_registration_number && business_registration_number.trim()) { + // 유효성 검증 + const businessNumberValidation = validateBusinessNumber(business_registration_number.trim()); + if (!businessNumberValidation.isValid) { + res.status(400).json({ + success: false, + message: businessNumberValidation.message, + errorCode: "INVALID_BUSINESS_NUMBER", + }); + return; + } + + // 중복 체크 + const duplicateBusinessNumber = await queryOne( + `SELECT company_code FROM company_mng + WHERE business_registration_number = $1 AND company_code != $2`, + [business_registration_number.trim(), companyCode] + ); + + if (duplicateBusinessNumber) { + res.status(400).json({ + success: false, + message: "이미 등록된 사업자등록번호입니다.", + errorCode: "DUPLICATE_BUSINESS_NUMBER", + }); + return; + } + } + // Raw Query로 회사 정보 수정 const result = await query( `UPDATE company_mng - SET company_name = $1, status = $2 - WHERE company_code = $3 + SET + company_name = $1, + business_registration_number = $2, + representative_name = $3, + representative_phone = $4, + email = $5, + website = $6, + address = $7, + status = $8 + WHERE company_code = $9 RETURNING *`, - [company_name.trim(), status || "active", companyCode] + [ + company_name.trim(), + business_registration_number?.trim() || null, + representative_name?.trim() || null, + representative_phone?.trim() || null, + email?.trim() || null, + website?.trim() || null, + address?.trim() || null, + status || "active", + companyCode, + ] ); if (result.length === 0) { diff --git a/backend-node/src/controllers/departmentController.ts b/backend-node/src/controllers/departmentController.ts new file mode 100644 index 00000000..9e3f0b6a --- /dev/null +++ b/backend-node/src/controllers/departmentController.ts @@ -0,0 +1,534 @@ +import { Response } from "express"; +import { logger } from "../utils/logger"; +import { AuthenticatedRequest } from "../types/auth"; +import { ApiResponse } from "../types/common"; +import { query, queryOne } from "../database/db"; + +/** + * 부서 목록 조회 (회사별) + */ +export async function getDepartments(req: AuthenticatedRequest, res: Response): Promise { + try { + const { companyCode } = req.params; + const userCompanyCode = req.user?.companyCode; + + logger.info("부서 목록 조회", { companyCode, userCompanyCode }); + + // 최고 관리자가 아니면 자신의 회사만 조회 가능 + if (userCompanyCode !== "*" && userCompanyCode !== companyCode) { + res.status(403).json({ + success: false, + message: "해당 회사의 부서를 조회할 권한이 없습니다.", + }); + return; + } + + // 부서 목록 조회 (부서원 수 포함) + const departments = await query(` + SELECT + d.dept_code, + d.dept_name, + d.company_code, + d.parent_dept_code, + COUNT(DISTINCT ud.user_id) as member_count + FROM dept_info d + LEFT JOIN user_dept ud ON d.dept_code = ud.dept_code + WHERE d.company_code = $1 + GROUP BY d.dept_code, d.dept_name, d.company_code, d.parent_dept_code + ORDER BY d.dept_name + `, [companyCode]); + + // 응답 형식 변환 + const formattedDepartments = departments.map((dept) => ({ + dept_code: dept.dept_code, + dept_name: dept.dept_name, + company_code: dept.company_code, + parent_dept_code: dept.parent_dept_code, + memberCount: parseInt(dept.member_count || "0"), + })); + + res.status(200).json({ + success: true, + data: formattedDepartments, + }); + } catch (error) { + logger.error("부서 목록 조회 실패", error); + res.status(500).json({ + success: false, + message: "부서 목록 조회 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서 상세 조회 + */ +export async function getDepartment(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode } = req.params; + + const department = await queryOne(` + SELECT + dept_code, + dept_name, + company_code, + parent_dept_code + FROM dept_info + WHERE dept_code = $1 + `, [deptCode]); + + if (!department) { + res.status(404).json({ + success: false, + message: "부서를 찾을 수 없습니다.", + }); + return; + } + + res.status(200).json({ + success: true, + data: department, + }); + } catch (error) { + logger.error("부서 상세 조회 실패", error); + res.status(500).json({ + success: false, + message: "부서 조회 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서 생성 + */ +export async function createDepartment(req: AuthenticatedRequest, res: Response): Promise { + try { + const { companyCode } = req.params; + const { dept_name, parent_dept_code } = req.body; + + if (!dept_name || !dept_name.trim()) { + res.status(400).json({ + success: false, + message: "부서명을 입력해주세요.", + }); + return; + } + + // 같은 회사 내 중복 부서명 확인 + const duplicate = await queryOne(` + SELECT dept_code, dept_name + FROM dept_info + WHERE company_code = $1 AND dept_name = $2 + `, [companyCode, dept_name.trim()]); + + if (duplicate) { + res.status(409).json({ + success: false, + message: `"${dept_name}" 부서가 이미 존재합니다.`, + isDuplicate: true, + }); + return; + } + + // 회사 이름 조회 + const company = await queryOne(` + SELECT company_name FROM company_mng WHERE company_code = $1 + `, [companyCode]); + + const companyName = company?.company_name || companyCode; + + // 부서 코드 생성 (전역 카운트: DEPT_1, DEPT_2, ...) + const codeResult = await queryOne(` + SELECT COALESCE(MAX(CAST(SUBSTRING(dept_code FROM 6) AS INTEGER)), 0) + 1 as next_number + FROM dept_info + WHERE dept_code ~ '^DEPT_[0-9]+$' + `); + + const nextNumber = codeResult?.next_number || 1; + const deptCode = `DEPT_${nextNumber}`; + + // 부서 생성 + const result = await query(` + INSERT INTO dept_info ( + dept_code, + dept_name, + company_code, + company_name, + parent_dept_code, + status, + regdate + ) VALUES ($1, $2, $3, $4, $5, $6, NOW()) + RETURNING * + `, [ + deptCode, + dept_name.trim(), + companyCode, + companyName, + parent_dept_code || null, + 'active', + ]); + + logger.info("부서 생성 성공", { deptCode, dept_name }); + + res.status(201).json({ + success: true, + message: "부서가 생성되었습니다.", + data: result[0], + }); + } catch (error) { + logger.error("부서 생성 실패", error); + res.status(500).json({ + success: false, + message: "부서 생성 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서 수정 + */ +export async function updateDepartment(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode } = req.params; + const { dept_name, parent_dept_code } = req.body; + + if (!dept_name || !dept_name.trim()) { + res.status(400).json({ + success: false, + message: "부서명을 입력해주세요.", + }); + return; + } + + const result = await query(` + UPDATE dept_info + SET + dept_name = $1, + parent_dept_code = $2 + WHERE dept_code = $3 + RETURNING * + `, [dept_name.trim(), parent_dept_code || null, deptCode]); + + if (result.length === 0) { + res.status(404).json({ + success: false, + message: "부서를 찾을 수 없습니다.", + }); + return; + } + + logger.info("부서 수정 성공", { deptCode }); + + res.status(200).json({ + success: true, + message: "부서가 수정되었습니다.", + data: result[0], + }); + } catch (error) { + logger.error("부서 수정 실패", error); + res.status(500).json({ + success: false, + message: "부서 수정 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서 삭제 + */ +export async function deleteDepartment(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode } = req.params; + + // 하위 부서 확인 + const hasChildren = await queryOne(` + SELECT COUNT(*) as count + FROM dept_info + WHERE parent_dept_code = $1 + `, [deptCode]); + + if (parseInt(hasChildren?.count || "0") > 0) { + res.status(400).json({ + success: false, + message: "하위 부서가 있는 부서는 삭제할 수 없습니다. 먼저 하위 부서를 삭제해주세요.", + }); + return; + } + + // 부서원 삭제 (부서 삭제 전에 먼저 삭제) + const deletedMembers = await query(` + DELETE FROM user_dept + WHERE dept_code = $1 + RETURNING user_id + `, [deptCode]); + + const memberCount = deletedMembers.length; + + // 부서 삭제 + const result = await query(` + DELETE FROM dept_info + WHERE dept_code = $1 + RETURNING dept_code, dept_name + `, [deptCode]); + + if (result.length === 0) { + res.status(404).json({ + success: false, + message: "부서를 찾을 수 없습니다.", + }); + return; + } + + logger.info("부서 삭제 성공", { + deptCode, + deptName: result[0].dept_name, + deletedMemberCount: memberCount + }); + + res.status(200).json({ + success: true, + message: memberCount > 0 + ? `부서가 삭제되었습니다. (부서원 ${memberCount}명 제외됨)` + : "부서가 삭제되었습니다.", + }); + } catch (error) { + logger.error("부서 삭제 실패", error); + res.status(500).json({ + success: false, + message: "부서 삭제 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서원 목록 조회 + */ +export async function getDepartmentMembers(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode } = req.params; + + const members = await query(` + SELECT + u.user_id, + u.user_name, + u.email, + u.tel as phone, + u.cell_phone, + u.position_name, + ud.dept_code, + d.dept_name, + ud.is_primary + FROM user_dept ud + JOIN user_info u ON ud.user_id = u.user_id + JOIN dept_info d ON ud.dept_code = d.dept_code + WHERE ud.dept_code = $1 + ORDER BY ud.is_primary DESC, u.user_name + `, [deptCode]); + + res.status(200).json({ + success: true, + data: members, + }); + } catch (error) { + logger.error("부서원 목록 조회 실패", error); + res.status(500).json({ + success: false, + message: "부서원 목록 조회 중 오류가 발생했습니다.", + }); + } +} + +/** + * 사용자 검색 (부서원 추가용) + */ +export async function searchUsers(req: AuthenticatedRequest, res: Response): Promise { + try { + const { companyCode } = req.params; + const { search } = req.query; + + if (!search || typeof search !== 'string') { + res.status(400).json({ + success: false, + message: "검색어를 입력해주세요.", + }); + return; + } + + // 사용자 검색 (ID 또는 이름) + const users = await query(` + SELECT + user_id, + user_name, + email, + position_name, + company_code + FROM user_info + WHERE company_code = $1 + AND ( + user_id ILIKE $2 OR + user_name ILIKE $2 + ) + ORDER BY user_name + LIMIT 20 + `, [companyCode, `%${search}%`]); + + res.status(200).json({ + success: true, + data: users, + }); + } catch (error) { + logger.error("사용자 검색 실패", error); + res.status(500).json({ + success: false, + message: "사용자 검색 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서원 추가 + */ +export async function addDepartmentMember(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode } = req.params; + const { user_id } = req.body; + + if (!user_id) { + res.status(400).json({ + success: false, + message: "사용자 ID를 입력해주세요.", + }); + return; + } + + // 사용자 존재 확인 + const user = await queryOne(` + SELECT user_id, user_name + FROM user_info + WHERE user_id = $1 + `, [user_id]); + + if (!user) { + res.status(404).json({ + success: false, + message: "사용자를 찾을 수 없습니다.", + }); + return; + } + + // 이미 부서원인지 확인 + const existing = await queryOne(` + SELECT * + FROM user_dept + WHERE user_id = $1 AND dept_code = $2 + `, [user_id, deptCode]); + + if (existing) { + res.status(409).json({ + success: false, + message: "이미 해당 부서의 부서원입니다.", + isDuplicate: true, + }); + return; + } + + // 주 부서가 있는지 확인 + const hasPrimary = await queryOne(` + SELECT * + FROM user_dept + WHERE user_id = $1 AND is_primary = true + `, [user_id]); + + // 부서원 추가 + await query(` + INSERT INTO user_dept (user_id, dept_code, is_primary, created_at) + VALUES ($1, $2, $3, NOW()) + `, [user_id, deptCode, !hasPrimary]); + + logger.info("부서원 추가 성공", { user_id, deptCode }); + + res.status(201).json({ + success: true, + message: "부서원이 추가되었습니다.", + }); + } catch (error) { + logger.error("부서원 추가 실패", error); + res.status(500).json({ + success: false, + message: "부서원 추가 중 오류가 발생했습니다.", + }); + } +} + +/** + * 부서원 제거 + */ +export async function removeDepartmentMember(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode, userId } = req.params; + + const result = await query(` + DELETE FROM user_dept + WHERE user_id = $1 AND dept_code = $2 + RETURNING * + `, [userId, deptCode]); + + if (result.length === 0) { + res.status(404).json({ + success: false, + message: "해당 부서원을 찾을 수 없습니다.", + }); + return; + } + + logger.info("부서원 제거 성공", { userId, deptCode }); + + res.status(200).json({ + success: true, + message: "부서원이 제거되었습니다.", + }); + } catch (error) { + logger.error("부서원 제거 실패", error); + res.status(500).json({ + success: false, + message: "부서원 제거 중 오류가 발생했습니다.", + }); + } +} + +/** + * 주 부서 설정 + */ +export async function setPrimaryDepartment(req: AuthenticatedRequest, res: Response): Promise { + try { + const { deptCode, userId } = req.params; + + // 다른 부서의 주 부서 해제 + await query(` + UPDATE user_dept + SET is_primary = false + WHERE user_id = $1 + `, [userId]); + + // 해당 부서를 주 부서로 설정 + await query(` + UPDATE user_dept + SET is_primary = true + WHERE user_id = $1 AND dept_code = $2 + `, [userId, deptCode]); + + logger.info("주 부서 설정 성공", { userId, deptCode }); + + res.status(200).json({ + success: true, + message: "주 부서가 설정되었습니다.", + }); + } catch (error) { + logger.error("주 부서 설정 실패", error); + res.status(500).json({ + success: false, + message: "주 부서 설정 중 오류가 발생했습니다.", + }); + } +} + diff --git a/backend-node/src/controllers/dynamicFormController.ts b/backend-node/src/controllers/dynamicFormController.ts index 47ee4e94..9b8ef6fc 100644 --- a/backend-node/src/controllers/dynamicFormController.ts +++ b/backend-node/src/controllers/dynamicFormController.ts @@ -12,6 +12,14 @@ export const saveFormData = async ( const { companyCode, userId } = req.user as any; const { screenId, tableName, data } = req.body; + // 🔍 디버깅: 사용자 정보 확인 + console.log("🔍 [saveFormData] 사용자 정보:", { + userId, + companyCode, + reqUser: req.user, + dataWriter: data.writer, + }); + // 필수 필드 검증 (screenId는 0일 수 있으므로 undefined 체크) if (screenId === undefined || screenId === null || !tableName || !data) { return res.status(400).json({ @@ -25,9 +33,12 @@ export const saveFormData = async ( ...data, created_by: userId, updated_by: userId, + writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정 screen_id: screenId, }; + console.log("✅ [saveFormData] 최종 writer 값:", formDataWithMeta.writer); + // company_code는 사용자가 명시적으로 입력한 경우에만 추가 if (data.company_code !== undefined) { formDataWithMeta.company_code = data.company_code; @@ -86,6 +97,7 @@ export const saveFormDataEnhanced = async ( ...data, created_by: userId, updated_by: userId, + writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정 screen_id: screenId, }; @@ -134,6 +146,7 @@ export const updateFormData = async ( const formDataWithMeta = { ...data, updated_by: userId, + writer: data.writer || userId, // ✅ writer가 없으면 userId로 설정 updated_at: new Date(), }; @@ -186,6 +199,7 @@ export const updateFormDataPartial = async ( const newDataWithMeta = { ...newData, updated_by: userId, + writer: newData.writer || userId, // ✅ writer가 없으면 userId로 설정 }; const result = await dynamicFormService.updateFormDataPartial( diff --git a/backend-node/src/controllers/tableManagementController.ts b/backend-node/src/controllers/tableManagementController.ts index d7b2bd74..9661ab0a 100644 --- a/backend-node/src/controllers/tableManagementController.ts +++ b/backend-node/src/controllers/tableManagementController.ts @@ -12,6 +12,7 @@ import { ColumnListResponse, ColumnSettingsResponse, } from "../types/tableManagement"; +import { query } from "../database/db"; // 🆕 query 함수 import /** * 테이블 목록 조회 @@ -506,7 +507,91 @@ export async function updateColumnInputType( } /** - * 테이블 데이터 조회 (페이징 + 검색) + * 단일 레코드 조회 (자동 입력용) + */ +export async function getTableRecord( + req: AuthenticatedRequest, + res: Response +): Promise { + try { + const { tableName } = req.params; + const { filterColumn, filterValue, displayColumn } = req.body; + + logger.info(`=== 단일 레코드 조회 시작: ${tableName} ===`); + logger.info(`필터: ${filterColumn} = ${filterValue}`); + logger.info(`표시 컬럼: ${displayColumn}`); + + if (!tableName || !filterColumn || !filterValue || !displayColumn) { + const response: ApiResponse = { + success: false, + message: "필수 파라미터가 누락되었습니다.", + error: { + code: "MISSING_PARAMETERS", + details: + "tableName, filterColumn, filterValue, displayColumn이 필요합니다.", + }, + }; + res.status(400).json(response); + return; + } + + const tableManagementService = new TableManagementService(); + + // 단일 레코드 조회 (WHERE filterColumn = filterValue) + const result = await tableManagementService.getTableData(tableName, { + page: 1, + size: 1, + search: { + [filterColumn]: filterValue, + }, + }); + + if (!result.data || result.data.length === 0) { + const response: ApiResponse = { + success: false, + message: "데이터를 찾을 수 없습니다.", + error: { + code: "NOT_FOUND", + details: `${filterColumn} = ${filterValue}에 해당하는 데이터가 없습니다.`, + }, + }; + res.status(404).json(response); + return; + } + + const record = result.data[0]; + const displayValue = record[displayColumn]; + + logger.info(`레코드 조회 완료: ${displayColumn} = ${displayValue}`); + + const response: ApiResponse<{ value: any; record: any }> = { + success: true, + message: "레코드를 성공적으로 조회했습니다.", + data: { + value: displayValue, + record: record, + }, + }; + + res.status(200).json(response); + } catch (error) { + logger.error("레코드 조회 중 오류 발생:", error); + + const response: ApiResponse = { + success: false, + message: "레코드 조회 중 오류가 발생했습니다.", + error: { + code: "RECORD_ERROR", + details: error instanceof Error ? error.message : "Unknown error", + }, + }; + + res.status(500).json(response); + } +} + +/** + * 테이블 데이터 조회 (페이징 + 검색 + 필터링) */ export async function getTableData( req: AuthenticatedRequest, @@ -520,12 +605,14 @@ export async function getTableData( search = {}, sortBy, sortOrder = "asc", + autoFilter, // 🆕 자동 필터 설정 추가 (컴포넌트에서 직접 전달) } = req.body; logger.info(`=== 테이블 데이터 조회 시작: ${tableName} ===`); logger.info(`페이징: page=${page}, size=${size}`); logger.info(`검색 조건:`, search); logger.info(`정렬: ${sortBy} ${sortOrder}`); + logger.info(`자동 필터:`, autoFilter); // 🆕 if (!tableName) { const response: ApiResponse = { @@ -542,11 +629,35 @@ export async function getTableData( const tableManagementService = new TableManagementService(); + // 🆕 현재 사용자 필터 적용 + let enhancedSearch = { ...search }; + if (autoFilter?.enabled && req.user) { + const filterColumn = autoFilter.filterColumn || "company_code"; + const userField = autoFilter.userField || "companyCode"; + const userValue = (req.user as any)[userField]; + + if (userValue) { + enhancedSearch[filterColumn] = userValue; + + logger.info("🔍 현재 사용자 필터 적용:", { + filterColumn, + userField, + userValue, + tableName, + }); + } else { + logger.warn("⚠️ 사용자 정보 필드 값 없음:", { + userField, + user: req.user, + }); + } + } + // 데이터 조회 const result = await tableManagementService.getTableData(tableName, { page: parseInt(page), size: parseInt(size), - search, + search: enhancedSearch, // 🆕 필터가 적용된 search 사용 sortBy, sortOrder, }); @@ -1216,9 +1327,7 @@ export async function getLogData( originalId: originalId as string, }); - logger.info( - `로그 데이터 조회 완료: ${tableName}_log, ${result.total}건` - ); + logger.info(`로그 데이터 조회 완료: ${tableName}_log, ${result.total}건`); const response: ApiResponse = { success: true, @@ -1254,7 +1363,9 @@ export async function toggleLogTable( const { tableName } = req.params; const { isActive } = req.body; - logger.info(`=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===`); + logger.info( + `=== 로그 테이블 토글: ${tableName}, isActive: ${isActive} ===` + ); if (!tableName) { const response: ApiResponse = { @@ -1288,9 +1399,7 @@ export async function toggleLogTable( isActive === "Y" || isActive === true ); - logger.info( - `로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}` - ); + logger.info(`로그 테이블 토글 완료: ${tableName}, isActive: ${isActive}`); const response: ApiResponse = { success: true, diff --git a/backend-node/src/routes/departmentRoutes.ts b/backend-node/src/routes/departmentRoutes.ts new file mode 100644 index 00000000..52cc309e --- /dev/null +++ b/backend-node/src/routes/departmentRoutes.ts @@ -0,0 +1,46 @@ +import { Router } from "express"; +import { authenticateToken } from "../middleware/authMiddleware"; +import * as departmentController from "../controllers/departmentController"; + +const router = Router(); + +// 인증 미들웨어 적용 +router.use(authenticateToken); + +/** + * 부서 관리 API 라우트 + * 기본 경로: /api/departments + */ + +// 부서 목록 조회 (회사별) +router.get("/companies/:companyCode/departments", departmentController.getDepartments); + +// 부서 상세 조회 +router.get("/:deptCode", departmentController.getDepartment); + +// 부서 생성 +router.post("/companies/:companyCode/departments", departmentController.createDepartment); + +// 부서 수정 +router.put("/:deptCode", departmentController.updateDepartment); + +// 부서 삭제 +router.delete("/:deptCode", departmentController.deleteDepartment); + +// 부서원 목록 조회 +router.get("/:deptCode/members", departmentController.getDepartmentMembers); + +// 사용자 검색 (부서원 추가용) +router.get("/companies/:companyCode/users/search", departmentController.searchUsers); + +// 부서원 추가 +router.post("/:deptCode/members", departmentController.addDepartmentMember); + +// 부서원 제거 +router.delete("/:deptCode/members/:userId", departmentController.removeDepartmentMember); + +// 주 부서 설정 +router.put("/:deptCode/members/:userId/primary", departmentController.setPrimaryDepartment); + +export default router; + diff --git a/backend-node/src/routes/tableManagementRoutes.ts b/backend-node/src/routes/tableManagementRoutes.ts index 5e5ddf38..9840c9c4 100644 --- a/backend-node/src/routes/tableManagementRoutes.ts +++ b/backend-node/src/routes/tableManagementRoutes.ts @@ -11,6 +11,7 @@ import { updateColumnInputType, updateTableLabel, getTableData, + getTableRecord, // 🆕 단일 레코드 조회 addTableData, editTableData, deleteTableData, @@ -134,6 +135,12 @@ router.get("/health", checkDatabaseConnection); */ router.post("/tables/:tableName/data", getTableData); +/** + * 단일 레코드 조회 (자동 입력용) + * POST /api/table-management/tables/:tableName/record + */ +router.post("/tables/:tableName/record", getTableRecord); + /** * 테이블 데이터 추가 * POST /api/table-management/tables/:tableName/add diff --git a/backend-node/src/services/ddlExecutionService.ts b/backend-node/src/services/ddlExecutionService.ts index 37659bcf..2ed01231 100644 --- a/backend-node/src/services/ddlExecutionService.ts +++ b/backend-node/src/services/ddlExecutionService.ts @@ -363,7 +363,7 @@ export class DDLExecutionService { "id" varchar(500) PRIMARY KEY DEFAULT gen_random_uuid()::text, "created_date" timestamp DEFAULT now(), "updated_date" timestamp DEFAULT now(), - "writer" varchar(500), + "writer" varchar(500) DEFAULT NULL, "company_code" varchar(500)`; // 최종 CREATE TABLE 쿼리 diff --git a/backend-node/src/services/tableManagementService.ts b/backend-node/src/services/tableManagementService.ts index 608f8b96..df67e2fe 100644 --- a/backend-node/src/services/tableManagementService.ts +++ b/backend-node/src/services/tableManagementService.ts @@ -1502,6 +1502,9 @@ export class TableManagementService { LIMIT $${paramIndex} OFFSET $${paramIndex + 1} `; + logger.info(`🔍 실행할 SQL: ${dataQuery}`); + logger.info(`🔍 파라미터: ${JSON.stringify([...searchValues, size, offset])}`); + let data = await query(dataQuery, [...searchValues, size, offset]); // 🎯 파일 컬럼이 있으면 파일 정보 보강 diff --git a/backend-node/src/utils/businessNumberValidator.ts b/backend-node/src/utils/businessNumberValidator.ts new file mode 100644 index 00000000..92385f28 --- /dev/null +++ b/backend-node/src/utils/businessNumberValidator.ts @@ -0,0 +1,52 @@ +/** + * 사업자등록번호 유효성 검사 유틸리티 (백엔드) + */ + +/** + * 사업자등록번호 포맷 검증 + */ +export function validateBusinessNumberFormat(value: string): boolean { + if (!value || value.trim() === "") { + return false; + } + + // 하이픈 제거 + const cleaned = value.replace(/-/g, ""); + + // 숫자 10자리인지 확인 + if (!/^\d{10}$/.test(cleaned)) { + return false; + } + + return true; +} + +/** + * 사업자등록번호 종합 검증 (포맷만 검사) + * 실제 국세청 검증은 API 호출로 처리하는 것을 권장 + */ +export function validateBusinessNumber(value: string): { + isValid: boolean; + message: string; +} { + if (!value || value.trim() === "") { + return { + isValid: false, + message: "사업자등록번호를 입력해주세요.", + }; + } + + if (!validateBusinessNumberFormat(value)) { + return { + isValid: false, + message: "사업자등록번호는 10자리 숫자여야 합니다.", + }; + } + + // 포맷만 검증하고 통과 + return { + isValid: true, + message: "", + }; +} + diff --git a/frontend/app/(main)/admin/company/[companyCode]/departments/page.tsx b/frontend/app/(main)/admin/company/[companyCode]/departments/page.tsx new file mode 100644 index 00000000..7854e6ee --- /dev/null +++ b/frontend/app/(main)/admin/company/[companyCode]/departments/page.tsx @@ -0,0 +1,12 @@ +"use client"; + +import { useParams } from "next/navigation"; +import { DepartmentManagement } from "@/components/admin/department/DepartmentManagement"; + +export default function DepartmentManagementPage() { + const params = useParams(); + const companyCode = params.companyCode as string; + + return ; +} + diff --git a/frontend/app/(main)/screens/[screenId]/page.tsx b/frontend/app/(main)/screens/[screenId]/page.tsx index 6930c583..5a4b3352 100644 --- a/frontend/app/(main)/screens/[screenId]/page.tsx +++ b/frontend/app/(main)/screens/[screenId]/page.tsx @@ -151,6 +151,57 @@ export default function ScreenViewPage() { } }, [screenId]); + // 🆕 autoFill 자동 입력 초기화 + useEffect(() => { + const initAutoFill = async () => { + if (!layout || !layout.components || !user) { + return; + } + + for (const comp of layout.components) { + // type: "component" 또는 type: "widget" 모두 처리 + if (comp.type === 'widget' || comp.type === 'component') { + const widget = comp as any; + const fieldName = widget.columnName || widget.id; + + // autoFill 처리 + if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { + const autoFillConfig = widget.autoFill || (comp as any).autoFill; + const currentValue = formData[fieldName]; + + if (currentValue === undefined || currentValue === '') { + const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; + + // 사용자 정보에서 필터 값 가져오기 + const userValue = user?.[userField as keyof typeof user]; + + if (userValue && sourceTable && filterColumn && displayColumn) { + try { + const { tableTypeApi } = await import("@/lib/api/screen"); + const result = await tableTypeApi.getTableRecord( + sourceTable, + filterColumn, + userValue, + displayColumn + ); + + setFormData((prev) => ({ + ...prev, + [fieldName]: result.value, + })); + } catch (error) { + console.error(`autoFill 조회 실패: ${fieldName}`, error); + } + } + } + } + } + } + }; + + initAutoFill(); + }, [layout, user]); + // 캔버스 비율 조정 (사용자 화면에 맞게 자동 스케일) - 모바일에서는 비활성화 useEffect(() => { // 모바일 환경에서는 스케일 조정 비활성화 (반응형만 작동) diff --git a/frontend/components/admin/CompanyFormModal.tsx b/frontend/components/admin/CompanyFormModal.tsx index 7b51ac6a..dd87140e 100644 --- a/frontend/components/admin/CompanyFormModal.tsx +++ b/frontend/components/admin/CompanyFormModal.tsx @@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; import { LoadingSpinner } from "@/components/common/LoadingSpinner"; +import { validateBusinessNumber, formatBusinessNumber } from "@/lib/validation/businessNumber"; interface CompanyFormModalProps { modalState: CompanyModalState; @@ -29,6 +30,7 @@ export function CompanyFormModal({ onClearError, }: CompanyFormModalProps) { const [isSaving, setIsSaving] = useState(false); + const [businessNumberError, setBusinessNumberError] = useState(""); // 모달이 열려있지 않으면 렌더링하지 않음 if (!modalState.isOpen) return null; @@ -36,15 +38,43 @@ export function CompanyFormModal({ const { mode, formData, selectedCompany } = modalState; const isEditMode = mode === "edit"; + // 사업자등록번호 변경 처리 + const handleBusinessNumberChange = (value: string) => { + // 자동 포맷팅 + const formatted = formatBusinessNumber(value); + onFormChange("business_registration_number", formatted); + + // 유효성 검사 (10자리가 다 입력되었을 때만) + const cleaned = formatted.replace(/-/g, ""); + if (cleaned.length === 10) { + const validation = validateBusinessNumber(formatted); + setBusinessNumberError(validation.isValid ? "" : validation.message); + } else if (cleaned.length < 10 && businessNumberError) { + // 10자리 미만이면 에러 초기화 + setBusinessNumberError(""); + } + }; + // 저장 처리 const handleSave = async () => { - // 입력값 검증 + // 입력값 검증 (필수 필드) if (!formData.company_name.trim()) { return; } + if (!formData.business_registration_number.trim()) { + return; + } + + // 사업자등록번호 최종 검증 + const validation = validateBusinessNumber(formData.business_registration_number); + if (!validation.isValid) { + setBusinessNumberError(validation.message); + return; + } setIsSaving(true); onClearError(); + setBusinessNumberError(""); try { const success = await onSave(); @@ -81,7 +111,7 @@ export function CompanyFormModal({
- {/* 회사명 입력 */} + {/* 회사명 입력 (필수) */}
+ {/* 사업자등록번호 입력 (필수) */} +
+ + handleBusinessNumberChange(e.target.value)} + placeholder="000-00-00000" + disabled={isLoading || isSaving} + maxLength={12} + className={businessNumberError ? "border-destructive" : ""} + /> + {businessNumberError ? ( +

{businessNumberError}

+ ) : ( +

10자리 숫자 (자동 하이픈 추가)

+ )} +
+ + {/* 대표자명 입력 */} +
+ + onFormChange("representative_name", e.target.value)} + placeholder="대표자명을 입력하세요" + disabled={isLoading || isSaving} + /> +
+ + {/* 대표 연락처 입력 */} +
+ + onFormChange("representative_phone", e.target.value)} + placeholder="010-0000-0000" + disabled={isLoading || isSaving} + type="tel" + /> +
+ + {/* 이메일 입력 */} +
+ + onFormChange("email", e.target.value)} + placeholder="company@example.com" + disabled={isLoading || isSaving} + type="email" + /> +
+ + {/* 웹사이트 입력 */} +
+ + onFormChange("website", e.target.value)} + placeholder="https://example.com" + disabled={isLoading || isSaving} + type="url" + /> +
+ + {/* 회사 주소 입력 */} +
+ + onFormChange("address", e.target.value)} + placeholder="서울특별시 강남구..." + disabled={isLoading || isSaving} + /> +
+ {/* 에러 메시지 */} {error && ( -
-

{error}

+
+

{error}

)} @@ -129,7 +243,13 @@ export function CompanyFormModal({ + + + + + ); +}; + diff --git a/frontend/components/screen/InteractiveDataTable.tsx b/frontend/components/screen/InteractiveDataTable.tsx index e05cc973..f4123169 100644 --- a/frontend/components/screen/InteractiveDataTable.tsx +++ b/frontend/components/screen/InteractiveDataTable.tsx @@ -485,6 +485,7 @@ export const InteractiveDataTable: React.FC = ({ page, size: pageSize, search: searchParams, + autoFilter: component.autoFilter, // 🆕 자동 필터 설정 전달 }); setData(result.data); @@ -576,7 +577,7 @@ export const InteractiveDataTable: React.FC = ({ setLoading(false); } }, - [component.tableName, pageSize], + [component.tableName, pageSize, component.autoFilter], // 🆕 autoFilter 추가 ); // 현재 사용자 정보 로드 diff --git a/frontend/components/screen/InteractiveScreenViewer.tsx b/frontend/components/screen/InteractiveScreenViewer.tsx index 45b263d6..4b292541 100644 --- a/frontend/components/screen/InteractiveScreenViewer.tsx +++ b/frontend/components/screen/InteractiveScreenViewer.tsx @@ -36,7 +36,7 @@ import { InteractiveDataTable } from "./InteractiveDataTable"; import { FileUpload } from "./widgets/FileUpload"; import { dynamicFormApi, DynamicFormData } from "@/lib/api/dynamicForm"; import { useParams } from "next/navigation"; -import { screenApi } from "@/lib/api/screen"; +import { screenApi, tableTypeApi } from "@/lib/api/screen"; import { DynamicWebTypeRenderer } from "@/lib/registry/DynamicWebTypeRenderer"; import { enhancedFormService } from "@/lib/services/enhancedFormService"; import { FormValidationIndicator } from "@/components/common/FormValidationIndicator"; @@ -237,14 +237,46 @@ export const InteractiveScreenViewer: React.FC = ( // 자동입력 필드들의 값을 formData에 초기 설정 React.useEffect(() => { // console.log("🚀 자동입력 초기화 useEffect 실행 - allComponents 개수:", allComponents.length); - const initAutoInputFields = () => { + const initAutoInputFields = async () => { // console.log("🔧 initAutoInputFields 실행 시작"); - allComponents.forEach(comp => { - if (comp.type === 'widget') { + for (const comp of allComponents) { + // 🆕 type: "component" 또는 type: "widget" 모두 처리 + if (comp.type === 'widget' || comp.type === 'component') { const widget = comp as WidgetComponent; const fieldName = widget.columnName || widget.id; - // 텍스트 타입 위젯의 자동입력 처리 + // 🆕 autoFill 처리 (테이블 조회 기반 자동 입력) + if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { + const autoFillConfig = widget.autoFill || (comp as any).autoFill; + const currentValue = formData[fieldName]; + if (currentValue === undefined || currentValue === '') { + const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; + + // 사용자 정보에서 필터 값 가져오기 + const userValue = user?.[userField]; + + if (userValue && sourceTable && filterColumn && displayColumn) { + try { + const result = await tableTypeApi.getTableRecord( + sourceTable, + filterColumn, + userValue, + displayColumn + ); + + updateFormData(fieldName, result.value); + } catch (error) { + console.error(`autoFill 조회 실패: ${fieldName}`, error); + } + } + } + continue; // autoFill이 활성화되면 일반 자동입력은 건너뜀 + } + + // 기존 widget 타입 전용 로직은 widget인 경우만 + if (comp.type !== 'widget') continue; + + // 텍스트 타입 위젯의 자동입력 처리 (기존 로직) if ((widget.widgetType === 'text' || widget.widgetType === 'email' || widget.widgetType === 'tel') && widget.webTypeConfig) { const config = widget.webTypeConfig as TextTypeConfig; @@ -278,12 +310,12 @@ export const InteractiveScreenViewer: React.FC = ( } } } - }); + } }; // 초기 로드 시 자동입력 필드들 설정 initAutoInputFields(); - }, [allComponents, generateAutoValue]); // formData는 의존성에서 제외 (무한 루프 방지) + }, [allComponents, generateAutoValue, user]); // formData는 의존성에서 제외 (무한 루프 방지) // 날짜 값 업데이트 const updateDateValue = (fieldName: string, date: Date | undefined) => { @@ -1221,6 +1253,12 @@ export const InteractiveScreenViewer: React.FC = ( const handleSaveAction = async () => { // console.log("💾 저장 시작"); + // ✅ 사용자 정보가 로드되지 않았으면 저장 불가 + if (!user?.userId) { + alert("사용자 정보를 불러오는 중입니다. 잠시 후 다시 시도해주세요."); + return; + } + // 개선된 검증 시스템이 활성화된 경우 if (enhancedValidation) { // console.log("🔍 개선된 검증 시스템 사용"); @@ -1357,19 +1395,26 @@ export const InteractiveScreenViewer: React.FC = ( allComponents.find(c => c.columnName)?.tableName || "dynamic_form_data"; // 기본값 - // 🆕 자동으로 작성자 정보 추가 - const writerValue = user?.userId || userName || "unknown"; + // 🆕 자동으로 작성자 정보 추가 (user.userId가 확실히 있음) + const writerValue = user.userId; + const companyCodeValue = user.companyCode || ""; + console.log("👤 현재 사용자 정보:", { - userId: user?.userId, + userId: user.userId, userName: userName, - writerValue: writerValue, + companyCode: user.companyCode, // ✅ 회사 코드 + formDataWriter: mappedData.writer, // ✅ 폼에서 입력한 writer 값 + formDataCompanyCode: mappedData.company_code, // ✅ 폼에서 입력한 company_code 값 + defaultWriterValue: writerValue, + companyCodeValue, // ✅ 최종 회사 코드 값 }); const dataWithUserInfo = { ...mappedData, - writer: writerValue, // 테이블 생성 시 자동 생성되는 컬럼 - created_by: writerValue, - updated_by: writerValue, + writer: mappedData.writer || writerValue, // ✅ 입력값 우선, 없으면 userId + created_by: writerValue, // created_by는 항상 로그인한 사람 + updated_by: writerValue, // updated_by는 항상 로그인한 사람 + company_code: mappedData.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.companyCode }; const saveData: DynamicFormData = { diff --git a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx index e6c71ed9..c3e09f2e 100644 --- a/frontend/components/screen/InteractiveScreenViewerDynamic.tsx +++ b/frontend/components/screen/InteractiveScreenViewerDynamic.tsx @@ -81,6 +81,21 @@ export const InteractiveScreenViewerDynamic: React.FC { + if (onFormDataChange) { + onFormDataChange(fieldName, value); + } else { + setLocalFormData((prev) => ({ + ...prev, + [fieldName]: value, + })); + } + }, + [onFormDataChange], + ); + // 자동값 생성 함수 const generateAutoValue = useCallback( (autoValueType: string): string => { @@ -105,6 +120,50 @@ export const InteractiveScreenViewerDynamic: React.FC { + const initAutoInputFields = async () => { + for (const comp of allComponents) { + // type: "component" 또는 type: "widget" 모두 처리 + if (comp.type === 'widget' || comp.type === 'component') { + const widget = comp as any; + const fieldName = widget.columnName || widget.id; + + // autoFill 처리 (테이블 조회 기반 자동 입력) + if (widget.autoFill?.enabled || (comp as any).autoFill?.enabled) { + const autoFillConfig = widget.autoFill || (comp as any).autoFill; + const currentValue = formData[fieldName]; + + if (currentValue === undefined || currentValue === '') { + const { sourceTable, filterColumn, userField, displayColumn } = autoFillConfig; + + // 사용자 정보에서 필터 값 가져오기 + const userValue = user?.[userField]; + + if (userValue && sourceTable && filterColumn && displayColumn) { + try { + const { tableTypeApi } = await import("@/lib/api/screen"); + const result = await tableTypeApi.getTableRecord( + sourceTable, + filterColumn, + userValue, + displayColumn + ); + + updateFormData(fieldName, result.value); + } catch (error) { + console.error(`autoFill 조회 실패: ${fieldName}`, error); + } + } + } + } + } + } + }; + + initAutoInputFields(); + }, [allComponents, user]); + // 팝업 화면 레이아웃 로드 React.useEffect(() => { if (popupScreen?.screenId) { @@ -142,15 +201,11 @@ export const InteractiveScreenViewerDynamic: React.FC { - // console.log(`🎯 InteractiveScreenViewerDynamic handleFormDataChange 호출: ${fieldName} = "${value}"`); - // console.log(`📋 onFormDataChange 존재 여부:`, !!onFormDataChange); - + const handleFormDataChange = (fieldName: string | any, value?: any) => { + // 일반 필드 변경 if (onFormDataChange) { - // console.log(`📤 InteractiveScreenViewerDynamic -> onFormDataChange 호출: ${fieldName} = "${value}"`); onFormDataChange(fieldName, value); } else { - // console.log(`💾 InteractiveScreenViewerDynamic 로컬 상태 업데이트: ${fieldName} = "${value}"`); setLocalFormData((prev) => ({ ...prev, [fieldName]: value })); } }; @@ -190,6 +245,9 @@ export const InteractiveScreenViewerDynamic: React.FC { console.log("🔍 테이블에서 선택된 행 데이터:", selectedData); diff --git a/frontend/components/screen/SaveModal.tsx b/frontend/components/screen/SaveModal.tsx index 271a83d9..dd402933 100644 --- a/frontend/components/screen/SaveModal.tsx +++ b/frontend/components/screen/SaveModal.tsx @@ -105,6 +105,12 @@ export const SaveModal: React.FC = ({ const handleSave = async () => { if (!screenData || !screenId) return; + // ✅ 사용자 정보가 로드되지 않았으면 저장 불가 + if (!user?.userId) { + toast.error("사용자 정보를 불러오는 중입니다. 잠시 후 다시 시도해주세요."); + return; + } + try { setIsSaving(true); @@ -129,19 +135,26 @@ export const SaveModal: React.FC = ({ // 저장할 데이터 준비 const dataToSave = initialData ? changedData : formData; - // 🆕 자동으로 작성자 정보 추가 - const writerValue = user?.userId || userName || "unknown"; + // 🆕 자동으로 작성자 정보 추가 (user.userId가 확실히 있음) + const writerValue = user.userId; + const companyCodeValue = user.companyCode || ""; + console.log("👤 현재 사용자 정보:", { - userId: user?.userId, + userId: user.userId, userName: userName, - writerValue: writerValue, + companyCode: user.companyCode, // ✅ 회사 코드 + formDataWriter: dataToSave.writer, // ✅ 폼에서 입력한 writer 값 + formDataCompanyCode: dataToSave.company_code, // ✅ 폼에서 입력한 company_code 값 + defaultWriterValue: writerValue, + companyCodeValue, // ✅ 최종 회사 코드 값 }); const dataWithUserInfo = { ...dataToSave, - writer: writerValue, // 테이블 생성 시 자동 생성되는 컬럼 - created_by: writerValue, - updated_by: writerValue, + writer: dataToSave.writer || writerValue, // ✅ 입력값 우선, 없으면 userId + created_by: writerValue, // created_by는 항상 로그인한 사람 + updated_by: writerValue, // updated_by는 항상 로그인한 사람 + company_code: dataToSave.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.companyCode }; // 테이블명 결정 @@ -277,6 +290,9 @@ export const SaveModal: React.FC = ({ }} screenId={screenId} tableName={screenData.tableName} + userId={user?.userId} // ✅ 사용자 ID 전달 + userName={user?.userName} // ✅ 사용자 이름 전달 + companyCode={user?.companyCode} // ✅ 회사 코드 전달 formData={formData} originalData={originalData} onFormDataChange={(fieldName, value) => { diff --git a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx index d7d33e4a..5e1471ca 100644 --- a/frontend/components/screen/config-panels/ButtonConfigPanel.tsx +++ b/frontend/components/screen/config-panels/ButtonConfigPanel.tsx @@ -267,6 +267,9 @@ export const ButtonConfigPanel: React.FC = ({ 모달 열기 제어 흐름 테이블 이력 보기 + 엑셀 다운로드 + 엑셀 업로드 + 바코드 스캔
@@ -709,6 +712,132 @@ export const ButtonConfigPanel: React.FC = ({
)} + {/* 엑셀 다운로드 액션 설정 */} + {(component.componentConfig?.action?.type || "save") === "excel_download" && ( +
+

엑셀 다운로드 설정

+ +
+ + onUpdateProperty("componentConfig.action.excelFileName", e.target.value)} + className="h-8 text-xs" + /> +

확장자(.xlsx)는 자동으로 추가됩니다

+
+ +
+ + onUpdateProperty("componentConfig.action.excelSheetName", e.target.value)} + className="h-8 text-xs" + /> +
+ +
+ + onUpdateProperty("componentConfig.action.excelIncludeHeaders", checked)} + /> +
+
+ )} + + {/* 엑셀 업로드 액션 설정 */} + {(component.componentConfig?.action?.type || "save") === "excel_upload" && ( +
+

📤 엑셀 업로드 설정

+ +
+ + +
+ + {(config.action?.excelUploadMode === "update" || config.action?.excelUploadMode === "upsert") && ( +
+ + onUpdateProperty("componentConfig.action.excelKeyColumn", e.target.value)} + className="h-8 text-xs" + /> +

UPDATE/UPSERT 시 기준이 되는 컬럼명

+
+ )} +
+ )} + + {/* 바코드 스캔 액션 설정 */} + {(component.componentConfig?.action?.type || "save") === "barcode_scan" && ( +
+

📷 바코드 스캔 설정

+ +
+ + onUpdateProperty("componentConfig.action.barcodeTargetField", e.target.value)} + className="h-8 text-xs" + /> +

스캔 결과가 입력될 폼 필드명

+
+ +
+ + +
+ +
+ + onUpdateProperty("componentConfig.action.barcodeAutoSubmit", checked)} + /> +
+
+ )} + {/* 제어 기능 섹션 */}
diff --git a/frontend/components/screen/panels/DataTableConfigPanel.tsx b/frontend/components/screen/panels/DataTableConfigPanel.tsx index 8cf02cbc..e92f0191 100644 --- a/frontend/components/screen/panels/DataTableConfigPanel.tsx +++ b/frontend/components/screen/panels/DataTableConfigPanel.tsx @@ -2198,6 +2198,90 @@ const DataTableConfigPanelComponent: React.FC = ({ + {/* 🆕 자동 필터 설정 */} +
+

+ + 현재 사용자 정보로 필터링 +

+
+
+ { + onUpdateComponent({ + autoFilter: { + enabled: checked as boolean, + filterColumn: component.autoFilter?.filterColumn || 'company_code', + userField: component.autoFilter?.userField || 'companyCode', + }, + }); + }} + /> + +
+ + {component.autoFilter?.enabled && ( +
+
+ + { + onUpdateComponent({ + autoFilter: { + ...component.autoFilter!, + filterColumn: e.target.value, + }, + }); + }} + placeholder="company_code" + className="text-xs" + /> +

+ 예: company_code, dept_code, user_id +

+
+ +
+ + +

+ 로그인한 사용자 정보에서 가져올 필드 +

+
+
+ )} +
+
+ {/* 페이지네이션 설정 */}

페이지네이션 설정

diff --git a/frontend/components/screen/panels/DetailSettingsPanel.tsx b/frontend/components/screen/panels/DetailSettingsPanel.tsx index 14c7e388..47e1b102 100644 --- a/frontend/components/screen/panels/DetailSettingsPanel.tsx +++ b/frontend/components/screen/panels/DetailSettingsPanel.tsx @@ -1,8 +1,12 @@ "use client"; import React, { useState, useEffect } from "react"; -import { Settings } from "lucide-react"; +import { Settings, Database } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Label } from "@/components/ui/label"; +import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Separator } from "@/components/ui/separator"; import { useWebTypes } from "@/hooks/admin/useWebTypes"; import { getConfigPanelComponent } from "@/lib/utils/getConfigPanelComponent"; import { @@ -1125,6 +1129,136 @@ export const DetailSettingsPanel: React.FC = ({ }); }} /> + + {/* 🆕 테이블 데이터 자동 입력 섹션 (component 타입용) */} +
+

+ + 테이블 데이터 자동 입력 +

+
+
+ { + onUpdateProperty(selectedComponent.id, "autoFill", { + enabled: checked as boolean, + sourceTable: selectedComponent.autoFill?.sourceTable || "", + filterColumn: selectedComponent.autoFill?.filterColumn || "company_code", + userField: selectedComponent.autoFill?.userField || "companyCode", + displayColumn: selectedComponent.autoFill?.displayColumn || "", + }); + }} + /> + +
+ + {selectedComponent.autoFill?.enabled && ( +
+
+ + +

데이터를 조회할 테이블

+
+ +
+ + { + onUpdateProperty(selectedComponent.id, "autoFill", { + ...selectedComponent.autoFill!, + filterColumn: e.target.value, + }); + }} + placeholder="company_code" + className="text-xs" + /> +

예: company_code, dept_code, user_id

+
+ +
+ + +

로그인한 사용자의 정보를 필터로 사용

+
+ +
+ + { + onUpdateProperty(selectedComponent.id, "autoFill", { + ...selectedComponent.autoFill!, + displayColumn: e.target.value, + }); + }} + placeholder="company_name" + className="text-xs" + /> +

+ 조회된 레코드에서 표시할 컬럼 (예: company_name) +

+
+
+ )} +
+
@@ -1202,7 +1336,144 @@ export const DetailSettingsPanel: React.FC = ({ {/* 상세 설정 영역 */} -
{renderWebTypeConfig(widget)}
+
+
+ {console.log("🔍 [DetailSettingsPanel] widget 타입:", selectedComponent?.type, "autoFill:", widget.autoFill)} + {/* 🆕 자동 입력 섹션 */} +
+

+ + 🔥 테이블 데이터 자동 입력 (테스트) +

+
+
+ { + onUpdateProperty(widget.id, "autoFill", { + enabled: checked as boolean, + sourceTable: widget.autoFill?.sourceTable || '', + filterColumn: widget.autoFill?.filterColumn || 'company_code', + userField: widget.autoFill?.userField || 'companyCode', + displayColumn: widget.autoFill?.displayColumn || '', + }); + }} + /> + +
+ + {widget.autoFill?.enabled && ( +
+
+ + +

+ 데이터를 조회할 테이블 +

+
+ +
+ + { + onUpdateProperty(widget.id, "autoFill", { + ...widget.autoFill!, + filterColumn: e.target.value, + }); + }} + placeholder="company_code" + className="text-xs" + /> +

+ 예: company_code, dept_code, user_id +

+
+ +
+ + +

+ 로그인한 사용자 정보에서 가져올 필드 +

+
+ +
+ + { + onUpdateProperty(widget.id, "autoFill", { + ...widget.autoFill!, + displayColumn: e.target.value, + }); + }} + placeholder="company_name" + className="text-xs" + /> +

+ Input에 표시할 컬럼명 (예: company_name, dept_name) +

+
+
+ )} +
+
+ + {/* 웹타입 설정 */} + + {renderWebTypeConfig(widget)} +
+
); }; diff --git a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx index b432ef8b..12007094 100644 --- a/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx +++ b/frontend/components/screen/panels/UnifiedPropertiesPanel.tsx @@ -118,9 +118,9 @@ export const UnifiedPropertiesPanel: React.FC = ({ {/* 안내 메시지 */}
- -

컴포넌트를 선택하여

-

속성을 편집하세요

+ +

컴포넌트를 선택하여

+

속성을 편집하세요

@@ -412,8 +412,11 @@ export const UnifiedPropertiesPanel: React.FC = ({ // 상세 설정 탭 (DetailSettingsPanel의 전체 로직 통합) const renderDetailTab = () => { + console.log("🔍 [renderDetailTab] selectedComponent.type:", selectedComponent.type); + // 1. DataTable 컴포넌트 if (selectedComponent.type === "datatable") { + console.log("✅ [renderDetailTab] DataTable 컴포넌트"); return ( = ({ // 5. 새로운 컴포넌트 시스템 (type: "component") if (selectedComponent.type === "component") { + console.log("✅ [renderDetailTab] Component 타입"); const componentId = (selectedComponent as any).componentType || selectedComponent.componentConfig?.type; const webType = selectedComponent.componentConfig?.webType; @@ -479,7 +483,7 @@ export const UnifiedPropertiesPanel: React.FC = ({ if (!componentId) { return (
-

컴포넌트 ID가 설정되지 않았습니다

+

컴포넌트 ID가 설정되지 않았습니다

); } @@ -511,7 +515,7 @@ export const UnifiedPropertiesPanel: React.FC = ({
{option.label}
-
{option.description}
+
{option.description}
))} @@ -535,45 +539,154 @@ export const UnifiedPropertiesPanel: React.FC = ({ }); }} /> + + {/* 🆕 테이블 데이터 자동 입력 (component 타입용) */} + +
+
+ +

테이블 데이터 자동 입력

+
+ + {/* 활성화 체크박스 */} +
+ { + handleUpdate("autoFill", { + ...selectedComponent.autoFill, + enabled: Boolean(checked), + }); + }} + /> + +
+ + {selectedComponent.autoFill?.enabled && ( + <> + {/* 조회할 테이블 */} +
+ + +
+ + {/* 필터링할 컬럼 */} +
+ + { + handleUpdate("autoFill", { + ...selectedComponent.autoFill, + enabled: selectedComponent.autoFill?.enabled || false, + filterColumn: e.target.value, + }); + }} + placeholder="예: company_code" + className="h-6 w-full px-2 py-0 text-xs" + /> +
+ + {/* 사용자 정보 필드 */} +
+ + +
+ + {/* 표시할 컬럼 */} +
+ + { + handleUpdate("autoFill", { + ...selectedComponent.autoFill, + enabled: selectedComponent.autoFill?.enabled || false, + displayColumn: e.target.value, + }); + }} + placeholder="예: company_name" + className="h-6 w-full px-2 py-0 text-xs" + /> +
+ + )} +
); } // 6. Widget 컴포넌트 if (selectedComponent.type === "widget") { + console.log("✅ [renderDetailTab] Widget 타입"); const widget = selectedComponent as WidgetComponent; + console.log("🔍 [renderDetailTab] widget.widgetType:", widget.widgetType); - // Widget에 webType이 있는 경우 - if (widget.webType) { - return ( -
- {/* WebType 선택 */} -
- - -
-
- ); - } - - // 새로운 컴포넌트 시스템 (widgetType이 button, card 등) + // 새로운 컴포넌트 시스템 (widgetType이 button, card 등) - 먼저 체크 if ( widget.widgetType && ["button", "card", "dashboard", "stats-card", "progress-bar", "chart", "alert", "badge"].includes( widget.widgetType, ) ) { + console.log("✅ [renderDetailTab] DynamicComponent 반환 (widgetType)"); return ( = ({ /> ); } + + // 일반 위젯 (webType 기반) + console.log("✅ [renderDetailTab] 일반 위젯 렌더링 시작"); + return ( +
+ {console.log("🔍 [UnifiedPropertiesPanel] widget.webType:", widget.webType, "widget:", widget)} + {/* WebType 선택 (있는 경우만) */} + {widget.webType && ( +
+ + +
+ )} + + {/* 🆕 테이블 데이터 자동 입력 (모든 widget 컴포넌트) */} + +
+
+ +

테이블 데이터 자동 입력

+
+ + {/* 활성화 체크박스 */} +
+ { + handleUpdate("autoFill", { + ...widget.autoFill, + enabled: Boolean(checked), + }); + }} + /> + +
+ + {widget.autoFill?.enabled && ( + <> + {/* 조회할 테이블 */} +
+ + +
+ + {/* 필터링할 컬럼 */} +
+ + { + handleUpdate("autoFill", { + ...widget.autoFill, + enabled: widget.autoFill?.enabled || false, + filterColumn: e.target.value, + }); + }} + placeholder="예: company_code" + className="h-6 w-full px-2 py-0 text-xs" + /> +
+ + {/* 사용자 정보 필드 */} +
+ + +
+ + {/* 표시할 컬럼 */} +
+ + { + handleUpdate("autoFill", { + ...widget.autoFill, + enabled: widget.autoFill?.enabled || false, + displayColumn: e.target.value, + }); + }} + placeholder="예: company_name" + className="h-6 w-full px-2 py-0 text-xs" + /> +
+ + )} +
+
+ ); } // 기본 메시지 return (
-

이 컴포넌트는 추가 설정이 없습니다

+

이 컴포넌트는 추가 설정이 없습니다

); }; @@ -602,9 +871,9 @@ export const UnifiedPropertiesPanel: React.FC = ({ return (
{/* 헤더 - 간소화 */} -
+
{selectedComponent.type === "widget" && ( -
+
{(selectedComponent as WidgetComponent).label || selectedComponent.id}
)} diff --git a/frontend/constants/company.ts b/frontend/constants/company.ts index 2c39247d..f63dc341 100644 --- a/frontend/constants/company.ts +++ b/frontend/constants/company.ts @@ -74,6 +74,12 @@ export const MOCK_COMPANIES: Company[] = [ // 새 회사 등록 시 기본값 export const DEFAULT_COMPANY_FORM_DATA = { company_name: "", + business_registration_number: "", + representative_name: "", + representative_phone: "", + email: "", + website: "", + address: "", }; // 페이징 관련 상수 diff --git a/frontend/hooks/useCompanyManagement.ts b/frontend/hooks/useCompanyManagement.ts index f25a881b..eba448ef 100644 --- a/frontend/hooks/useCompanyManagement.ts +++ b/frontend/hooks/useCompanyManagement.ts @@ -144,6 +144,12 @@ export const useCompanyManagement = () => { selectedCompany: company, formData: { company_name: company.company_name, + business_registration_number: company.business_registration_number || "", + representative_name: company.representative_name || "", + representative_phone: company.representative_phone || "", + email: company.email || "", + website: company.website || "", + address: company.address || "", }, }); }, []); @@ -175,6 +181,10 @@ export const useCompanyManagement = () => { setError("회사명을 입력해주세요."); return false; } + if (!modalState.formData.business_registration_number.trim()) { + setError("사업자등록번호를 입력해주세요."); + return false; + } setIsLoading(true); setError(null); @@ -199,6 +209,10 @@ export const useCompanyManagement = () => { setError("올바른 데이터를 입력해주세요."); return false; } + if (!modalState.formData.business_registration_number.trim()) { + setError("사업자등록번호를 입력해주세요."); + return false; + } setIsLoading(true); setError(null); @@ -206,6 +220,12 @@ export const useCompanyManagement = () => { try { await companyAPI.update(modalState.selectedCompany.company_code, { company_name: modalState.formData.company_name, + business_registration_number: modalState.formData.business_registration_number, + representative_name: modalState.formData.representative_name, + representative_phone: modalState.formData.representative_phone, + email: modalState.formData.email, + website: modalState.formData.website, + address: modalState.formData.address, status: modalState.selectedCompany.status, }); closeModal(); diff --git a/frontend/lib/api/department.ts b/frontend/lib/api/department.ts new file mode 100644 index 00000000..2486d11f --- /dev/null +++ b/frontend/lib/api/department.ts @@ -0,0 +1,157 @@ +/** + * 부서 관리 API 클라이언트 + */ + +import { apiClient } from "./client"; +import { Department, DepartmentMember, DepartmentFormData } from "@/types/department"; + +/** + * 부서 목록 조회 (회사별) + */ +export async function getDepartments(companyCode: string) { + try { + const url = `/departments/companies/${companyCode}/departments`; + const response = await apiClient.get<{ success: boolean; data: Department[] }>(url); + return response.data; + } catch (error: any) { + console.error("부서 목록 조회 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 부서 상세 조회 + */ +export async function getDepartment(deptCode: string) { + try { + const response = await apiClient.get<{ success: boolean; data: Department }>(`/departments/${deptCode}`); + return response.data; + } catch (error: any) { + console.error("부서 상세 조회 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 부서 생성 + */ +export async function createDepartment(companyCode: string, data: DepartmentFormData) { + try { + const response = await apiClient.post<{ success: boolean; data: Department }>( + `/departments/companies/${companyCode}/departments`, + data, + ); + return response.data; + } catch (error: any) { + console.error("부서 생성 실패:", error); + const isDuplicate = error.response?.status === 409; + return { + success: false, + error: error.response?.data?.message || error.message, + isDuplicate, + }; + } +} + +/** + * 부서 수정 + */ +export async function updateDepartment(deptCode: string, data: DepartmentFormData) { + try { + const response = await apiClient.put<{ success: boolean; data: Department }>(`/departments/${deptCode}`, data); + return response.data; + } catch (error: any) { + console.error("부서 수정 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 부서 삭제 + */ +export async function deleteDepartment(deptCode: string) { + try { + const response = await apiClient.delete<{ success: boolean }>(`/departments/${deptCode}`); + return response.data; + } catch (error: any) { + console.error("부서 삭제 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 부서원 목록 조회 + */ +export async function getDepartmentMembers(deptCode: string) { + try { + const response = await apiClient.get<{ success: boolean; data: DepartmentMember[] }>( + `/departments/${deptCode}/members`, + ); + return response.data; + } catch (error: any) { + console.error("부서원 목록 조회 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 사용자 검색 (부서원 추가용) + */ +export async function searchUsers(companyCode: string, search: string) { + try { + const response = await apiClient.get<{ success: boolean; data: any[] }>( + `/departments/companies/${companyCode}/users/search`, + { params: { search } }, + ); + return response.data; + } catch (error: any) { + console.error("사용자 검색 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 부서원 추가 + */ +export async function addDepartmentMember(deptCode: string, userId: string) { + try { + const response = await apiClient.post<{ success: boolean; message?: string }>(`/departments/${deptCode}/members`, { + user_id: userId, + }); + return response.data; + } catch (error: any) { + console.error("부서원 추가 실패:", error); + const isDuplicate = error.response?.status === 409; + return { + success: false, + error: error.response?.data?.message || error.message, + isDuplicate, + }; + } +} + +/** + * 부서원 제거 + */ +export async function removeDepartmentMember(deptCode: string, userId: string) { + try { + const response = await apiClient.delete<{ success: boolean }>(`/departments/${deptCode}/members/${userId}`); + return response.data; + } catch (error: any) { + console.error("부서원 제거 실패:", error); + return { success: false, error: error.message }; + } +} + +/** + * 주 부서 설정 + */ +export async function setPrimaryDepartment(deptCode: string, userId: string) { + try { + const response = await apiClient.put<{ success: boolean }>(`/departments/${deptCode}/members/${userId}/primary`); + return response.data; + } catch (error: any) { + console.error("주 부서 설정 실패:", error); + return { success: false, error: error.message }; + } +} diff --git a/frontend/lib/api/dynamicForm.ts b/frontend/lib/api/dynamicForm.ts index 77bd4606..455ab5eb 100644 --- a/frontend/lib/api/dynamicForm.ts +++ b/frontend/lib/api/dynamicForm.ts @@ -410,6 +410,128 @@ export class DynamicFormApi { }; } } + + /** + * 테이블 데이터 조회 (페이징 + 검색) + * @param tableName 테이블명 + * @param params 검색 파라미터 + * @returns 테이블 데이터 + */ + static async getTableData( + tableName: string, + params?: { + page?: number; + pageSize?: number; + search?: string; + sortBy?: string; + sortOrder?: "asc" | "desc"; + filters?: Record; + }, + ): Promise> { + try { + console.log("📊 테이블 데이터 조회 요청:", { tableName, params }); + + const response = await apiClient.post(`/table-management/tables/${tableName}/data`, params || {}); + + console.log("✅ 테이블 데이터 조회 성공 (원본):", response.data); + console.log("🔍 response.data 상세:", { + type: typeof response.data, + isArray: Array.isArray(response.data), + keys: response.data ? Object.keys(response.data) : [], + hasData: response.data?.data !== undefined, + dataType: response.data?.data ? typeof response.data.data : "N/A", + dataIsArray: response.data?.data ? Array.isArray(response.data.data) : false, + dataLength: response.data?.data ? (Array.isArray(response.data.data) ? response.data.data.length : "not array") : "no data", + // 중첩 구조 확인 + dataDataExists: response.data?.data?.data !== undefined, + dataDataIsArray: response.data?.data?.data ? Array.isArray(response.data.data.data) : false, + dataDataLength: response.data?.data?.data ? (Array.isArray(response.data.data.data) ? response.data.data.data.length : "not array") : "no nested data", + }); + + // API 응답 구조: { data: [...], total, page, size, totalPages } + // 또는 중첩: { success: true, data: { data: [...], total, ... } } + // data 배열만 추출 + let tableData: any[] = []; + + if (Array.isArray(response.data)) { + // 케이스 1: 응답이 배열이면 그대로 사용 + console.log("✅ 케이스 1: 응답이 배열"); + tableData = response.data; + } else if (response.data && Array.isArray(response.data.data)) { + // 케이스 2: 응답이 { data: [...] } 구조면 data 배열 추출 + console.log("✅ 케이스 2: 응답이 { data: [...] } 구조"); + tableData = response.data.data; + } else if (response.data?.data?.data && Array.isArray(response.data.data.data)) { + // 케이스 2-1: 중첩 구조 { success: true, data: { data: [...] } } + console.log("✅ 케이스 2-1: 중첩 구조 { data: { data: [...] } }"); + tableData = response.data.data.data; + } else if (response.data && typeof response.data === "object") { + // 케이스 3: 응답이 객체면 배열로 감싸기 (최후의 수단) + console.log("⚠️ 케이스 3: 응답이 객체 (배열로 감싸기)"); + tableData = [response.data]; + } + + console.log("✅ 테이블 데이터 추출 완료:", { + originalType: typeof response.data, + isArray: Array.isArray(response.data), + hasDataProperty: response.data?.data !== undefined, + extractedCount: tableData.length, + firstRow: tableData[0], + allRows: tableData, + }); + + return { + success: true, + data: tableData, + message: "테이블 데이터 조회가 완료되었습니다.", + }; + } catch (error: any) { + console.error("❌ 테이블 데이터 조회 실패:", error); + + const errorMessage = error.response?.data?.message || error.message || "테이블 데이터 조회 중 오류가 발생했습니다."; + + return { + success: false, + message: errorMessage, + errorCode: error.response?.data?.errorCode, + }; + } + } + + /** + * 엑셀 업로드 (대량 데이터 삽입/업데이트) + * @param payload 업로드 데이터 + * @returns 업로드 결과 + */ + static async uploadExcelData(payload: { + tableName: string; + data: any[]; + uploadMode: "insert" | "update" | "upsert"; + keyColumn?: string; + }): Promise> { + try { + console.log("📤 엑셀 업로드 요청:", payload); + + const response = await apiClient.post(`/dynamic-form/excel-upload`, payload); + + console.log("✅ 엑셀 업로드 성공:", response.data); + return { + success: true, + data: response.data, + message: "엑셀 파일이 성공적으로 업로드되었습니다.", + }; + } catch (error: any) { + console.error("❌ 엑셀 업로드 실패:", error); + + const errorMessage = error.response?.data?.message || error.message || "엑셀 업로드 중 오류가 발생했습니다."; + + return { + success: false, + message: errorMessage, + errorCode: error.response?.data?.errorCode, + }; + } + } } // 편의를 위한 기본 export diff --git a/frontend/lib/api/screen.ts b/frontend/lib/api/screen.ts index 3c885a8b..a37fbdff 100644 --- a/frontend/lib/api/screen.ts +++ b/frontend/lib/api/screen.ts @@ -313,6 +313,21 @@ export const tableTypeApi = { deleteTableData: async (tableName: string, data: Record[] | { ids: string[] }): Promise => { await apiClient.delete(`/table-management/tables/${tableName}/delete`, { data }); }, + + // 🆕 단일 레코드 조회 (자동 입력용) + getTableRecord: async ( + tableName: string, + filterColumn: string, + filterValue: any, + displayColumn: string, + ): Promise<{ value: any; record: Record }> => { + const response = await apiClient.post(`/table-management/tables/${tableName}/record`, { + filterColumn, + filterValue, + displayColumn, + }); + return response.data.data; + }, }; // 메뉴-화면 할당 관련 API diff --git a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx index f2d6e554..93d96ca0 100644 --- a/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx +++ b/frontend/lib/registry/components/button-primary/ButtonPrimaryComponent.tsx @@ -247,8 +247,8 @@ export const ButtonPrimaryComponent: React.FC = ({ // 추가 안전장치: 모든 로딩 토스트 제거 toast.dismiss(); - // UI 전환 액션(edit, modal, navigate)을 제외하고만 로딩 토스트 표시 - const silentActions = ["edit", "modal", "navigate"]; + // UI 전환 액션 및 모달 액션은 로딩 토스트 표시하지 않음 + const silentActions = ["edit", "modal", "navigate", "excel_upload", "barcode_scan"]; if (!silentActions.includes(actionConfig.type)) { currentLoadingToastRef.current = toast.loading( actionConfig.type === "save" @@ -274,9 +274,9 @@ export const ButtonPrimaryComponent: React.FC = ({ // 실패한 경우 오류 처리 if (!success) { - // UI 전환 액션(edit, modal, navigate)은 에러도 조용히 처리 - const silentActions = ["edit", "modal", "navigate"]; - if (silentActions.includes(actionConfig.type)) { + // UI 전환 액션 및 모달 액션은 에러도 조용히 처리 (모달 내부에서 자체 에러 표시) + const silentErrorActions = ["edit", "modal", "navigate", "excel_upload", "barcode_scan"]; + if (silentErrorActions.includes(actionConfig.type)) { return; } // 기본 에러 메시지 결정 @@ -302,8 +302,10 @@ export const ButtonPrimaryComponent: React.FC = ({ } // 성공한 경우에만 성공 토스트 표시 - // edit, modal, navigate 액션은 조용히 처리 (UI 전환만 하므로 토스트 불필요) - if (actionConfig.type !== "edit" && actionConfig.type !== "modal" && actionConfig.type !== "navigate") { + // edit, modal, navigate, excel_upload, barcode_scan 액션은 조용히 처리 + // (UI 전환만 하거나 모달 내부에서 자체적으로 메시지 표시) + const silentSuccessActions = ["edit", "modal", "navigate", "excel_upload", "barcode_scan"]; + if (!silentSuccessActions.includes(actionConfig.type)) { // 기본 성공 메시지 결정 const defaultSuccessMessage = actionConfig.type === "save" diff --git a/frontend/lib/registry/components/table-list/TableListComponent.tsx b/frontend/lib/registry/components/table-list/TableListComponent.tsx index 74a53561..4e5243b6 100644 --- a/frontend/lib/registry/components/table-list/TableListComponent.tsx +++ b/frontend/lib/registry/components/table-list/TableListComponent.tsx @@ -284,7 +284,7 @@ export const TableListComponent: React.FC = ({ // 컬럼 라벨 가져오기 // ======================================== - const fetchColumnLabels = async () => { + const fetchColumnLabels = useCallback(async () => { if (!tableConfig.selectedTable) return; try { @@ -339,13 +339,13 @@ export const TableListComponent: React.FC = ({ } catch (error) { console.error("컬럼 라벨 가져오기 실패:", error); } - }; + }, [tableConfig.selectedTable]); // ======================================== // 테이블 라벨 가져오기 // ======================================== - const fetchTableLabel = async () => { + const fetchTableLabel = useCallback(async () => { if (!tableConfig.selectedTable) return; try { @@ -374,7 +374,7 @@ export const TableListComponent: React.FC = ({ } catch (error) { console.error("테이블 라벨 가져오기 실패:", error); } - }; + }, [tableConfig.selectedTable]); // ======================================== // 데이터 가져오기 @@ -531,7 +531,10 @@ export const TableListComponent: React.FC = ({ onSelectedRowsChange(Array.from(newSelectedRows), selectedRowsData); } if (onFormDataChange) { - onFormDataChange({ selectedRows: Array.from(newSelectedRows), selectedRowsData }); + onFormDataChange({ + selectedRows: Array.from(newSelectedRows), + selectedRowsData, + }); } const allRowsSelected = data.every((row, index) => newSelectedRows.has(getRowKey(row, index))); @@ -549,7 +552,10 @@ export const TableListComponent: React.FC = ({ onSelectedRowsChange(Array.from(newSelectedRows), data); } if (onFormDataChange) { - onFormDataChange({ selectedRows: Array.from(newSelectedRows), selectedRowsData: data }); + onFormDataChange({ + selectedRows: Array.from(newSelectedRows), + selectedRowsData: data, + }); } } else { setSelectedRows(new Set()); @@ -930,7 +936,7 @@ export const TableListComponent: React.FC = ({ useEffect(() => { fetchColumnLabels(); fetchTableLabel(); - }, [tableConfig.selectedTable]); + }, [tableConfig.selectedTable, fetchColumnLabels, fetchTableLabel]); useEffect(() => { if (!isDesignMode && tableConfig.selectedTable) { @@ -945,6 +951,7 @@ export const TableListComponent: React.FC = ({ searchTerm, refreshKey, isDesignMode, + fetchTableDataDebounced, ]); useEffect(() => { diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 8b805d93..753deea5 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -17,7 +17,10 @@ export type ButtonActionType = | "navigate" // 페이지 이동 | "modal" // 모달 열기 | "control" // 제어 흐름 - | "view_table_history"; // 테이블 이력 보기 + | "view_table_history" // 테이블 이력 보기 + | "excel_download" // 엑셀 다운로드 + | "excel_upload" // 엑셀 업로드 + | "barcode_scan"; // 바코드 스캔 /** * 버튼 액션 설정 @@ -56,6 +59,20 @@ export interface ButtonActionConfig { historyRecordIdSource?: "selected_row" | "form_field" | "context"; // 레코드 ID 가져올 소스 historyRecordLabelField?: string; // 레코드 라벨로 표시할 필드 (선택사항) historyDisplayColumn?: string; // 전체 이력에서 레코드 구분용 컬럼 (예: device_code, name) + + // 엑셀 다운로드 관련 + excelFileName?: string; // 다운로드할 파일명 (기본: 테이블명_날짜.xlsx) + excelSheetName?: string; // 시트명 (기본: "Sheet1") + excelIncludeHeaders?: boolean; // 헤더 포함 여부 (기본: true) + + // 엑셀 업로드 관련 + excelUploadMode?: "insert" | "update" | "upsert"; // 업로드 모드 + excelKeyColumn?: string; // 업데이트/Upsert 시 키 컬럼 + + // 바코드 스캔 관련 + barcodeTargetField?: string; // 스캔 결과를 입력할 필드명 + barcodeFormat?: "all" | "1d" | "2d"; // 바코드 포맷 (기본: "all") + barcodeAutoSubmit?: boolean; // 스캔 후 자동 제출 여부 } /** @@ -121,6 +138,15 @@ export class ButtonActionExecutor { case "view_table_history": return this.handleViewTableHistory(config, context); + case "excel_download": + return await this.handleExcelDownload(config, context); + + case "excel_upload": + return await this.handleExcelUpload(config, context); + + case "barcode_scan": + return await this.handleBarcodeScan(config, context); + default: console.warn(`지원되지 않는 액션 타입: ${config.type}`); return false; @@ -203,15 +229,29 @@ export class ButtonActionExecutor { // INSERT 처리 // 🆕 자동으로 작성자 정보 추가 - const writerValue = context.userId || context.userName || "unknown"; + if (!context.userId) { + throw new Error("사용자 정보를 불러올 수 없습니다. 다시 로그인해주세요."); + } + + const writerValue = context.userId; const companyCodeValue = context.companyCode || ""; + console.log("👤 [buttonActions] 사용자 정보:", { + userId: context.userId, + userName: context.userName, + companyCode: context.companyCode, // ✅ 회사 코드 + formDataWriter: formData.writer, // ✅ 폼에서 입력한 writer 값 + formDataCompanyCode: formData.company_code, // ✅ 폼에서 입력한 company_code 값 + defaultWriterValue: writerValue, + companyCodeValue, // ✅ 최종 회사 코드 값 + }); + const dataWithUserInfo = { ...formData, - writer: writerValue, - created_by: writerValue, - updated_by: writerValue, - company_code: companyCodeValue, + writer: formData.writer || writerValue, // ✅ 입력값 우선, 없으면 userId + created_by: writerValue, // created_by는 항상 로그인한 사람 + updated_by: writerValue, // updated_by는 항상 로그인한 사람 + company_code: formData.company_code || companyCodeValue, // ✅ 입력값 우선, 없으면 user.companyCode }; saveResult = await DynamicFormApi.saveFormData({ @@ -1632,6 +1672,226 @@ export class ButtonActionExecutor { } } + /** + * 엑셀 다운로드 액션 처리 + */ + private static async handleExcelDownload(config: ButtonActionConfig, context: ButtonActionContext): Promise { + try { + console.log("📥 엑셀 다운로드 시작:", { config, context }); + + // 동적 import로 엑셀 유틸리티 로드 + const { exportToExcel } = await import("@/lib/utils/excelExport"); + + let dataToExport: any[] = []; + + // 1순위: 선택된 행 데이터 + if (context.selectedRowsData && context.selectedRowsData.length > 0) { + dataToExport = context.selectedRowsData; + console.log("✅ 선택된 행 데이터 사용:", dataToExport.length); + } + // 2순위: 테이블 전체 데이터 (API 호출) + else if (context.tableName) { + console.log("🔄 테이블 전체 데이터 조회 중...", context.tableName); + try { + const { dynamicFormApi } = await import("@/lib/api/dynamicForm"); + const response = await dynamicFormApi.getTableData(context.tableName, { + page: 1, + pageSize: 10000, // 최대 10,000개 행 + sortBy: "id", // 기본 정렬: id 컬럼 + sortOrder: "asc", // 오름차순 + }); + + console.log("📦 API 응답 구조:", { + response, + responseSuccess: response.success, + responseData: response.data, + responseDataType: typeof response.data, + responseDataIsArray: Array.isArray(response.data), + responseDataLength: Array.isArray(response.data) ? response.data.length : "N/A", + }); + + if (response.success && response.data) { + dataToExport = response.data; + console.log("✅ 테이블 전체 데이터 조회 완료:", { + count: dataToExport.length, + firstRow: dataToExport[0], + }); + } else { + console.error("❌ API 응답에 데이터가 없습니다:", response); + } + } catch (error) { + console.error("❌ 테이블 데이터 조회 실패:", error); + } + } + // 4순위: 폼 데이터 + else if (context.formData && Object.keys(context.formData).length > 0) { + dataToExport = [context.formData]; + console.log("✅ 폼 데이터 사용:", dataToExport); + } + + console.log("📊 최종 다운로드 데이터:", { + selectedRowsData: context.selectedRowsData, + selectedRowsLength: context.selectedRowsData?.length, + formData: context.formData, + tableName: context.tableName, + dataToExport, + dataToExportType: typeof dataToExport, + dataToExportIsArray: Array.isArray(dataToExport), + dataToExportLength: Array.isArray(dataToExport) ? dataToExport.length : "N/A", + }); + + // 배열이 아니면 배열로 변환 + if (!Array.isArray(dataToExport)) { + console.warn("⚠️ dataToExport가 배열이 아닙니다. 변환 시도:", dataToExport); + + // 객체인 경우 배열로 감싸기 + if (typeof dataToExport === "object" && dataToExport !== null) { + dataToExport = [dataToExport]; + } else { + toast.error("다운로드할 데이터 형식이 올바르지 않습니다."); + return false; + } + } + + if (dataToExport.length === 0) { + toast.error("다운로드할 데이터가 없습니다."); + return false; + } + + // 파일명 생성 + const fileName = config.excelFileName || `${context.tableName || "데이터"}_${new Date().toISOString().split("T")[0]}.xlsx`; + const sheetName = config.excelSheetName || "Sheet1"; + const includeHeaders = config.excelIncludeHeaders !== false; + + console.log("📥 엑셀 다운로드 실행:", { + fileName, + sheetName, + includeHeaders, + dataCount: dataToExport.length, + firstRow: dataToExport[0], + }); + + // 엑셀 다운로드 실행 + await exportToExcel(dataToExport, fileName, sheetName, includeHeaders); + + toast.success(config.successMessage || "엑셀 파일이 다운로드되었습니다."); + return true; + } catch (error) { + console.error("❌ 엑셀 다운로드 실패:", error); + toast.error(config.errorMessage || "엑셀 다운로드 중 오류가 발생했습니다."); + return false; + } + } + + /** + * 엑셀 업로드 액션 처리 + */ + private static async handleExcelUpload(config: ButtonActionConfig, context: ButtonActionContext): Promise { + try { + console.log("📤 엑셀 업로드 모달 열기:", { config, context }); + + // 동적 import로 모달 컴포넌트 로드 + const { ExcelUploadModal } = await import("@/components/common/ExcelUploadModal"); + const { createRoot } = await import("react-dom/client"); + + // 모달 컨테이너 생성 + const modalContainer = document.createElement("div"); + document.body.appendChild(modalContainer); + + const root = createRoot(modalContainer); + + const closeModal = () => { + root.unmount(); + document.body.removeChild(modalContainer); + }; + + root.render( + React.createElement(ExcelUploadModal, { + open: true, + onOpenChange: (open: boolean) => { + if (!open) closeModal(); + }, + tableName: context.tableName || "", + uploadMode: config.excelUploadMode || "insert", + keyColumn: config.excelKeyColumn, + onSuccess: () => { + // 성공 메시지는 ExcelUploadModal 내부에서 이미 표시됨 + context.onRefresh?.(); + closeModal(); + }, + }), + ); + + return true; + } catch (error) { + console.error("❌ 엑셀 업로드 모달 열기 실패:", error); + toast.error(config.errorMessage || "엑셀 업로드 중 오류가 발생했습니다."); + return false; + } + } + + /** + * 바코드 스캔 액션 처리 + */ + private static async handleBarcodeScan(config: ButtonActionConfig, context: ButtonActionContext): Promise { + try { + console.log("📷 바코드 스캔 모달 열기:", { config, context }); + + // 동적 import로 모달 컴포넌트 로드 + const { BarcodeScanModal } = await import("@/components/common/BarcodeScanModal"); + const { createRoot } = await import("react-dom/client"); + + // 모달 컨테이너 생성 + const modalContainer = document.createElement("div"); + document.body.appendChild(modalContainer); + + const root = createRoot(modalContainer); + + const closeModal = () => { + root.unmount(); + document.body.removeChild(modalContainer); + }; + + root.render( + React.createElement(BarcodeScanModal, { + open: true, + onOpenChange: (open: boolean) => { + if (!open) closeModal(); + }, + targetField: config.barcodeTargetField, + barcodeFormat: config.barcodeFormat || "all", + autoSubmit: config.barcodeAutoSubmit || false, + onScanSuccess: (barcode: string) => { + console.log("✅ 바코드 스캔 성공:", barcode); + + // 대상 필드에 값 입력 + if (config.barcodeTargetField && context.onFormDataChange) { + context.onFormDataChange({ + ...context.formData, + [config.barcodeTargetField]: barcode, + }); + } + + toast.success(`바코드 스캔 완료: ${barcode}`); + + // 자동 제출 옵션이 켜져있으면 저장 + if (config.barcodeAutoSubmit) { + this.handleSave(config, context); + } + + closeModal(); + }, + }), + ); + + return true; + } catch (error) { + console.error("❌ 바코드 스캔 모달 열기 실패:", error); + toast.error("바코드 스캔 중 오류가 발생했습니다."); + return false; + } + } + /** * 폼 데이터 유효성 검사 */ @@ -1703,4 +1963,22 @@ export const DEFAULT_BUTTON_ACTIONS: Record[], + fileName: string = "export.xlsx", + sheetName: string = "Sheet1", + includeHeaders: boolean = true +): Promise { + try { + console.log("📥 엑셀 내보내기 시작:", { + dataCount: data.length, + fileName, + sheetName, + includeHeaders, + }); + + if (data.length === 0) { + throw new Error("내보낼 데이터가 없습니다."); + } + + // 워크북 생성 + const workbook = XLSX.utils.book_new(); + + // 데이터를 워크시트로 변환 + const worksheet = XLSX.utils.json_to_sheet(data, { + header: includeHeaders ? undefined : [], + skipHeader: !includeHeaders, + }); + + // 컬럼 너비 자동 조정 + const columnWidths = autoSizeColumns(data); + worksheet["!cols"] = columnWidths; + + // 워크시트를 워크북에 추가 + XLSX.utils.book_append_sheet(workbook, worksheet, sheetName); + + // 파일 다운로드 + XLSX.writeFile(workbook, fileName); + + console.log("✅ 엑셀 내보내기 완료:", fileName); + } catch (error) { + console.error("❌ 엑셀 내보내기 실패:", error); + throw error; + } +} + +/** + * 컬럼 너비 자동 조정 + */ +function autoSizeColumns(data: Record[]): Array<{ wch: number }> { + if (data.length === 0) return []; + + const keys = Object.keys(data[0]); + const columnWidths: Array<{ wch: number }> = []; + + keys.forEach((key) => { + // 헤더 길이 + let maxWidth = key.length; + + // 데이터 길이 확인 + data.forEach((row) => { + const value = row[key]; + const valueLength = value ? String(value).length : 0; + maxWidth = Math.max(maxWidth, valueLength); + }); + + // 최소 10, 최대 50으로 제한 + columnWidths.push({ wch: Math.min(Math.max(maxWidth, 10), 50) }); + }); + + return columnWidths; +} + +/** + * 엑셀 파일을 읽어서 JSON 데이터로 변환 + * @param file 읽을 파일 + * @param sheetName 읽을 시트명 (기본: 첫 번째 시트) + * @returns JSON 데이터 배열 + */ +export async function importFromExcel( + file: File, + sheetName?: string +): Promise[]> { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = (e) => { + try { + const data = e.target?.result; + if (!data) { + reject(new Error("파일을 읽을 수 없습니다.")); + return; + } + + // 워크북 읽기 + const workbook = XLSX.read(data, { type: "binary" }); + + // 시트 선택 (지정된 시트 또는 첫 번째 시트) + const targetSheetName = sheetName || workbook.SheetNames[0]; + const worksheet = workbook.Sheets[targetSheetName]; + + if (!worksheet) { + reject(new Error(`시트 "${targetSheetName}"를 찾을 수 없습니다.`)); + return; + } + + // JSON으로 변환 + const jsonData = XLSX.utils.sheet_to_json(worksheet); + + console.log("✅ 엑셀 가져오기 완료:", { + sheetName: targetSheetName, + rowCount: jsonData.length, + }); + + resolve(jsonData as Record[]); + } catch (error) { + console.error("❌ 엑셀 가져오기 실패:", error); + reject(error); + } + }; + + reader.onerror = () => { + reject(new Error("파일 읽기 중 오류가 발생했습니다.")); + }; + + reader.readAsBinaryString(file); + }); +} + +/** + * 엑셀 파일의 시트 목록 가져오기 + */ +export async function getExcelSheetNames(file: File): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + + reader.onload = (e) => { + try { + const data = e.target?.result; + if (!data) { + reject(new Error("파일을 읽을 수 없습니다.")); + return; + } + + const workbook = XLSX.read(data, { type: "binary" }); + resolve(workbook.SheetNames); + } catch (error) { + console.error("❌ 시트 목록 가져오기 실패:", error); + reject(error); + } + }; + + reader.onerror = () => { + reject(new Error("파일 읽기 중 오류가 발생했습니다.")); + }; + + reader.readAsBinaryString(file); + }); +} + diff --git a/frontend/lib/validation/businessNumber.ts b/frontend/lib/validation/businessNumber.ts new file mode 100644 index 00000000..81b133d2 --- /dev/null +++ b/frontend/lib/validation/businessNumber.ts @@ -0,0 +1,74 @@ +/** + * 사업자등록번호 유효성 검사 유틸리티 + */ + +/** + * 사업자등록번호 포맷 검증 (000-00-00000 형식) + */ +export function validateBusinessNumberFormat(value: string): boolean { + if (!value || value.trim() === "") { + return false; + } + + // 하이픈 제거 + const cleaned = value.replace(/-/g, ""); + + // 숫자 10자리인지 확인 + if (!/^\d{10}$/.test(cleaned)) { + return false; + } + + return true; +} + +/** + * 사업자등록번호 종합 검증 (포맷만 검사) + * 실제 국세청 검증은 백엔드에서 API 호출로 처리하는 것을 권장 + */ +export function validateBusinessNumber(value: string): { + isValid: boolean; + message: string; +} { + if (!value || value.trim() === "") { + return { + isValid: false, + message: "사업자등록번호를 입력해주세요.", + }; + } + + if (!validateBusinessNumberFormat(value)) { + return { + isValid: false, + message: "사업자등록번호는 10자리 숫자여야 합니다.", + }; + } + + // 포맷만 검증하고 통과 + return { + isValid: true, + message: "", + }; +} + +/** + * 사업자등록번호 포맷팅 (자동 하이픈 추가) + */ +export function formatBusinessNumber(value: string): string { + if (!value) return ""; + + // 숫자만 추출 + const cleaned = value.replace(/\D/g, ""); + + // 최대 10자리까지만 + const limited = cleaned.slice(0, 10); + + // 하이픈 추가 (000-00-00000) + if (limited.length <= 3) { + return limited; + } else if (limited.length <= 5) { + return `${limited.slice(0, 3)}-${limited.slice(3)}`; + } else { + return `${limited.slice(0, 3)}-${limited.slice(3, 5)}-${limited.slice(5)}`; + } +} + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5458073c..08f030e2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,6 +44,7 @@ "@types/react-window": "^1.8.8", "@types/three": "^0.180.0", "@xyflow/react": "^12.8.4", + "@zxing/library": "^0.21.3", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -69,6 +70,7 @@ "react-hot-toast": "^2.6.0", "react-leaflet": "^5.0.0", "react-resizable-panels": "^3.0.6", + "react-webcam": "^7.2.0", "react-window": "^2.1.0", "reactflow": "^11.11.4", "recharts": "^3.2.1", @@ -100,6 +102,12 @@ "typescript": "^5" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.19", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.19.tgz", + "integrity": "sha512-Pp2gAQXPZ2o7lt4j0IMwNRXqQ3pagxtDj5wctL5U2Lz4oV0ocDNlkgx4DpxfyKav4S/bePuI+SMqcBSUHLy9kg==", + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -127,9 +135,9 @@ } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.6.2.tgz", - "integrity": "sha512-+AG0jN9HTwfDLBhjhX1FKi6zlIAc/YGgEHlN/OMaHD1pOPFsC5CpYQpLkPX0aFjyaVmoq9330cQDCU4qnSL1qA==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", + "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", "license": "MIT", "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", @@ -246,9 +254,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz", - "integrity": "sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q==", + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.15.tgz", + "integrity": "sha512-q0p6zkVq2lJnmzZVPR33doA51G7YOja+FBvRdp5ISIthL0MtFCgYHHhR563z9WFGxcOn0WfjSkPDJ5Qig3H3Sw==", "funding": [ { "type": "github", @@ -262,9 +270,6 @@ "license": "MIT-0", "engines": { "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" } }, "node_modules/@csstools/css-tokenizer": { @@ -352,9 +357,9 @@ } }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.6.0.tgz", + "integrity": "sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==", "dev": true, "license": "MIT", "optional": true, @@ -364,9 +369,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.6.0.tgz", + "integrity": "sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==", "license": "MIT", "optional": true, "dependencies": { @@ -417,9 +422,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -427,13 +432,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", - "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.6", + "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -442,19 +447,22 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", - "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", - "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -489,9 +497,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", - "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "version": "9.39.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", + "integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", "dev": true, "license": "MIT", "engines": { @@ -502,9 +510,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", - "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -512,13 +520,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", - "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.2", + "@eslint/core": "^0.17.0", "levn": "^0.4.1" }, "engines": { @@ -1055,19 +1063,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1355,66 +1350,66 @@ } }, "node_modules/@prisma/config": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.16.2.tgz", - "integrity": "sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz", + "integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", - "effect": "3.16.12", + "effect": "3.18.4", "empathic": "2.0.0" } }, "node_modules/@prisma/debug": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.16.2.tgz", - "integrity": "sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz", + "integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.16.2.tgz", - "integrity": "sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz", + "integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.2", - "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", - "@prisma/fetch-engine": "6.16.2", - "@prisma/get-platform": "6.16.2" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/fetch-engine": "6.18.0", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43.tgz", - "integrity": "sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==", + "version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz", + "integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==", "dev": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.16.2.tgz", - "integrity": "sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz", + "integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.2", - "@prisma/engines-version": "6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43", - "@prisma/get-platform": "6.16.2" + "@prisma/debug": "6.18.0", + "@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f", + "@prisma/get-platform": "6.18.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.16.2.tgz", - "integrity": "sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz", + "integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.16.2" + "@prisma/debug": "6.18.0" } }, "node_modules/@radix-ui/number": { @@ -2571,35 +2566,6 @@ } } }, - "node_modules/@react-three/drei/node_modules/zustand": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", - "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, "node_modules/@react-three/fiber": { "version": "9.4.0", "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.4.0.tgz", @@ -2650,41 +2616,6 @@ } } }, - "node_modules/@react-three/fiber/node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT" - }, - "node_modules/@react-three/fiber/node_modules/zustand": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", - "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, "node_modules/@reactflow/background": { "version": "11.3.14", "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", @@ -2700,6 +2631,34 @@ "react-dom": ">=17" } }, + "node_modules/@reactflow/background/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@reactflow/controls": { "version": "11.2.14", "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", @@ -2715,6 +2674,34 @@ "react-dom": ">=17" } }, + "node_modules/@reactflow/controls/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@reactflow/core": { "version": "11.11.4", "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", @@ -2736,6 +2723,34 @@ "react-dom": ">=17" } }, + "node_modules/@reactflow/core/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@reactflow/minimap": { "version": "11.7.14", "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", @@ -2755,6 +2770,34 @@ "react-dom": ">=17" } }, + "node_modules/@reactflow/minimap/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@reactflow/node-resizer": { "version": "2.2.14", "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", @@ -2772,6 +2815,34 @@ "react-dom": ">=17" } }, + "node_modules/@reactflow/node-resizer/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@reactflow/node-toolbar": { "version": "1.3.14", "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", @@ -2787,28 +2858,30 @@ "react-dom": ">=17" } }, - "node_modules/@reduxjs/toolkit": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.0.tgz", - "integrity": "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==", + "node_modules/@reactflow/node-toolbar/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", "license": "MIT", "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@standard-schema/utils": "^0.3.0", - "immer": "^10.0.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" }, "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" }, "peerDependenciesMeta": { - "react": { + "@types/react": { "optional": true }, - "react-redux": { + "immer": { + "optional": true + }, + "react": { "optional": true } } @@ -2821,9 +2894,9 @@ "license": "MIT" }, "node_modules/@rushstack/eslint-patch": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", - "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.14.1.tgz", + "integrity": "sha512-jGTk8UD/RdjsNZW8qq10r0RBvxL8OWtoT+kImlzPDFilmozzM+9QmIJsmze9UiSBrFU45ZxhTYBypn9q9z/VfQ==", "dev": true, "license": "MIT" }, @@ -2849,54 +2922,49 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.13.tgz", - "integrity": "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.16.tgz", + "integrity": "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", - "jiti": "^2.5.1", - "lightningcss": "1.30.1", - "magic-string": "^0.30.18", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.19", "source-map-js": "^1.2.1", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.16" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.13.tgz", - "integrity": "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.16.tgz", + "integrity": "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.4", - "tar": "^7.4.3" - }, "engines": { "node": ">= 10" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-arm64": "4.1.13", - "@tailwindcss/oxide-darwin-x64": "4.1.13", - "@tailwindcss/oxide-freebsd-x64": "4.1.13", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", - "@tailwindcss/oxide-linux-x64-musl": "4.1.13", - "@tailwindcss/oxide-wasm32-wasi": "4.1.13", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" + "@tailwindcss/oxide-android-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-arm64": "4.1.16", + "@tailwindcss/oxide-darwin-x64": "4.1.16", + "@tailwindcss/oxide-freebsd-x64": "4.1.16", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", + "@tailwindcss/oxide-linux-x64-musl": "4.1.16", + "@tailwindcss/oxide-wasm32-wasi": "4.1.16", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.13.tgz", - "integrity": "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.16.tgz", + "integrity": "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA==", "cpu": [ "arm64" ], @@ -2911,9 +2979,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.13.tgz", - "integrity": "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.16.tgz", + "integrity": "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA==", "cpu": [ "arm64" ], @@ -2928,9 +2996,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.13.tgz", - "integrity": "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.16.tgz", + "integrity": "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg==", "cpu": [ "x64" ], @@ -2945,9 +3013,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.13.tgz", - "integrity": "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.16.tgz", + "integrity": "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg==", "cpu": [ "x64" ], @@ -2962,9 +3030,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.13.tgz", - "integrity": "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.16.tgz", + "integrity": "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw==", "cpu": [ "arm" ], @@ -2979,9 +3047,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.13.tgz", - "integrity": "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.16.tgz", + "integrity": "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w==", "cpu": [ "arm64" ], @@ -2996,9 +3064,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.13.tgz", - "integrity": "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.16.tgz", + "integrity": "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ==", "cpu": [ "arm64" ], @@ -3013,9 +3081,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.13.tgz", - "integrity": "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.16.tgz", + "integrity": "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew==", "cpu": [ "x64" ], @@ -3030,9 +3098,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.13.tgz", - "integrity": "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.16.tgz", + "integrity": "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw==", "cpu": [ "x64" ], @@ -3047,9 +3115,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.13.tgz", - "integrity": "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.16.tgz", + "integrity": "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -3065,21 +3133,21 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.5", - "@emnapi/runtime": "^1.4.5", - "@emnapi/wasi-threads": "^1.0.4", - "@napi-rs/wasm-runtime": "^0.2.12", - "@tybys/wasm-util": "^0.10.0", - "tslib": "^2.8.0" + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.13.tgz", - "integrity": "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", + "integrity": "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A==", "cpu": [ "arm64" ], @@ -3094,9 +3162,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.13.tgz", - "integrity": "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.16.tgz", + "integrity": "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg==", "cpu": [ "x64" ], @@ -3111,23 +3179,23 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.13.tgz", - "integrity": "sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.16.tgz", + "integrity": "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.1.13", - "@tailwindcss/oxide": "4.1.13", + "@tailwindcss/node": "4.1.16", + "@tailwindcss/oxide": "4.1.16", "postcss": "^8.4.41", - "tailwindcss": "4.1.13" + "tailwindcss": "4.1.16" } }, "node_modules/@tanstack/query-core": { - "version": "5.90.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.2.tgz", - "integrity": "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==", + "version": "5.90.6", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.6.tgz", + "integrity": "sha512-AnZSLF26R8uX+tqb/ivdrwbVdGemdEDm1Q19qM6pry6eOZ6bEYiY7mWhzXT1YDIPTNEVcZ5kYP9nWjoxDLiIVw==", "license": "MIT", "funding": { "type": "github", @@ -3146,12 +3214,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.2", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.2.tgz", - "integrity": "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==", + "version": "5.90.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.6.tgz", + "integrity": "sha512-gB1sljYjcobZKxjPbKSa31FUTyr+ROaBdoH+wSSs9Dk+yDCmMs+TkTV3PybRRVLC7ax7q0erJ9LvRWnMktnRAw==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.2" + "@tanstack/query-core": "5.90.6" }, "funding": { "type": "github", @@ -3597,21 +3665,6 @@ "url": "https://opencollective.com/turf" } }, - "node_modules/@turf/buffer/node_modules/d3-array": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", - "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", - "license": "BSD-3-Clause" - }, - "node_modules/@turf/buffer/node_modules/d3-geo": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", - "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1" - } - }, "node_modules/@turf/center": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@turf/center/-/center-7.2.0.tgz", @@ -5573,9 +5626,9 @@ } }, "node_modules/@types/node": { - "version": "20.19.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", - "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5602,22 +5655,22 @@ "optional": true }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "19.1.9", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz", - "integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", + "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", "peerDependencies": { - "@types/react": "^19.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-reconciler": { @@ -5686,17 +5739,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", - "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz", + "integrity": "sha512-ZGBMToy857/NIPaaCucIUQgqueOiq7HeAKkhlvqVV4lm089zUFW6ikRySx2v+cAhKeUCPuWVHeimyk6Dw1iY3w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/type-utils": "8.44.1", - "@typescript-eslint/utils": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/type-utils": "8.46.2", + "@typescript-eslint/utils": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -5710,7 +5763,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.44.1", + "@typescript-eslint/parser": "^8.46.2", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -5726,16 +5779,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", - "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.2.tgz", + "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4" }, "engines": { @@ -5751,14 +5804,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", - "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.2.tgz", + "integrity": "sha512-PULOLZ9iqwI7hXcmL4fVfIsBi6AN9YxRc0frbvmg8f+4hQAjQ5GYNKK0DIArNo+rOKmR/iBYwkpBmnIwin4wBg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.44.1", - "@typescript-eslint/types": "^8.44.1", + "@typescript-eslint/tsconfig-utils": "^8.46.2", + "@typescript-eslint/types": "^8.46.2", "debug": "^4.3.4" }, "engines": { @@ -5773,14 +5826,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", - "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.2.tgz", + "integrity": "sha512-LF4b/NmGvdWEHD2H4MsHD8ny6JpiVNDzrSZr3CsckEgCbAGZbYM4Cqxvi9L+WqDMT+51Ozy7lt2M+d0JLEuBqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1" + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5791,9 +5844,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", - "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.2.tgz", + "integrity": "sha512-a7QH6fw4S57+F5y2FIxxSDyi5M4UfGF+Jl1bCGd7+L4KsaUY80GsiF/t0UoRFDHAguKlBaACWJRmdrc6Xfkkag==", "dev": true, "license": "MIT", "engines": { @@ -5808,15 +5861,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", - "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.2.tgz", + "integrity": "sha512-HbPM4LbaAAt/DjxXaG9yiS9brOOz6fabal4uvUmaUYe6l3K1phQDMQKBRUrr06BQkxkvIZVVHttqiybM9nJsLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1", - "@typescript-eslint/utils": "8.44.1", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2", + "@typescript-eslint/utils": "8.46.2", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -5833,9 +5886,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", - "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.2.tgz", + "integrity": "sha512-lNCWCbq7rpg7qDsQrd3D6NyWYu+gkTENkG5IKYhUIcxSb59SQC/hEQ+MrG4sTgBVghTonNWq42bA/d4yYumldQ==", "dev": true, "license": "MIT", "engines": { @@ -5847,16 +5900,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", - "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.2.tgz", + "integrity": "sha512-f7rW7LJ2b7Uh2EiQ+7sza6RDZnajbNbemn54Ob6fRwQbgcIn+GWfyuHDHRYgRoZu1P4AayVScrRW+YfbTvPQoQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.44.1", - "@typescript-eslint/tsconfig-utils": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/visitor-keys": "8.44.1", + "@typescript-eslint/project-service": "8.46.2", + "@typescript-eslint/tsconfig-utils": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/visitor-keys": "8.46.2", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -5932,16 +5985,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", - "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.2.tgz", + "integrity": "sha512-sExxzucx0Tud5tE0XqR0lT0psBQvEpnpiul9XbGUB1QwpWJJAps1O/Z7hJxLGiZLBKMCutjTzDgmd1muEhBnVg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.44.1", - "@typescript-eslint/types": "8.44.1", - "@typescript-eslint/typescript-estree": "8.44.1" + "@typescript-eslint/scope-manager": "8.46.2", + "@typescript-eslint/types": "8.46.2", + "@typescript-eslint/typescript-estree": "8.46.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5956,13 +6009,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.44.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", - "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "version": "8.46.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.2.tgz", + "integrity": "sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/types": "8.46.2", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -6276,12 +6329,12 @@ } }, "node_modules/@xyflow/react": { - "version": "12.8.5", - "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.5.tgz", - "integrity": "sha512-NRwcE8QE7dh6BbaIT7GmNccP7/RMDZJOKtzK4HQw599TAfzC8e5E/zw/7MwtpnSbbkqBYc+jZyOisd57sp/hPQ==", + "version": "12.9.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.9.2.tgz", + "integrity": "sha512-Xr+LFcysHCCoc5KRHaw+FwbqbWYxp9tWtk1mshNcqy25OAPuaKzXSdqIMNOA82TIXF/gFKo0Wgpa6PU7wUUVqw==", "license": "MIT", "dependencies": { - "@xyflow/system": "0.0.69", + "@xyflow/system": "0.0.72", "classcat": "^5.0.3", "zustand": "^4.4.0" }, @@ -6290,10 +6343,38 @@ "react-dom": ">=17" } }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@xyflow/system": { - "version": "0.0.69", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.69.tgz", - "integrity": "sha512-+KYwHDnsapZQ1xSgsYwOKYN93fUR770LwfCT5qrvcmzoMaabO1rHa6twiEk7E5VUIceWciF8ukgfq9JC83B5jQ==", + "version": "0.0.72", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.72.tgz", + "integrity": "sha512-WBI5Aau0fXTXwxHPzceLNS6QdXggSWnGjDtj/gG669crApN8+SCmEtkBth1m7r6pStNo/5fI9McEi7Dk0ymCLA==", "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", @@ -6307,6 +6388,28 @@ "d3-zoom": "^3.0.0" } }, + "node_modules/@zxing/library": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.21.3.tgz", + "integrity": "sha512-hZHqFe2JyH/ZxviJZosZjV+2s6EDSY0O24R+FQmlWZBZXP9IqMo7S3nb3+2LBWxodJQkSurdQGnqE7KXqrYgow==", + "license": "MIT", + "dependencies": { + "ts-custom-error": "^3.2.1" + }, + "engines": { + "node": ">= 10.4.0" + }, + "optionalDependencies": { + "@zxing/text-encoding": "~0.9.0" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -6617,9 +6720,9 @@ } }, "node_modules/axe-core": { - "version": "4.10.3", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", - "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", + "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", "dev": true, "license": "MPL-2.0", "engines": { @@ -6627,9 +6730,9 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -6857,9 +6960,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001745", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", - "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "version": "1.0.30001753", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001753.tgz", + "integrity": "sha512-Bj5H35MD/ebaOV4iDLqPEtiliTN29qkGtEHCwawWn4cYm+bPJM2NsaP30vtZcnERClMzp52J4+aw2UNbK4o+zw==", "funding": [ { "type": "opencollective", @@ -6951,16 +7054,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/citty": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", @@ -7075,10 +7168,13 @@ } }, "node_modules/commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "license": "MIT" + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -7099,12 +7195,6 @@ "tinyqueue": "^2.0.3" } }, - "node_modules/concaveman/node_modules/robust-predicates": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", - "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", - "license": "Unlicense" - }, "node_modules/confbox": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", @@ -7207,9 +7297,9 @@ } }, "node_modules/cssstyle": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.1.tgz", - "integrity": "sha512-g5PC9Aiph9eiczFpcgUhd9S4UUO3F+LHGRIi5NUMZ+4xtoIYbHNZwZnWA2JsFGe8OU8nl4WyaEFiZuGuxlutJQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.2.tgz", + "integrity": "sha512-zDMqXh8Vs1CdRYZQ2M633m/SFgcjlu8RB8b/1h82i+6vpArF507NSYIWJHGlJaTWoS+imcnctmEz43txhbVkOw==", "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^4.0.3", @@ -7396,15 +7486,6 @@ "node": ">=12" } }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -7450,17 +7531,20 @@ } }, "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "license": "BSD-3-Clause", "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" + "d3-array": "1" } }, + "node_modules/d3-geo/node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==", + "license": "BSD-3-Clause" + }, "node_modules/d3-hierarchy": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", @@ -7642,6 +7726,18 @@ "node": ">=12" } }, + "node_modules/d3/node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7830,6 +7926,12 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delaunator/node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7856,9 +7958,9 @@ } }, "node_modules/detect-libc": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.1.tgz", - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "devOptional": true, "license": "Apache-2.0", "engines": { @@ -7888,15 +7990,6 @@ "redux": "^4.2.0" } }, - "node_modules/dnd-core/node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -7928,45 +8021,27 @@ } }, "node_modules/docx-preview": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.6.tgz", - "integrity": "sha512-gKVPE18hlpfuhQHiptsw1rbOwzQeGSwK10/w7hv1ZMEqHmjtCuTpz6AUMfu1twIPGxgpcsMXThKI6B6WsP3L1w==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.7.tgz", + "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", "license": "Apache-2.0", "dependencies": { "jszip": ">=3.0.0" } }, "node_modules/docx/node_modules/@types/node": { - "version": "24.6.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.1.tgz", - "integrity": "sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==", + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", "license": "MIT", "dependencies": { - "undici-types": "~7.13.0" - } - }, - "node_modules/docx/node_modules/nanoid": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", - "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" + "undici-types": "~7.16.0" } }, "node_modules/docx/node_modules/undici-types": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", - "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT" }, "node_modules/dompurify": { @@ -8027,9 +8102,9 @@ "license": "ISC" }, "node_modules/effect": { - "version": "3.16.12", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz", - "integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==", + "version": "3.18.4", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz", + "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==", "dev": true, "license": "MIT", "dependencies": { @@ -8254,9 +8329,9 @@ } }, "node_modules/es-toolkit": { - "version": "1.39.10", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.10.tgz", - "integrity": "sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==", + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.41.0.tgz", + "integrity": "sha512-bDd3oRmbVgqZCJS6WmeQieOrzpl3URcWBUVDXxOELlUW2FuW+0glPOz1n0KnRie+PdyvUZcXz2sOn00c6pPRIA==", "license": "MIT", "workspaces": [ "docs", @@ -8277,25 +8352,24 @@ } }, "node_modules/eslint": { - "version": "9.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", - "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "version": "9.39.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.0.tgz", + "integrity": "sha512-iy2GE3MHrYTL5lrCtMZ0X1KLEKKUjmK0kzwcnefhR66txcEmXZD2YWgR5GNdcEwkNx3a0siYkSvl0vIC+Svjmg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.1", - "@eslint/core": "^0.15.2", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.36.0", - "@eslint/plugin-kit": "^0.3.5", + "@eslint/js": "9.39.0", + "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", @@ -8862,12 +8936,6 @@ "pako": "^2.1.0" } }, - "node_modules/fast-png/node_modules/pako": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", - "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", - "license": "(MIT AND Zlib)" - }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -9049,6 +9117,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/geojson-equality-ts": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/geojson-equality-ts/-/geojson-equality-ts-1.0.2.tgz", @@ -9147,9 +9225,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9227,9 +9305,9 @@ "license": "MIT" }, "node_modules/goober": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", - "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", "license": "MIT", "peerDependencies": { "csstype": "^3.0.10" @@ -9363,9 +9441,9 @@ } }, "node_modules/hls.js": { - "version": "1.6.13", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.13.tgz", - "integrity": "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==", + "version": "1.6.14", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.14.tgz", + "integrity": "sha512-CSpT2aXsv71HST8C5ETeVo+6YybqCpHBiYrCRQSn3U5QUZuLTSsvtq/bj+zuvjLVADeKxoebzo16OkH8m1+65Q==", "license": "Apache-2.0" }, "node_modules/hoist-non-react-statics": { @@ -9377,6 +9455,12 @@ "react-is": "^16.7.0" } }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -9483,9 +9567,9 @@ "license": "MIT" }, "node_modules/immer": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz", - "integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", "license": "MIT", "funding": { "type": "opencollective", @@ -9727,14 +9811,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", - "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "get-proto": "^1.0.0", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" }, @@ -9969,10 +10054,9 @@ } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, "node_modules/isexe": { @@ -9982,16 +10066,16 @@ "license": "ISC" }, "node_modules/isomorphic-dompurify": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.28.0.tgz", - "integrity": "sha512-9G5v8g4tYoix5odskjG704Khm1zNrqqqOC4YjCwEUhx0OvuaijRCprAV2GwJ9iw/01c6H1R+rs/2AXPZLlgDaQ==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.31.0.tgz", + "integrity": "sha512-/XPACpfVJeEiy28UgkBWUWdhgKN8xwFYkoVFsqrcSJJ5pXZ3HStuF3ih/Hr8PwhCXHqFAys+b4tcgw0pbUT4rw==", "license": "MIT", "dependencies": { - "dompurify": "^3.2.7", - "jsdom": "^27.0.0" + "dompurify": "^3.3.0", + "jsdom": "^27.1.0" }, "engines": { - "node": ">=18" + "node": ">=20.19.5" } }, "node_modules/iterator.prototype": { @@ -10034,9 +10118,9 @@ } }, "node_modules/jiti": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz", - "integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -10064,21 +10148,21 @@ } }, "node_modules/jsdom": { - "version": "27.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.0.tgz", - "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==", + "version": "27.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.1.0.tgz", + "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", "license": "MIT", "dependencies": { - "@asamuzakjp/dom-selector": "^6.5.4", - "cssstyle": "^5.3.0", + "@acemir/cssom": "^0.9.19", + "@asamuzakjp/dom-selector": "^6.7.3", + "cssstyle": "^5.3.2", "data-urls": "^6.0.0", - "decimal.js": "^10.5.0", + "decimal.js": "^10.6.0", "html-encoding-sniffer": "^4.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "parse5": "^7.3.0", - "rrweb-cssom": "^0.8.0", + "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", @@ -10086,12 +10170,12 @@ "webidl-conversions": "^8.0.0", "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0", - "ws": "^8.18.2", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=20" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" }, "peerDependencies": { "canvas": "^3.0.0" @@ -10190,6 +10274,12 @@ "setimmediate": "^1.0.5" } }, + "node_modules/jszip/node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -10250,9 +10340,9 @@ } }, "node_modules/lightningcss": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", - "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -10266,22 +10356,44 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-darwin-arm64": "1.30.1", - "lightningcss-darwin-x64": "1.30.1", - "lightningcss-freebsd-x64": "1.30.1", - "lightningcss-linux-arm-gnueabihf": "1.30.1", - "lightningcss-linux-arm64-gnu": "1.30.1", - "lightningcss-linux-arm64-musl": "1.30.1", - "lightningcss-linux-x64-gnu": "1.30.1", - "lightningcss-linux-x64-musl": "1.30.1", - "lightningcss-win32-arm64-msvc": "1.30.1", - "lightningcss-win32-x64-msvc": "1.30.1" + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.1.tgz", - "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", "cpu": [ "arm64" ], @@ -10300,9 +10412,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", - "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", "cpu": [ "x64" ], @@ -10321,9 +10433,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.1.tgz", - "integrity": "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", "cpu": [ "x64" ], @@ -10342,9 +10454,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.1.tgz", - "integrity": "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", "cpu": [ "arm" ], @@ -10363,9 +10475,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.1.tgz", - "integrity": "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", "cpu": [ "arm64" ], @@ -10384,9 +10496,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.1.tgz", - "integrity": "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", "cpu": [ "arm64" ], @@ -10405,9 +10517,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.1.tgz", - "integrity": "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", "cpu": [ "x64" ], @@ -10426,9 +10538,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.1.tgz", - "integrity": "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", "cpu": [ "x64" ], @@ -10447,9 +10559,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.1.tgz", - "integrity": "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", "cpu": [ "arm64" ], @@ -10468,9 +10580,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.1.tgz", - "integrity": "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==", + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", "cpu": [ "x64" ], @@ -10564,9 +10676,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10716,29 +10828,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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" - } - }, - "node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10746,9 +10835,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", "funding": [ { "type": "github", @@ -10757,16 +10846,16 @@ ], "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, "license": "MIT", "bin": { @@ -10838,6 +10927,24 @@ } } }, + "node_modules/next/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -11098,9 +11205,9 @@ } }, "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { @@ -11117,9 +11224,9 @@ } }, "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -11230,6 +11337,12 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/point-in-polygon-hao/node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/polyclip-ts": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/polyclip-ts/-/polyclip-ts-0.16.8.tgz", @@ -11254,6 +11367,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -11278,6 +11392,25 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/potpack": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", @@ -11423,15 +11556,15 @@ } }, "node_modules/prisma": { - "version": "6.16.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.16.2.tgz", - "integrity": "sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz", + "integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.16.2", - "@prisma/engines": "6.16.2" + "@prisma/config": "6.18.0", + "@prisma/engines": "6.18.0" }, "bin": { "prisma": "build/index.js" @@ -11476,6 +11609,13 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -11646,10 +11786,16 @@ "react": "^19.1.0" } }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, "node_modules/react-hook-form": { - "version": "7.63.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.63.0.tgz", - "integrity": "sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==", + "version": "7.66.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz", + "integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -11680,10 +11826,11 @@ } }, "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", + "license": "MIT", + "peer": true }, "node_modules/react-leaflet": { "version": "5.0.0", @@ -11714,35 +11861,6 @@ "react": "^19.0.0" } }, - "node_modules/react-reconciler/node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT" - }, - "node_modules/react-redux": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", - "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "use-sync-external-store": "^1.4.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25 || ^19", - "react": "^18.0 || ^19", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, "node_modules/react-remove-scroll": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", @@ -11837,10 +11955,20 @@ } } }, + "node_modules/react-webcam": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-webcam/-/react-webcam-7.2.0.tgz", + "integrity": "sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.2.0", + "react-dom": ">=16.2.0" + } + }, "node_modules/react-window": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.1.1.tgz", - "integrity": "sha512-Wx5yHri8G1nFxImnJRkEEKtRTnG3cWaqknUJyYvgisQtl1mw/d8LQmLXfuKxpn2dY8IwDn5mCIuxm2NVyIvgVQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.2.tgz", + "integrity": "sha512-kvHKwFImKBWNbx2S87NZOhQhAVkBthjmnOfHlhQI45p3A+D+V53E+CqQMsyHrxNe3ke+YtWXuKDa1eoHAaIWJg==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0", @@ -11880,12 +12008,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readable-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -11901,9 +12023,9 @@ } }, "node_modules/recharts": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.2.1.tgz", - "integrity": "sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.3.0.tgz", + "integrity": "sha512-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg==", "license": "MIT", "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", @@ -11927,13 +12049,62 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/redux": { + "node_modules/recharts/node_modules/@reduxjs/toolkit": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.9.2.tgz", + "integrity": "sha512-ZAYu/NXkl/OhqTz7rfPaAhY0+e8Fr15jqNxte/2exKUxvHyQ/hcqmdekiN1f+Lcw3pE+34FCgX+26zcUE3duCg==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/recharts/node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/recharts/node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "license": "MIT" }, - "node_modules/redux-thunk": { + "node_modules/recharts/node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", @@ -11942,6 +12113,15 @@ "redux": "^5.0.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -12009,13 +12189,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -12071,17 +12251,11 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==", "license": "Unlicense" }, - "node_modules/rrweb-cssom": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", - "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", - "license": "MIT" - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -12132,6 +12306,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -12155,6 +12336,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-push-apply/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -12198,15 +12386,15 @@ } }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "devOptional": true, "license": "ISC", "bin": { @@ -12357,6 +12545,12 @@ "node": ">=0.8" } }, + "node_modules/sheetjs-style/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "license": "MIT" + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -12797,16 +12991,16 @@ } }, "node_modules/tailwindcss": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", - "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", + "integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==", "dev": true, "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { @@ -12817,23 +13011,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", - "dev": true, - "license": "ISC", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -12993,6 +13170,12 @@ "topoquantize": "bin/topoquantize" } }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/topojson-server": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", @@ -13005,6 +13188,12 @@ "geo2topo": "bin/geo2topo" } }, + "node_modules/topojson-server/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/tough-cookie": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", @@ -13072,6 +13261,15 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-custom-error": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz", + "integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -13100,6 +13298,34 @@ "zustand": "^4.3.2" } }, + "node_modules/tunnel-rat/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -13202,9 +13428,9 @@ } }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -13336,9 +13562,9 @@ } }, "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -13532,6 +13758,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-builtin-type/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/which-collection": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", @@ -13715,16 +13948,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "license": "MIT" }, - "node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -13739,29 +13962,27 @@ } }, "node_modules/zod": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", - "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz", + "integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.8.tgz", + "integrity": "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==", "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, "engines": { - "node": ">=12.7.0" + "node": ">=12.20.0" }, "peerDependencies": { - "@types/react": ">=16.8", + "@types/react": ">=18.0.0", "immer": ">=9.0.6", - "react": ">=16.8" + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" }, "peerDependenciesMeta": { "@types/react": { @@ -13772,6 +13993,9 @@ }, "react": { "optional": true + }, + "use-sync-external-store": { + "optional": true } } } diff --git a/frontend/package.json b/frontend/package.json index fe7fc518..6d4f3369 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,6 +52,7 @@ "@types/react-window": "^1.8.8", "@types/three": "^0.180.0", "@xyflow/react": "^12.8.4", + "@zxing/library": "^0.21.3", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -77,6 +78,7 @@ "react-hot-toast": "^2.6.0", "react-leaflet": "^5.0.0", "react-resizable-panels": "^3.0.6", + "react-webcam": "^7.2.0", "react-window": "^2.1.0", "reactflow": "^11.11.4", "recharts": "^3.2.1", diff --git a/frontend/types/company.ts b/frontend/types/company.ts index 503ce856..d4c9272a 100644 --- a/frontend/types/company.ts +++ b/frontend/types/company.ts @@ -6,6 +6,12 @@ export interface Company { company_code: string; // 회사 코드 (varchar 32) - PK company_name: string; // 회사명 (varchar 64) + business_registration_number?: string; // 사업자등록번호 (varchar 20) + representative_name?: string; // 대표자명 (varchar 100) + representative_phone?: string; // 대표 연락처 (varchar 20) + email?: string; // 이메일 (varchar 255) + website?: string; // 웹사이트 (varchar 500) + address?: string; // 회사 주소 (text) writer: string; // 등록자 (varchar 32) regdate: string; // 등록일시 (timestamp -> ISO string) status: string; // 상태 (varchar 32) @@ -20,7 +26,13 @@ export interface Company { // 회사 등록/수정 폼 데이터 export interface CompanyFormData { - company_name: string; // 등록 시에는 회사명만 필요 + company_name: string; // 회사명 (필수) + business_registration_number: string; // 사업자등록번호 (필수) + representative_name?: string; // 대표자명 + representative_phone?: string; // 대표 연락처 + email?: string; // 이메일 + website?: string; // 웹사이트 + address?: string; // 회사 주소 } // 회사 검색 필터 diff --git a/frontend/types/department.ts b/frontend/types/department.ts new file mode 100644 index 00000000..0abc25d0 --- /dev/null +++ b/frontend/types/department.ts @@ -0,0 +1,69 @@ +/** + * 부서 관리 관련 타입 정의 + */ + +// 부서 정보 (dept_info 테이블 기반) +export interface Department { + dept_code: string; // 부서 코드 + dept_name: string; // 부서명 + company_code: string; // 회사 코드 + parent_dept_code?: string | null; // 상위 부서 코드 + sort_order?: number; // 정렬 순서 + created_at?: string; + updated_at?: string; + // UI용 추가 필드 + children?: Department[]; // 하위 부서 목록 + memberCount?: number; // 부서원 수 +} + +// 부서원 정보 +export interface DepartmentMember { + user_id: string; // 사용자 ID + user_name: string; // 사용자명 + dept_code: string; // 부서 코드 + dept_name: string; // 부서명 + is_primary: boolean; // 주 부서 여부 + position_name?: string; // 직책명 + email?: string; // 이메일 + phone?: string; // 전화번호 + cell_phone?: string; // 휴대폰 +} + +// 사용자-부서 매핑 (겸직 지원) +export interface UserDepartmentMapping { + user_id: string; + dept_code: string; + is_primary: boolean; // 주 부서 여부 + created_at?: string; +} + +// 부서 등록/수정 폼 데이터 +export interface DepartmentFormData { + dept_name: string; // 부서명 (필수) + parent_dept_code?: string | null; // 상위 부서 코드 +} + +// 부서 트리 노드 (UI용) +export interface DepartmentTreeNode { + dept_code: string; + dept_name: string; + parent_dept_code?: string | null; + children: DepartmentTreeNode[]; + memberCount: number; + isExpanded: boolean; +} + +// 부서 API 응답 +export interface DepartmentApiResponse { + success: boolean; + message: string; + data?: Department | Department[]; +} + +// 부서원 API 응답 +export interface DepartmentMemberApiResponse { + success: boolean; + message: string; + data?: DepartmentMember | DepartmentMember[]; +} + diff --git a/frontend/types/screen-management.ts b/frontend/types/screen-management.ts index b2eff0f1..56a9ba2d 100644 --- a/frontend/types/screen-management.ts +++ b/frontend/types/screen-management.ts @@ -84,6 +84,15 @@ export interface WidgetComponent extends BaseComponent { entityConfig?: EntityTypeConfig; buttonConfig?: ButtonTypeConfig; arrayConfig?: ArrayTypeConfig; + + // 🆕 자동 입력 설정 (테이블 조회 기반) + autoFill?: { + enabled: boolean; // 자동 입력 활성화 + sourceTable: string; // 조회할 테이블 (예: company_mng) + filterColumn: string; // 필터링할 컬럼 (예: company_code) + userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보 필드 + displayColumn: string; // 표시할 컬럼 (예: company_name) + }; } /** @@ -121,6 +130,13 @@ export interface DataTableComponent extends BaseComponent { searchable?: boolean; sortable?: boolean; filters?: DataTableFilter[]; + + // 🆕 현재 사용자 정보로 자동 필터링 + autoFilter?: { + enabled: boolean; // 자동 필터 활성화 여부 + filterColumn: string; // 필터링할 테이블 컬럼 (예: company_code, dept_code) + userField: 'companyCode' | 'userId' | 'deptCode'; // 사용자 정보에서 가져올 필드 + }; } /**