diff --git a/bun.lock b/bun.lock index cb925c7..1f04f05 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "acad-ia-2", "dependencies": { "@radix-ui/react-avatar": "^1.1.11", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.8", "@radix-ui/react-popover": "^1.1.15", @@ -25,10 +26,11 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", - "lucide-react": "^0.561.0", + "lucide-react": "^0.562.0", + "motion": "^12.24.7", "react": "^19.2.0", "react-dom": "^19.2.0", - "tailwind-merge": "^3.0.2", + "tailwind-merge": "^3.4.0", "tailwindcss": "^4.0.6", "tw-animate-css": "^1.3.6", }, @@ -45,6 +47,7 @@ "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-unused-imports": "^4.3.0", "jsdom": "^27.0.0", "prettier": "^3.5.3", @@ -239,6 +242,8 @@ "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], @@ -271,8 +276,6 @@ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], @@ -759,6 +762,8 @@ "eslint-plugin-n": ["eslint-plugin-n@17.23.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.5.0", "enhanced-resolve": "^5.17.1", "eslint-plugin-es-x": "^7.8.0", "get-tsconfig": "^4.8.1", "globals": "^15.11.0", "globrex": "^0.1.2", "ignore": "^5.3.2", "semver": "^7.6.3", "ts-declaration-location": "^1.0.6" }, "peerDependencies": { "eslint": ">=8.23.0" } }, "sha512-68PealUpYoHOBh332JLLD9Sj7OQUDkFpmcfqt8R9sySfFSeuGJjMTJQvCRRB96zO3A/PELRLkPrzsHmzEFQQ5A=="], + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="], + "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.3.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA=="], "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], @@ -801,6 +806,8 @@ "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + "framer-motion": ["framer-motion@12.24.7", "", { "dependencies": { "motion-dom": "^12.24.3", "motion-utils": "^12.23.28", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-EolFLm7NdEMhWO/VTMZ0LlR4fLHGDiJItTx3i8dlyQooOOBoYAaysK4paGD4PrwqnoDdeDOS+TxnSBIAnNHs3w=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], @@ -851,7 +858,11 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="], + + "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@4.0.0", "", { "dependencies": { "whatwg-encoding": "^3.1.1" } }, "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], @@ -989,7 +1000,7 @@ "lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], - "lucide-react": ["lucide-react@0.561.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A=="], + "lucide-react": ["lucide-react@0.562.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw=="], "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], @@ -1003,6 +1014,12 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "motion": ["motion@12.24.7", "", { "dependencies": { "framer-motion": "^12.24.7", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-0jOoqFlQ7JBvAcRhRv28pwUgZ1xw9WS4+tCU6aqYjxgiNVZCVi34ED2cihW3EgjIIWPBoZJis5og1mx/LmQWVQ=="], + + "motion-dom": ["motion-dom@12.24.3", "", { "dependencies": { "motion-utils": "^12.23.28" } }, "sha512-ZjMZCwhTglim0LM64kC1iFdm4o+2P9IKk3rl/Nb4RKsb5p4O9HJ1C2LWZXOFdsRtp6twpqWRXaFKOduF30ntow=="], + + "motion-utils": ["motion-utils@12.23.28", "", {}, "sha512-0W6cWd5Okoyf8jmessVK3spOmbyE0yTdNKujHctHH9XdAE4QDuZ1/LjSXC68rrhsJU+TkzXURC5OdSWh9ibOwQ=="], + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], @@ -1293,6 +1310,8 @@ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -1301,6 +1320,10 @@ "@radix-ui/react-arrow/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-checkbox/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-checkbox/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-collection/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1333,10 +1356,6 @@ "@radix-ui/react-roving-focus/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-scroll-area/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-scroll-area/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-select/@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], @@ -1417,6 +1436,8 @@ "@radix-ui/react-arrow/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-checkbox/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-dismissable-layer/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-focus-scope/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -1427,8 +1448,6 @@ "@radix-ui/react-roving-focus/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-scroll-area/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-tabs/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], diff --git a/components.json b/components.json index 6998bdf..5eeb81e 100644 --- a/components.json +++ b/components.json @@ -17,5 +17,11 @@ "lib": "@/lib", "hooks": "@/hooks" }, - "iconLibrary": "lucide" -} \ No newline at end of file + "iconLibrary": "lucide", + "registries": { + "@shadcn-studio": "https://shadcnstudio.com/r/{name}.json", + "@ss-components": "https://shadcnstudio.com/r/components/{name}.json", + "@ss-blocks": "https://shadcnstudio.com/r/blocks/{name}.json", + "@ss-themes": "https://shadcnstudio.com/r/themes/{name}.json" + } +} diff --git a/eslint.config.js b/eslint.config.js index 386af91..9bd9ca1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,6 +3,7 @@ import { tanstackConfig } from '@tanstack/eslint-config' import eslintConfigPrettier from 'eslint-config-prettier' import jsxA11y from 'eslint-plugin-jsx-a11y' +import reactHooks from 'eslint-plugin-react-hooks' import unusedImports from 'eslint-plugin-unused-imports' export default [ @@ -24,9 +25,12 @@ export default [ // 3. TUS REGLAS Y CONFIGURACIÓN "PRO" { + // Opcional: Puedes ser explícito sobre dónde aplicar esto + files: ['**/*.{ts,tsx,js,jsx}'], plugins: { 'jsx-a11y': jsxA11y, 'unused-imports': unusedImports, + 'react-hooks': reactHooks, }, // Configuración robusta del Resolver (La versión de Copilot) settings: { @@ -44,7 +48,8 @@ export default [ // --- REGLAS DE ACCESIBILIDAD (A11Y) --- // Activamos las recomendadas manualmente ...jsxA11y.configs.recommended.rules, - + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', // --- ORDEN DE IMPORTS --- 'sort-imports': 'off', // Apagamos el nativo 'import/order': [ diff --git a/package.json b/package.json index 12315ca..fce2a72 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.8", @@ -43,10 +44,11 @@ "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", - "lucide-react": "^0.561.0", + "lucide-react": "^0.562.0", + "motion": "^12.24.7", "react": "^19.2.0", "react-dom": "^19.2.0", - "tailwind-merge": "^3.0.2", + "tailwind-merge": "^3.4.0", "tailwindcss": "^4.0.6", "tw-animate-css": "^1.3.6" }, @@ -63,6 +65,7 @@ "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-unused-imports": "^4.3.0", "jsdom": "^27.0.0", "prettier": "^3.5.3", diff --git a/src/components/asignaturas/wizard/PasoBasicosForm.tsx b/src/components/asignaturas/wizard/PasoBasicosForm.tsx index 4fdeacf..166907f 100644 --- a/src/components/asignaturas/wizard/PasoBasicosForm.tsx +++ b/src/components/asignaturas/wizard/PasoBasicosForm.tsx @@ -1,7 +1,7 @@ import type { NewSubjectWizardState, TipoAsignatura, -} from '@/features/asignaturas/new/types' +} from '@/features/asignaturas/nueva/types' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' @@ -15,7 +15,7 @@ import { import { ESTRUCTURAS_SEP, TIPOS_MATERIA, -} from '@/features/asignaturas/new/catalogs' +} from '@/features/asignaturas/nueva/catalogs' export function PasoBasicosForm({ wizard, diff --git a/src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx b/src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx index 947869d..a6e8429 100644 --- a/src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx +++ b/src/components/asignaturas/wizard/PasoConfiguracionPanel.tsx @@ -1,6 +1,6 @@ import * as Icons from 'lucide-react' -import type { NewSubjectWizardState } from '@/features/asignaturas/new/types' +import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' import { Button } from '@/components/ui/button' import { @@ -25,7 +25,7 @@ import { FACULTADES, MATERIAS_MOCK, PLANES_MOCK, -} from '@/features/asignaturas/new/catalogs' +} from '@/features/asignaturas/nueva/catalogs' export function PasoConfiguracionPanel({ wizard, diff --git a/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx b/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx index b5f1dc3..1ddcf4b 100644 --- a/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx +++ b/src/components/asignaturas/wizard/PasoMetodoCardGroup.tsx @@ -4,7 +4,7 @@ import type { ModoCreacion, NewSubjectWizardState, SubModoClonado, -} from '@/features/asignaturas/new/types' +} from '@/features/asignaturas/nueva/types' import { Card, diff --git a/src/components/asignaturas/wizard/PasoResumenCard.tsx b/src/components/asignaturas/wizard/PasoResumenCard.tsx index ef708dc..f6f64df 100644 --- a/src/components/asignaturas/wizard/PasoResumenCard.tsx +++ b/src/components/asignaturas/wizard/PasoResumenCard.tsx @@ -1,6 +1,6 @@ import * as Icons from 'lucide-react' -import type { NewSubjectWizardState } from '@/features/asignaturas/new/types' +import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' import { Card, @@ -9,7 +9,7 @@ import { CardHeader, CardTitle, } from '@/components/ui/card' -import { ESTRUCTURAS_SEP } from '@/features/asignaturas/new/catalogs' +import { ESTRUCTURAS_SEP } from '@/features/asignaturas/nueva/catalogs' export function PasoResumenCard({ wizard }: { wizard: NewSubjectWizardState }) { return ( diff --git a/src/components/asignaturas/wizard/WizardControls.tsx b/src/components/asignaturas/wizard/WizardControls.tsx index 73b6e38..9e39b0e 100644 --- a/src/components/asignaturas/wizard/WizardControls.tsx +++ b/src/components/asignaturas/wizard/WizardControls.tsx @@ -1,4 +1,4 @@ -import type { NewSubjectWizardState } from '@/features/asignaturas/new/types' +import type { NewSubjectWizardState } from '@/features/asignaturas/nueva/types' import { Button } from '@/components/ui/button' diff --git a/src/components/planes/wizard/PasoBasicosForm.tsx b/src/components/planes/wizard/PasoBasicosForm.tsx deleted file mode 100644 index e7f16c1..0000000 --- a/src/components/planes/wizard/PasoBasicosForm.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import type { CARRERAS } from '@/features/planes/new/catalogs' -import type { NewPlanWizardState } from '@/features/planes/new/types' - -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { - FACULTADES, - NIVELES, - TIPOS_CICLO, -} from '@/features/planes/new/catalogs' - -export function PasoBasicosForm({ - wizard, - onChange, - carrerasFiltradas, -}: { - wizard: NewPlanWizardState - onChange: React.Dispatch> - carrerasFiltradas: typeof CARRERAS -}) { - return ( -
-
- - ) => - onChange((w) => ({ - ...w, - datosBasicos: { ...w.datosBasicos, nombrePlan: e.target.value }, - })) - } - /> -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - ) => - onChange((w) => ({ - ...w, - datosBasicos: { - ...w.datosBasicos, - numCiclos: Number(e.target.value || 1), - }, - })) - } - /> -
-
- ) -} diff --git a/src/components/planes/wizard/PasoBasicosForm/PasoBasicosForm.tsx b/src/components/planes/wizard/PasoBasicosForm/PasoBasicosForm.tsx new file mode 100644 index 0000000..bdc6b97 --- /dev/null +++ b/src/components/planes/wizard/PasoBasicosForm/PasoBasicosForm.tsx @@ -0,0 +1,253 @@ +import { TemplateSelectorCard } from './TemplateSelectorCard' + +import type { CARRERAS } from '@/features/planes/nuevo/catalogs' +import type { NewPlanWizardState } from '@/features/planes/nuevo/types' + +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Separator } from '@/components/ui/separator' +import { + FACULTADES, + NIVELES, + TIPOS_CICLO, + PLANTILLAS_ANEXO_1, + PLANTILLAS_ANEXO_2, +} from '@/features/planes/nuevo/catalogs' +import { cn } from '@/lib/utils' + +export function PasoBasicosForm({ + wizard, + onChange, + carrerasFiltradas, +}: { + wizard: NewPlanWizardState + onChange: React.Dispatch> + carrerasFiltradas: typeof CARRERAS +}) { + return ( +
+
+
+ + ) => + onChange((w) => ({ + ...w, + datosBasicos: { ...w.datosBasicos, nombrePlan: e.target.value }, + })) + } + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + /> +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + ) => + onChange((w) => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + // Keep undefined when the input is empty so the field stays optional + numCiclos: + e.target.value === '' ? undefined : Number(e.target.value), + }, + })) + } + className="placeholder:text-muted-foreground/70 font-medium not-italic placeholder:font-normal placeholder:italic" + placeholder="Ej. 8" + /> +
+
+ +
+ + onChange((w) => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + plantillaPlanId: templateId, + plantillaPlanVersion: version, + }, + })) + } + /> + + onChange((w) => ({ + ...w, + datosBasicos: { + ...w.datosBasicos, + plantillaMapaId: templateId, + plantillaMapaVersion: version, + }, + })) + } + /> +
+
+ ) +} diff --git a/src/components/planes/wizard/PasoBasicosForm/TemplateSelectorCard.tsx b/src/components/planes/wizard/PasoBasicosForm/TemplateSelectorCard.tsx new file mode 100644 index 0000000..73b234a --- /dev/null +++ b/src/components/planes/wizard/PasoBasicosForm/TemplateSelectorCard.tsx @@ -0,0 +1,189 @@ +import { useMemo, useState } from 'react' + +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from '@/components/ui/card' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { cn } from '@/lib/utils' + +type TemplateData = { + id: string + name: string + versions: Array +} + +// Default data (kept for backward compatibility if caller doesn't pass templates) +const DEFAULT_TEMPLATES_DATA: Array = [ + { + id: 'sep-2025', + name: 'Licenciatura RVOE SEP', + versions: ['v2025.2 (Vigente)', 'v2025.1', 'v2024.Final'], + }, + { + id: 'interno-mix', + name: 'Estándar Institucional Mixto', + versions: ['v2.0', 'v1.5', 'v1.0-beta'], + }, + { + id: 'conacyt', + name: 'Formato Posgrado CONAHCYT', + versions: ['v3.0 (2025)', 'v2.8'], + }, +] + +interface Props { + cardTitle?: string + cardDescription?: string + templatesData?: Array + // Controlled selection (optional). If not provided, component manages its own state + selectedTemplateId?: string + selectedVersion?: string + onChange?: (sel: { templateId: string; version: string }) => void +} + +export function TemplateSelectorCard({ + cardTitle = 'Configuración del Documento', + cardDescription = 'Selecciona la base para tu nuevo plan.', + templatesData = DEFAULT_TEMPLATES_DATA, + selectedTemplateId, + selectedVersion, + onChange, +}: Props) { + const [internalTemplate, setInternalTemplate] = useState('') + const [internalVersion, setInternalVersion] = useState('') + + const selectedTemplate = selectedTemplateId ?? internalTemplate + const version = selectedVersion ?? internalVersion + + // Buscamos las versiones de la plantilla seleccionada + const currentTemplateData = useMemo( + () => templatesData.find((t) => t.id === selectedTemplate), + [templatesData, selectedTemplate], + ) + const availableVersions = currentTemplateData?.versions || [] + + const handleTemplateChange = (value: string) => { + const template = templatesData.find((t) => t.id === value) + const firstVersion = template?.versions?.[0] ?? '' + if (onChange) { + onChange({ templateId: value, version: firstVersion }) + } else { + setInternalTemplate(value) + setInternalVersion(firstVersion) + } + } + + const handleVersionChange = (value: string) => { + if (onChange) { + onChange({ templateId: selectedTemplate, version: value }) + } else { + setInternalVersion(value) + } + } + + return ( + + + {cardTitle} + {cardDescription} + + + + {/* SELECT 1: PRIMARIO (Llamativo) */} +
+ + +
+ + {/* SELECT 2: SECUNDARIO (Sutil) */} +
+
+ +
+ + +
+
+
+ ) +} diff --git a/src/components/planes/wizard/PasoDetallesPanel/FileDropZone.tsx b/src/components/planes/wizard/PasoDetallesPanel/FileDropZone.tsx new file mode 100644 index 0000000..65b9d83 --- /dev/null +++ b/src/components/planes/wizard/PasoDetallesPanel/FileDropZone.tsx @@ -0,0 +1,205 @@ +import { Upload, File, X, FileText } from 'lucide-react' +import { useState, useCallback, useEffect, useRef } from 'react' + +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +interface UploadedFile { + id: string + name: string + size: string + type: string +} + +interface FileDropzoneProps { + onFilesChange?: (files: Array) => void + acceptedTypes?: string + maxFiles?: number + title?: string + description?: string +} + +export function FileDropzone({ + onFilesChange, + acceptedTypes = '.doc,.docx,.pdf', + maxFiles = 5, + title = 'Arrastra archivos aquí', + description = 'o haz clic para seleccionar', +}: FileDropzoneProps) { + const [isDragging, setIsDragging] = useState(false) + const [files, setFiles] = useState>([]) + const onFilesChangeRef = useRef(onFilesChange) + + const addFiles = useCallback( + (newFiles: Array) => { + const toUpload: Array = newFiles.map((file) => ({ + id: + typeof crypto !== 'undefined' && 'randomUUID' in crypto + ? (crypto as any).randomUUID() + : `file-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + name: file.name, + size: formatFileSize(file.size), + type: file.name.split('.').pop() || 'file', + })) + setFiles((prev) => { + const room = Math.max(0, maxFiles - prev.length) + const next = [...prev, ...toUpload.slice(0, room)].slice(0, maxFiles) + return next + }) + }, + [maxFiles], + ) + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault() + setIsDragging(true) + }, []) + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault() + setIsDragging(false) + }, []) + + const handleDrop = useCallback( + (e: React.DragEvent) => { + e.preventDefault() + setIsDragging(false) + const droppedFiles = Array.from(e.dataTransfer.files) + addFiles(droppedFiles) + }, + [addFiles], + ) + + const handleFileInput = useCallback( + (e: React.ChangeEvent) => { + if (e.target.files) { + const selectedFiles = Array.from(e.target.files) + addFiles(selectedFiles) + } + }, + [addFiles], + ) + + const removeFile = useCallback((fileId: string) => { + setFiles((prev) => { + const next = prev.filter((f) => f.id !== fileId) + return next + }) + }, []) + + // Keep latest callback in a ref to avoid retriggering effect on identity change + useEffect(() => { + onFilesChangeRef.current = onFilesChange + }, [onFilesChange]) + + // Only emit when files actually change to avoid parent update loops + useEffect(() => { + if (onFilesChangeRef.current) onFilesChangeRef.current(files) + }, [files]) + + const formatFileSize = (bytes: number): string => { + if (bytes < 1024) return bytes + ' B' + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB' + return (bytes / (1024 * 1024)).toFixed(1) + ' MB' + } + + const getFileIcon = (type: string) => { + switch (type.toLowerCase()) { + case 'pdf': + return + case 'doc': + case 'docx': + return + default: + return + } + } + + return ( +
+
+ + +
+ + {/* Uploaded files list */} + {files.length > 0 && ( +
+ {files.map((file) => ( +
+ {getFileIcon(file.type)} +
+

+ {file.name} +

+

{file.size}

+
+ +
+ ))} +
+ )} + + {files.length >= maxFiles && ( +

+ Máximo de {maxFiles} archivos alcanzado +

+ )} +
+ ) +} diff --git a/src/components/planes/wizard/PasoDetallesPanel.tsx b/src/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel.tsx similarity index 71% rename from src/components/planes/wizard/PasoDetallesPanel.tsx rename to src/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel.tsx index 5ef68e0..ba3347e 100644 --- a/src/components/planes/wizard/PasoDetallesPanel.tsx +++ b/src/components/planes/wizard/PasoDetallesPanel/PasoDetallesPanel.tsx @@ -1,4 +1,7 @@ -import type { NewPlanWizardState } from '@/features/planes/new/types' +import { FileDropzone } from './FileDropZone' +import ReferenciasParaIA from './ReferenciasParaIA' + +import type { NewPlanWizardState } from '@/features/planes/nuevo/types' import { Button } from '@/components/ui/button' import { @@ -14,7 +17,7 @@ import { CARRERAS, FACULTADES, PLANES_EXISTENTES, -} from '@/features/planes/new/catalogs' +} from '@/features/planes/nuevo/catalogs' export function PasoDetallesPanel({ wizard, @@ -42,8 +45,8 @@ export function PasoDetallesPanel({ if (wizard.modoCreacion === 'IA') { return ( -
-
+
+