Changeset 4fe4582
- Timestamp:
- 02/16/26 17:38:16 (7 months ago)
- Branches:
- master
- Children:
- 8496f3c
- Parents:
- d7deae5
- Files:
-
- 5 added
- 11 edited
-
package-lock.json (modified) (22 diffs)
-
package.json (modified) (1 diff)
-
public/fonts/Roboto-Bold.ttf (added)
-
public/fonts/Roboto-Regular.ttf (added)
-
src/app/globals.css (modified) (1 diff)
-
src/app/layout.tsx (modified) (2 diffs)
-
src/app/page.tsx (modified) (2 diffs)
-
src/app/students/applications/page.tsx (modified) (2 diffs)
-
src/app/students/documents/page.tsx (modified) (7 diffs)
-
src/app/students/semesters/page.tsx (modified) (5 diffs)
-
src/components/contact-bubble.tsx (added)
-
src/components/exams.tsx (modified) (1 diff)
-
src/lib/pdf-fonts.ts (added)
-
src/lib/pdf-generators.ts (added)
-
src/locales/en/translation.json (modified) (7 diffs)
-
src/locales/mk/translation.json (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
-
package-lock.json
rd7deae5 r4fe4582 17 17 "clsx": "^2.1.1", 18 18 "i18next": "^25.8.0", 19 "jspdf": "^4.1.0", 20 "jspdf-autotable": "^5.0.7", 19 21 "lucide-react": "^0.544.0", 20 22 "next": "^16.1.6", … … 1969 1971 } 1970 1972 }, 1973 "node_modules/@types/pako": { 1974 "version": "2.0.4", 1975 "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", 1976 "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", 1977 "license": "MIT" 1978 }, 1979 "node_modules/@types/raf": { 1980 "version": "3.4.3", 1981 "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", 1982 "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", 1983 "license": "MIT", 1984 "optional": true 1985 }, 1971 1986 "node_modules/@types/react": { 1972 1987 "version": "19.2.10", … … 1989 2004 "@types/react": "^19.2.0" 1990 2005 } 2006 }, 2007 "node_modules/@types/trusted-types": { 2008 "version": "2.0.7", 2009 "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", 2010 "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", 2011 "license": "MIT", 2012 "optional": true 1991 2013 }, 1992 2014 "node_modules/@typescript-eslint/eslint-plugin": { … … 2846 2868 "dev": true, 2847 2869 "license": "MIT" 2870 }, 2871 "node_modules/base64-arraybuffer": { 2872 "version": "1.0.2", 2873 "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", 2874 "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", 2875 "license": "MIT", 2876 "optional": true, 2877 "engines": { 2878 "node": ">= 0.6.0" 2879 } 2848 2880 }, 2849 2881 "node_modules/baseline-browser-mapping": { … … 2995 3027 "license": "CC-BY-4.0" 2996 3028 }, 3029 "node_modules/canvg": { 3030 "version": "3.0.11", 3031 "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", 3032 "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", 3033 "license": "MIT", 3034 "optional": true, 3035 "dependencies": { 3036 "@babel/runtime": "^7.12.5", 3037 "@types/raf": "^3.4.0", 3038 "core-js": "^3.8.3", 3039 "raf": "^3.4.1", 3040 "regenerator-runtime": "^0.13.7", 3041 "rgbcolor": "^1.0.1", 3042 "stackblur-canvas": "^2.0.0", 3043 "svg-pathdata": "^6.0.3" 3044 }, 3045 "engines": { 3046 "node": ">=10.0.0" 3047 } 3048 }, 2997 3049 "node_modules/chalk": { 2998 3050 "version": "4.1.2", … … 3066 3118 "license": "MIT" 3067 3119 }, 3120 "node_modules/core-js": { 3121 "version": "3.48.0", 3122 "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", 3123 "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", 3124 "hasInstallScript": true, 3125 "license": "MIT", 3126 "optional": true, 3127 "funding": { 3128 "type": "opencollective", 3129 "url": "https://opencollective.com/core-js" 3130 } 3131 }, 3068 3132 "node_modules/cross-spawn": { 3069 3133 "version": "7.0.6", … … 3079 3143 "engines": { 3080 3144 "node": ">= 8" 3145 } 3146 }, 3147 "node_modules/css-line-break": { 3148 "version": "2.1.0", 3149 "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", 3150 "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", 3151 "license": "MIT", 3152 "optional": true, 3153 "dependencies": { 3154 "utrie": "^1.0.2" 3081 3155 } 3082 3156 }, … … 3236 3310 "engines": { 3237 3311 "node": ">=0.10.0" 3312 } 3313 }, 3314 "node_modules/dompurify": { 3315 "version": "3.3.1", 3316 "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", 3317 "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", 3318 "license": "(MPL-2.0 OR Apache-2.0)", 3319 "optional": true, 3320 "optionalDependencies": { 3321 "@types/trusted-types": "^2.0.7" 3238 3322 } 3239 3323 }, … … 3959 4043 "license": "MIT" 3960 4044 }, 4045 "node_modules/fast-png": { 4046 "version": "6.4.0", 4047 "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", 4048 "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", 4049 "license": "MIT", 4050 "dependencies": { 4051 "@types/pako": "^2.0.3", 4052 "iobuffer": "^5.3.2", 4053 "pako": "^2.1.0" 4054 } 4055 }, 3961 4056 "node_modules/fastq": { 3962 4057 "version": "1.20.1", … … 3968 4063 "reusify": "^1.0.4" 3969 4064 } 4065 }, 4066 "node_modules/fflate": { 4067 "version": "0.8.2", 4068 "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", 4069 "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", 4070 "license": "MIT" 3970 4071 }, 3971 4072 "node_modules/file-entry-cache": { … … 4348 4449 "dependencies": { 4349 4450 "void-elements": "3.1.0" 4451 } 4452 }, 4453 "node_modules/html2canvas": { 4454 "version": "1.4.1", 4455 "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", 4456 "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", 4457 "license": "MIT", 4458 "optional": true, 4459 "dependencies": { 4460 "css-line-break": "^2.1.0", 4461 "text-segmentation": "^1.0.3" 4462 }, 4463 "engines": { 4464 "node": ">=8.0.0" 4350 4465 } 4351 4466 }, … … 4461 4576 } 4462 4577 }, 4578 "node_modules/iobuffer": { 4579 "version": "5.4.0", 4580 "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", 4581 "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", 4582 "license": "MIT" 4583 }, 4463 4584 "node_modules/is-array-buffer": { 4464 4585 "version": "3.0.5", … … 4955 5076 "bin": { 4956 5077 "json5": "lib/cli.js" 5078 } 5079 }, 5080 "node_modules/jspdf": { 5081 "version": "4.1.0", 5082 "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.1.0.tgz", 5083 "integrity": "sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==", 5084 "license": "MIT", 5085 "peer": true, 5086 "dependencies": { 5087 "@babel/runtime": "^7.28.4", 5088 "fast-png": "^6.2.0", 5089 "fflate": "^0.8.1" 5090 }, 5091 "optionalDependencies": { 5092 "canvg": "^3.0.11", 5093 "core-js": "^3.6.0", 5094 "dompurify": "^3.3.1", 5095 "html2canvas": "^1.0.0-rc.5" 5096 } 5097 }, 5098 "node_modules/jspdf-autotable": { 5099 "version": "5.0.7", 5100 "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.7.tgz", 5101 "integrity": "sha512-2wr7H6liNDBYNwt25hMQwXkEWFOEopgKIvR1Eukuw6Zmprm/ZcnmLTQEjW7Xx3FCbD3v7pflLcnMAv/h1jFDQw==", 5102 "license": "MIT", 5103 "peerDependencies": { 5104 "jspdf": "^2 || ^3 || ^4" 4957 5105 } 4958 5106 }, … … 5818 5966 } 5819 5967 }, 5968 "node_modules/pako": { 5969 "version": "2.1.0", 5970 "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", 5971 "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", 5972 "license": "(MIT AND Zlib)" 5973 }, 5820 5974 "node_modules/parent-module": { 5821 5975 "version": "1.0.1", … … 5857 6011 "dev": true, 5858 6012 "license": "MIT" 6013 }, 6014 "node_modules/performance-now": { 6015 "version": "2.1.0", 6016 "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 6017 "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", 6018 "license": "MIT", 6019 "optional": true 5859 6020 }, 5860 6021 "node_modules/picocolors": { … … 5983 6144 "license": "MIT" 5984 6145 }, 6146 "node_modules/raf": { 6147 "version": "3.4.1", 6148 "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", 6149 "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", 6150 "license": "MIT", 6151 "optional": true, 6152 "dependencies": { 6153 "performance-now": "^2.1.0" 6154 } 6155 }, 5985 6156 "node_modules/react": { 5986 6157 "version": "19.2.4", … … 6063 6234 } 6064 6235 }, 6236 "node_modules/regenerator-runtime": { 6237 "version": "0.13.11", 6238 "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", 6239 "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", 6240 "license": "MIT", 6241 "optional": true 6242 }, 6065 6243 "node_modules/regexp.prototype.flags": { 6066 6244 "version": "1.5.4", … … 6134 6312 "iojs": ">=1.0.0", 6135 6313 "node": ">=0.10.0" 6314 } 6315 }, 6316 "node_modules/rgbcolor": { 6317 "version": "1.0.1", 6318 "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", 6319 "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", 6320 "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", 6321 "optional": true, 6322 "engines": { 6323 "node": ">= 0.8.15" 6136 6324 } 6137 6325 }, … … 6443 6631 "license": "MIT" 6444 6632 }, 6633 "node_modules/stackblur-canvas": { 6634 "version": "2.7.0", 6635 "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", 6636 "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", 6637 "license": "MIT", 6638 "optional": true, 6639 "engines": { 6640 "node": ">=0.1.14" 6641 } 6642 }, 6445 6643 "node_modules/stop-iteration-iterator": { 6446 6644 "version": "1.1.0", … … 6642 6840 } 6643 6841 }, 6842 "node_modules/svg-pathdata": { 6843 "version": "6.0.3", 6844 "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", 6845 "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", 6846 "license": "MIT", 6847 "optional": true, 6848 "engines": { 6849 "node": ">=12.0.0" 6850 } 6851 }, 6644 6852 "node_modules/tailwind-merge": { 6645 6853 "version": "3.4.0", … … 6671 6879 "type": "opencollective", 6672 6880 "url": "https://opencollective.com/webpack" 6881 } 6882 }, 6883 "node_modules/text-segmentation": { 6884 "version": "1.0.3", 6885 "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", 6886 "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", 6887 "license": "MIT", 6888 "optional": true, 6889 "dependencies": { 6890 "utrie": "^1.0.2" 6673 6891 } 6674 6892 }, … … 7015 7233 } 7016 7234 }, 7235 "node_modules/utrie": { 7236 "version": "1.0.2", 7237 "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", 7238 "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", 7239 "license": "MIT", 7240 "optional": true, 7241 "dependencies": { 7242 "base64-arraybuffer": "^1.0.2" 7243 } 7244 }, 7017 7245 "node_modules/void-elements": { 7018 7246 "version": "3.1.0", -
package.json
rd7deae5 r4fe4582 18 18 "clsx": "^2.1.1", 19 19 "i18next": "^25.8.0", 20 "jspdf": "^4.1.0", 21 "jspdf-autotable": "^5.0.7", 20 22 "lucide-react": "^0.544.0", 21 23 "next": "^16.1.6", -
src/app/globals.css
rd7deae5 r4fe4582 46 46 :root { 47 47 --radius: 0.625rem; 48 --background: oklch(1 0 0);48 --background: #F4FAFF; 49 49 --foreground: oklch(0.145 0 0); 50 50 --card: oklch(1 0 0); -
src/app/layout.tsx
rd7deae5 r4fe4582 8 8 import { ThemeProvider } from '../components/theme-provider'; 9 9 import { ThemeToggle } from '../components/theme-toggle'; 10 import ContactBubble from '../components/contact-bubble'; 10 11 11 12 config.autoAddCss = false; … … 35 36 </div> 36 37 <div className="mx-auto max-w-6xl px-4">{children}</div> 38 <ContactBubble /> 37 39 </I18nextProvider> 38 40 </ThemeProvider> -
src/app/page.tsx
rd7deae5 r4fe4582 58 58 <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5"> 59 59 <div className="text-sm font-bold text-card-foreground mb-2">{t('login_student_title')}</div> 60 <div className="text-xs text-card-foreground whitespace-pre-wrap font-mono">60 <div className="text-xs text-card-foreground whitespace-pre-wrap"> 61 61 {t('login_student_example')} 62 62 </div> 63 <div className="mt-3 text-xs text- muted-foreground">64 {t('login_ note_slow')}63 <div className="mt-3 text-xs text-blue-600 font-medium"> 64 {t('login_demo_note')} 65 65 </div> 66 66 </div> … … 68 68 <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5"> 69 69 <div className="text-sm font-bold text-card-foreground mb-2">{t('login_professor_title')}</div> 70 <div className="text-xs text-card-foreground whitespace-pre-wrap font-mono">70 <div className="text-xs text-card-foreground whitespace-pre-wrap"> 71 71 {t('login_professor_example')} 72 </div> 73 <div className="mt-3 text-xs text-blue-600 font-medium"> 74 {t('login_demo_note')} 72 75 </div> 73 76 </div> -
src/app/students/applications/page.tsx
rd7deae5 r4fe4582 30 30 const CompletedBadge = ({ completed }: { completed: string }) => { 31 31 const { t } = useTranslation(); 32 if (completed === t('yes', 'Да')) {32 if (completed === 'Да') { 33 33 return ( 34 34 <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full" title={t('completed')}> … … 230 230 <h3 className="text-lg font-bold text-card-foreground">{t('completed', 'Завршени')}</h3> 231 231 <p className="text-2xl font-bold text-green-600"> 232 {applications.filter(app => app.completed === t('yes', 'Да')).length}232 {applications.filter(app => app.completed === 'Да').length} 233 233 </p> 234 234 </div> -
src/app/students/documents/page.tsx
rd7deae5 r4fe4582 6 6 faChevronDown, 7 7 faCheckCircle, 8 faClock, 8 9 faMoneyBillWave, 9 10 faFileText, … … 11 12 faChevronRight, 12 13 faAngleDoubleLeft, 13 faAngleDoubleRight 14 faAngleDoubleRight, 15 faSpinner 14 16 } from '@fortawesome/free-solid-svg-icons'; 15 17 import { useState } from 'react'; 16 18 import documentsData from '@/data/documents.json'; 17 19 import { useTranslation } from 'react-i18next'; 20 import { getAccessToken } from '@/lib/auth'; 21 import { downloadDocumentPDF } from '@/lib/pdf-generators'; 18 22 19 23 interface TableCellProps { … … 30 34 const StatusBadge = ({ status }: { status: string }) => { 31 35 const { t } = useTranslation(); 32 if (status === t('approved', 'Одобрено')) {36 if (status === 'approved' || status === t('approved', 'Одобрено')) { 33 37 return ( 34 38 <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full"> 35 39 <FontAwesomeIcon icon={faCheckCircle} className="w-5 h-5" /> 40 </span> 41 ); 42 } 43 if (status === 'pending') { 44 return ( 45 <span className="inline-flex items-center gap-1 text-xs font-medium text-yellow-700 bg-yellow-100 px-2 py-1 rounded-full"> 46 <FontAwesomeIcon icon={faClock} className="w-3 h-3" /> 47 {t('pending', 'Во обработка')} 36 48 </span> 37 49 ); … … 52 64 }; 53 65 66 interface DocumentRecord { 67 id: number; 68 archive: string; 69 date: string; 70 request: string; 71 price: number; 72 paid: string; 73 document: string; 74 payOnline: boolean; 75 status: string; 76 comment: string; 77 } 78 54 79 export default function DocumentsPage() { 55 80 const [selectedDocumentType, setSelectedDocumentType] = useState("select_document"); … … 58 83 const [currentPage, setCurrentPage] = useState(1); 59 84 const [recordsPerPage, setRecordsPerPage] = useState(15); 85 const [downloadingId, setDownloadingId] = useState<number | null>(null); 86 const [documents, setDocuments] = useState<DocumentRecord[]>(documentsData.documents as DocumentRecord[]); 87 const [isSubmitting, setIsSubmitting] = useState(false); 60 88 const { t } = useTranslation(); 61 89 90 const handleDownload = async (docId: number, request: string, archive: string, date: string) => { 91 const token = getAccessToken(); 92 if (!token) { 93 alert(t('not_authenticated', 'Не сте најавени. Ве молиме најавете се повторно.')); 94 return; 95 } 96 setDownloadingId(docId); 97 try { 98 await downloadDocumentPDF(request, archive, date, token); 99 } catch (err) { 100 console.error('PDF generation error:', err); 101 alert(t('pdf_error', 'Грешка при генерирање на документот. Обидете се повторно.')); 102 } finally { 103 setDownloadingId(null); 104 } 105 }; 106 107 const handleSubmit = () => { 108 if (selectedDocumentType === "select_document") return; 109 110 const docType = documentsData.documentTypes.find(d => d.id === selectedDocumentType); 111 if (!docType) return; 112 113 setIsSubmitting(true); 114 115 // Generate archive number (random 5-digit) 116 const archiveNumber = String(90000 + Math.floor(Math.random() * 10000)); 117 118 // Current date in DD.MM.YYYY format 119 const now = new Date(); 120 const dateStr = `${String(now.getDate()).padStart(2, '0')}.${String(now.getMonth() + 1).padStart(2, '0')}.${now.getFullYear()}`; 121 122 const newDoc: DocumentRecord = { 123 id: documents.length > 0 ? Math.max(...documents.map(d => d.id)) + 1 : 1, 124 archive: archiveNumber, 125 date: dateStr, 126 request: docType.name, 127 price: docType.price, 128 paid: "Не", 129 document: "Преземи", 130 payOnline: docType.price > 0, 131 status: "pending", 132 comment: comment, 133 }; 134 135 setDocuments(prev => [newDoc, ...prev]); 136 setSelectedDocumentType("select_document"); 137 setComment(""); 138 setIsSubmitting(false); 139 alert(t('document_submitted', 'Барањето за документ е успешно поднесено!')); 140 }; 141 62 142 const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType); 63 const { documents,paymentInfo } = documentsData;143 const { paymentInfo } = documentsData; 64 144 65 145 const totalPages = Math.ceil(documents.length / recordsPerPage); … … 153 233 <div className="flex justify-end mt-6"> 154 234 <button 155 className="bg-primary hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200 flex items-center gap-2" 156 disabled={selectedDocumentType === "select_document"} 235 onClick={handleSubmit} 236 className="bg-primary hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition-colors duration-200 flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed" 237 disabled={selectedDocumentType === "select_document" || isSubmitting} 157 238 > 158 <FontAwesomeIcon icon={ faFileText} className="w-4 h-4"/>159 { t('submit', 'Поднеси')}239 <FontAwesomeIcon icon={isSubmitting ? faSpinner : faFileText} className={`w-4 h-4 ${isSubmitting ? 'animate-spin' : ''}`} /> 240 {isSubmitting ? t('submitting', 'Се поднесува...') : t('submit', 'Поднеси')} 160 241 </button> 161 242 </div> … … 197 278 </TableCell> 198 279 <TableCell className="text-center"> 199 <span className= "text-sm font-medium text-green-600">280 <span className={`text-sm font-medium ${document.paid === 'ДА' ? 'text-green-600' : 'text-red-500'}`}> 200 281 {document.paid} 201 282 </span> 202 283 </TableCell> 203 284 <TableCell className="text-center"> 204 <button className="text-primary hover:text-blue-700 font-medium text-sm underline"> 205 {document.document} 285 <button 286 onClick={() => handleDownload(document.id, document.request, document.archive, document.date)} 287 disabled={downloadingId === document.id} 288 className="text-primary hover:text-blue-700 font-medium text-sm underline disabled:opacity-50 disabled:cursor-wait inline-flex items-center gap-1" 289 > 290 {downloadingId === document.id ? ( 291 <> 292 <FontAwesomeIcon icon={faSpinner} className="w-3 h-3 animate-spin" /> 293 {t('generating', 'Генерира...')} 294 </> 295 ) : ( 296 t('download', document.document) 297 )} 206 298 </button> 207 299 </TableCell> -
src/app/students/semesters/page.tsx
rd7deae5 r4fe4582 57 57 const { t } = useTranslation(); 58 58 const baseClasses = "px-2 py-1 rounded-full text-xs font-medium"; 59 if (status === t('valid')) {59 if (status === 'Валидно') { 60 60 return ( 61 61 <span className={`${baseClasses} bg-green-100 text-green-800 flex items-center gap-1`}> 62 62 <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" /> 63 {t(' valid')}63 {t('Валидно', 'Валидно')} 64 64 </span> 65 65 ); … … 67 67 return ( 68 68 <span className={`${baseClasses} bg-accent text-gray-800`}> 69 {t(status ) || status}69 {t(status, status)} 70 70 </span> 71 71 ); … … 74 74 const YesNoBadge = ({ value }: { value: string }) => { 75 75 const { t } = useTranslation(); 76 if (value === t('yes')) {76 if (value === 'Да') { 77 77 return ( 78 78 <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full"> … … 80 80 </span> 81 81 ); 82 } else if (value === t('no')) {82 } else if (value === 'Не') { 83 83 return ( 84 84 <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full"> … … 313 313 <span className="text-green-600 font-medium flex items-center gap-1"> 314 314 <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" /> 315 { semester.completed}315 {t(semester.completed, semester.completed)} 316 316 </span> 317 317 ) : ( 318 318 <span className="text-red-600 font-medium flex items-center gap-1"> 319 319 <FontAwesomeIcon icon={faTimesCircle} className="w-3 h-3" /> 320 { semester.completed}320 {t('Не', 'Не')} 321 321 </span> 322 322 )} -
src/components/exams.tsx
rd7deae5 r4fe4582 290 290 <tr key={exam.id} className="hover:bg-accent/50 transition-colors duration-150"> 291 291 <td className="py-4 px-4 text-sm text-card-foreground">{exam.id}</td> 292 <td className="py-4 px-4 text-sm text-card-foreground font-medium">{ exam.subject}</td>293 <td className="py-4 px-4 text-sm text-muted-foreground">{ exam.semester}</td>292 <td className="py-4 px-4 text-sm text-card-foreground font-medium">{t(exam.subject, exam.subject)}</td> 293 <td className="py-4 px-4 text-sm text-muted-foreground">{t(exam.semester, exam.semester)}</td> 294 294 <td className="py-4 px-4 text-sm text-muted-foreground">{exam.credits}</td> 295 295 <td className="py-4 px-4 text-sm text-muted-foreground">{exam.date}</td> -
src/locales/en/translation.json
rd7deae5 r4fe4582 7 7 "student_id_placeholder": "e.g. 20001", 8 8 "no_students_found": "No students found.", 9 "Забелешка": "Note",10 "Завршени": "Completed",11 "Останати": "Remaining",12 9 "taxes": "Taxes", 13 10 "signatures": "Signatures", … … 24 21 "add_comment": "Add comment", 25 22 "submit": "Submit", 23 "submitting": "Submitting...", 24 "document_submitted": "Document request submitted successfully!", 25 "document_submit_error": "Error submitting document request.", 26 "not_authenticated": "You are not logged in. Please log in again.", 27 "pdf_error": "Error generating document. Please try again.", 28 "generating": "Generating...", 29 "pending": "Pending", 26 30 "my_documents": "My Documents", 27 31 "archive": "Archive", … … 42 46 "mkd": "MKD", 43 47 "free": "0.00", 44 "university": "University 'St. Cyril and Methodius' - Skopje", 48 "download": "Download", 49 "university": "University ‘St. Cyril and Methodius’ - Skopje", 45 50 "faculty": "Faculty of Computer Science and Engineering", 46 51 "logout": "Logout", … … 140 145 "doc1": "Document 1", 141 146 "verified": "Verified", 142 147 "debt": "Debt", 148 "debt_from_documents": "Debt from documents", 149 "ticket_number": "Ticket number", 150 "select_semester": "Select semester", 151 "enrolled_subjects": "Enrolled subjects", 152 "financial_info": "Financial information", 153 "material_costs": "Material costs", 154 "material_costs_info": "Material costs include all required fees for materials and resources for the subjects.", 155 "electronic_registration": "Electronic registration", 156 "eukim": "eUKIM", 157 "bank_provision": "Bank provision", 158 "which_time": "Which time", 159 "signature": "Signature", 160 "group": "Group", 161 "professor": "Professor", 162 "subjects_list": "Subjects list", 163 "subjects_overview": "Overview of your enrolled subjects and their status.", 164 "mandatory_short": "Mand.", 165 "elective_short": "Elect.", 166 "enrolled_by_student": "Enrolled by student", 167 "due": "Debt", 168 "failed_to_load_subjects": "Failed to load subjects.", 169 "hours": "Hours", 170 "administrative_regulation": "Request for administrative regulation of retroactive semester (from 2024) (1000)", 171 "exam_application_satisfaction": "Request for late exam registration (from 2024) (1000)", 143 172 "semester_enrollment_after_deadline": "Request for semester enrollment after deadline (from 2024) (1500)", 144 173 "certificate_issuance_various": "Request for issuance of certificates for various reasons (from 2024) (1500)", … … 158 187 "exam_recognition": "Recognition of passed exams (0)", 159 188 "student_card_old": "Student card (Old) (0)", 189 "Уверение за положени испити ФИНКИ": "Certificate of passed exams FINKI", 190 "УППИ образец": "UPPI form", 191 "Уверение за редовен студент - ФИНКИ": "Regular student certificate - FINKI", 160 192 "2025_summer_session2": "2025 (Summer) Second exam session", 161 193 "2025_summer_session1": "2025 (Summer) First exam session", … … 168 200 "applications_important_note_text": "The serial number of the electronic application must be entered on the paper application that the student will submit physically.", 169 201 "login_student_title": "Login for Student", 170 "login_student_example": "\"email\":\"boris@example.com\",\n\"password\":\"your-password-here\",", 171 "login_note_slow": "May take up to 30 seconds after long inactivity.", 202 "login_student_example": "Email: boris@example.com\nPassword: your-password-here", 172 203 "login_professor_title": "Login for Professor", 173 "login_professor_example": " \"email\":\"andonov@gmail.com\",\n\"password\":\"123lol456\",",204 "login_professor_example": "Email: andonov@gmail.com\nPassword: 123lol456", 174 205 "login_welcome": "Welcome", 175 206 "login_subtitle": "Sign in to your IKnow account", … … 184 215 "login_no_account": "Don't have an account?", 185 216 "login_contact_admin": "Contact administration", 186 "login_footer": "Student Information System" 187 , 188 "debt": "Debt", 189 "status": "Status" 190 , 191 "debt_from_documents": "Debt from documents", 192 "status": "Status", 193 "subjects_overview": "Overview of your enrolled subjects and their status." 194 , 195 "electronic_registration": "Electronic registration", 196 "eukim": "eUKIM", 197 "bank_provision": "Bank provision" 198 , 199 "failed_to_load_subjects": "Failed to load subjects." 200 , 201 "debt_from_documents": "Debt from documents", 202 "ticket_number": "Ticket number", 203 "serial_number": "Serial number", 204 "select_semester": "Select semester", 205 "enrolled_subjects": "Enrolled subjects", 206 "financial_info": "Financial information", 207 "material_costs": "Material costs", 208 "material_costs_info": "Material costs include all required fees for materials and resources for the subjects.", 209 "which_time": "Which time", 210 "signature": "Signature", 211 "group": "Group", 212 "professor": "Professor", 213 "subjects_list": "Subjects list", 214 "mandatory_short": "Mand.", 215 "elective_short": "Elect.", 216 "enrolled_by_student": "Enrolled by student", 217 "due": "Debt" 217 "login_footer": "Student Information System", 218 "contact_us": "Contact Us", 219 "close": "Close", 220 "contact_name": "Full Name", 221 "contact_name_placeholder": "Your name", 222 "contact_email": "Email", 223 "contact_email_placeholder": "example@email.com", 224 "contact_subject": "Subject", 225 "contact_subject_placeholder": "Message topic", 226 "contact_message": "Message", 227 "contact_message_placeholder": "Write your message...", 228 "contact_send": "Send", 229 "contact_sending": "Sending...", 230 "contact_sent_title": "Message sent!", 231 "contact_sent_desc": "We will get back to you soon.", 232 "Компјутерски мрежи и безбедност": "Computer Networks and Security", 233 "Шаблони за дизајн на кориснички интерфејси": "User Interface Design Patterns", 234 "Деловна пракса": "Business Practice", 235 "Маркетинг": "Marketing", 236 "Оперативни системи": "Operating Systems", 237 "Софтверско инженерство": "Software Engineering", 238 "Вештачка интелигенција": "Artificial Intelligence", 239 "Сашо Граматиков": "Sasho Gramatikov", 240 "Сузана Лошковска": "Suzana Loshkovska", 241 "Александар Стојменски": "Aleksandar Stojmenski", 242 "Билјана Стојкоска": "Biljana Stojkoska", 243 "Невена Ацковска": "Nevena Ackovska", 244 "Ѓорѓи Маџаров": "Gjorgji Madjarov", 245 "Соња Гиевска": "Sonja Gievska", 246 "Летна": "Summer", 247 "Зимска": "Winter", 248 "Прва испитна сесија": "First exam session", 249 "Втора испитна сесија": "Second exam session", 250 "Валидно": "Valid", 251 "Да": "Yes", 252 "Не": "No", 253 "exams_overview": "Overview of your passed exams and grades.", 254 "failed_to_load_semesters": "Failed to load semesters.", 255 "add_grade": "Add Grade", 256 "edit_grade": "Edit Grade", 257 "remove_grade": "Remove Grade", 258 "current": "Current", 259 "login_demo_note": "Use these credentials to explore the system." 218 260 } -
src/locales/mk/translation.json
rd7deae5 r4fe4582 14 14 "edit_grade": "Измени оценка", 15 15 "remove_grade": "Избриши оценка", 16 "taxes": "Такси", 17 "signatures": "Потписи", 18 "valid": "Валидно", 19 "completed_semester": "Завршени семестри", 20 "verified_semesters": "Потврдени семестри", 21 "total_semesters": "Вкупно семестри", 22 "completed_semesters": "Завршени семестри", 16 23 "documents_overview": "Преглед на вашите документи и нивниот статус.", 17 24 "new_document_request": "Ново барање за документ", 18 "select_document": "Избер идокумент",25 "select_document": "Изберете документ", 19 26 "price": "Цена", 20 27 "comment": "Коментар", 21 "subjects_overview": "Преглед на вашите запишани предмети и нивниот статус.",22 "enrolled_by_student": "Запишан од студентот",23 "financial_info": "Финансиски информации",24 "electronic_registration": "Електронска регистрација",25 "eukim": "еУКИМ",26 "bank_provision": "Банкарска провизија",27 "debt_from_documents": "Долг од документи",28 "debt": "Долг",29 "select_semester": "Избери семестар",30 "enrolled_subjects": "Запишани предмети",31 "subjects_list": "Листа на предмети",32 "hours": "Часови",33 "which_time": "Кој пат",34 "signature": "Потпис",35 "group": "Група",36 "professor": "Професор",37 "ticket_number": "Број на тикет",38 "due": "Долг",39 "material_costs": "Материјални трошоци",40 "material_costs_info": "Материјалните трошоци ги вклучуваат сите задолжителни надоместоци за материјали и ресурси за предметите.",41 "Download": "Преземи",42 "ДА": "ДА",43 "Не": "Не",44 "approved": "Одобрено",45 "Онлајн плаќањата имаат банкарска провизија за безготовинско плаќање од 1,14% од оригиналната сума (минималната провизија 3,00, максималната 300,00 мкд)": "Онлајн плаќањата имаат банкарска провизија за безготовинско плаќање од 1,14% од оригиналната сума (минималната провизија 3,00, максималната 300,00 мкд)",46 28 "add_comment": "Додај коментар", 47 29 "submit": "Поднеси", 30 "submitting": "Се поднесува...", 31 "document_submitted": "Барањето за документ е успешно поднесено!", 32 "document_submit_error": "Грешка при поднесување на барањето.", 33 "not_authenticated": "Не сте најавени. Ве молиме најавете се повторно.", 34 "pdf_error": "Грешка при генерирање на документот. Обидете се повторно.", 35 "generating": "Генерира...", 36 "pending": "Во обработка", 48 37 "my_documents": "Мои документи", 49 38 "archive": "Архива", 50 39 "request": "Барање", 51 "paid": "Платено",52 "ДА": "ДА",53 "Не": "Не",54 40 "document": "Документ", 55 "Download": "Преземи",56 41 "pay_online": "Плати онлајн", 57 "status": "Статус",58 42 "none": "—", 59 43 "show_rows": "Прикажи редови", … … 65 49 "documents_payment_info": "За повеќе информации за плаќање документи, обратете се во администрација.", 66 50 "total_documents": "Вкупно документи", 67 "approved": "Одобрен и",51 "approved": "Одобрено", 68 52 "total_price": "Вкупна цена", 69 53 "mkd": "мкд", 70 54 "free": "0,00", 55 "download": "Преземи", 71 56 "choose_document": "Избери документ", 72 "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.",73 57 "university": "Универзитет „Св. КИРИЛ И МЕТОДИЈ“ - Скопје", 74 58 "faculty": "Факултет за информатички науки и компјутерско инженерство", … … 147 131 "decade": "Декада", 148 132 "important_note": "Важна забелешка", 133 "applications_important_note": "Важна забелешка за испитните пријави", 149 134 "yes": "Да", 150 135 "no": "Не", … … 168 153 "doc1": "Документ 1", 169 154 "verified": "Потврдено", 170 "taxes": "Такси", 171 "signatures": "Потписи", 172 "valid": "Валидно", 173 "completed_semester": "Завршени семестри", 174 "verified_semesters": "Потврдени семестри", 175 "total_semesters": "Вкупно семестри", 176 "completed_semesters": "Завршени семестри", 177 "2025_summer_session2": "2025 (Летна) Втора испитна сесија", 178 "2025_summer_session1": "2025 (Летна) Прва испитна сесија", 179 "2025_winter_session2": "2025 (Зимска) Втора испитна сесија", 180 "2025_winter_session1": "2025 (Зимска) Прва испитна сесија", 181 "2024_summer_session2": "2024 (Летна) Втора испитна сесија", 182 "2024_summer_session1": "2024 (Летна) Прва испитна сесија", 183 "2024_winter_session2": "2024 (Зимска) Втора испитна сесија", 184 "2024_winter_session1": "2024 (Зимска) Прва испитна сесија", 185 "select_document": "Изберете документ", 155 "debt": "Долг", 156 "debt_from_documents": "Долг од документи", 157 "ticket_number": "Број на тикет", 158 "select_semester": "Избери семестар", 159 "enrolled_subjects": "Запишани предмети", 160 "financial_info": "Финансиски информации", 161 "material_costs": "Материјални трошоци", 162 "material_costs_info": "Материјалните трошоци ги вклучуваат сите задолжителни надоместоци за материјали и ресурси за предметите.", 163 "electronic_registration": "Електронска регистрација", 164 "eukim": "еУКИМ", 165 "bank_provision": "Банкарска провизија", 166 "which_time": "Кој пат", 167 "signature": "Потпис", 168 "group": "Група", 169 "professor": "Професор", 170 "subjects_list": "Листа на предмети", 171 "subjects_overview": "Преглед на вашите запишани предмети и нивниот статус.", 172 "mandatory_short": "Зад.", 173 "elective_short": "Изб.", 174 "enrolled_by_student": "Запишан од студентот", 175 "due": "Долг", 176 "failed_to_load_subjects": "Не успеавме да ги вчитаме предметите.", 177 "hours": "Часови", 186 178 "administrative_regulation": "Барање за административно регулирање на ретроактивен семестар (од 2024г) ( 1000)", 187 179 "exam_application_satisfaction": "Барање за задочно пријавување на испит (од 2024г) ( 1000)", … … 220 212 "Ѓорѓи Маџаров": "Ѓорѓи Маџаров", 221 213 "Соња Гиевска": "Соња Гиевска", 214 "exams_overview": "Преглед на вашите положени испити и оценки.", 215 "failed_to_load_semesters": "Не успеавме да ги вчитаме семестрите.", 216 "Летна": "Летна", 217 "Зимска": "Зимска", 218 "Прва испитна сесија": "Прва испитна сесија", 219 "Втора испитна сесија": "Втора испитна сесија", 220 "Валидно": "Валидно", 221 "Да": "Да", 222 "Не": "Не", 222 223 "2025_summer_session2": "2025 (Летна) Втора испитна сесија", 223 224 "2025_summer_session1": "2025 (Летна) Прва испитна сесија", … … 230 231 "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.", 231 232 "login_student_title": "Најава за студент", 232 "login_student_example": "\"email\":\"boris@example.com\",\n\"password\":\"your-password-here\",", 233 "login_note_slow": "Може да потрае до 30 секунди по долга неактивност.", 233 "login_student_example": "Е-пошта: boris@example.com\nЛозинка: your-password-here", 234 234 "login_professor_title": "Најава за професор", 235 "login_professor_example": " \"email\":\"andonov@gmail.com\",\n\"password\":\"123lol456\",",235 "login_professor_example": "Е-пошта: andonov@gmail.com\nЛозинка: 123lol456", 236 236 "login_welcome": "Добредојде", 237 237 "login_subtitle": "Најавете се во вашиот IKnow акаунт", … … 246 246 "login_no_account": "Немате акаунт?", 247 247 "login_contact_admin": "Контактирајте ја администрацијата", 248 "login_footer": "Студентски информационен систем" 249 , 250 "failed_to_load_subjects": "Не успеавме да ги вчитаме предметите." 251 , 252 "serial_number": "Сериски број", 253 "mandatory_short": "Зад.", 254 "elective_short": "Изб." 248 "login_footer": "Студентски информационен систем", 249 "contact_us": "Контактирај нè", 250 "close": "Затвори", 251 "contact_name": "Име и презиме", 252 "contact_name_placeholder": "Вашето име", 253 "contact_email": "Е-пошта", 254 "contact_email_placeholder": "example@email.com", 255 "contact_subject": "Наслов", 256 "contact_subject_placeholder": "Тема на пораката", 257 "contact_message": "Порака", 258 "contact_message_placeholder": "Напишете ја вашата порака...", 259 "contact_send": "Испрати", 260 "contact_sending": "Испраќа...", 261 "contact_sent_title": "Пораката е испратена!", 262 "contact_sent_desc": "Ќе ви одговориме наскоро.", 263 "login_demo_note": "Користете ги овие податоци за да го истражите системот." 255 264 }
Note:
See TracChangeset
for help on using the changeset viewer.
