Changeset 4fe4582


Ignore:
Timestamp:
02/16/26 17:38:16 (7 months ago)
Author:
Stefan-Saveski <stefansaveski19@…>
Branches:
master
Children:
8496f3c
Parents:
d7deae5
Message:

Refactor code structure for improved readability and maintainability

Files:
5 added
11 edited

Legend:

Unmodified
Added
Removed
  • package-lock.json

    rd7deae5 r4fe4582  
    1717        "clsx": "^2.1.1",
    1818        "i18next": "^25.8.0",
     19        "jspdf": "^4.1.0",
     20        "jspdf-autotable": "^5.0.7",
    1921        "lucide-react": "^0.544.0",
    2022        "next": "^16.1.6",
     
    19691971      }
    19701972    },
     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    },
    19711986    "node_modules/@types/react": {
    19721987      "version": "19.2.10",
     
    19892004        "@types/react": "^19.2.0"
    19902005      }
     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
    19912013    },
    19922014    "node_modules/@typescript-eslint/eslint-plugin": {
     
    28462868      "dev": true,
    28472869      "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      }
    28482880    },
    28492881    "node_modules/baseline-browser-mapping": {
     
    29953027      "license": "CC-BY-4.0"
    29963028    },
     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    },
    29973049    "node_modules/chalk": {
    29983050      "version": "4.1.2",
     
    30663118      "license": "MIT"
    30673119    },
     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    },
    30683132    "node_modules/cross-spawn": {
    30693133      "version": "7.0.6",
     
    30793143      "engines": {
    30803144        "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"
    30813155      }
    30823156    },
     
    32363310      "engines": {
    32373311        "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"
    32383322      }
    32393323    },
     
    39594043      "license": "MIT"
    39604044    },
     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    },
    39614056    "node_modules/fastq": {
    39624057      "version": "1.20.1",
     
    39684063        "reusify": "^1.0.4"
    39694064      }
     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"
    39704071    },
    39714072    "node_modules/file-entry-cache": {
     
    43484449      "dependencies": {
    43494450        "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"
    43504465      }
    43514466    },
     
    44614576      }
    44624577    },
     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    },
    44634584    "node_modules/is-array-buffer": {
    44644585      "version": "3.0.5",
     
    49555076      "bin": {
    49565077        "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"
    49575105      }
    49585106    },
     
    58185966      }
    58195967    },
     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    },
    58205974    "node_modules/parent-module": {
    58215975      "version": "1.0.1",
     
    58576011      "dev": true,
    58586012      "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
    58596020    },
    58606021    "node_modules/picocolors": {
     
    59836144      "license": "MIT"
    59846145    },
     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    },
    59856156    "node_modules/react": {
    59866157      "version": "19.2.4",
     
    60636234      }
    60646235    },
     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    },
    60656243    "node_modules/regexp.prototype.flags": {
    60666244      "version": "1.5.4",
     
    61346312        "iojs": ">=1.0.0",
    61356313        "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"
    61366324      }
    61376325    },
     
    64436631      "license": "MIT"
    64446632    },
     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    },
    64456643    "node_modules/stop-iteration-iterator": {
    64466644      "version": "1.1.0",
     
    66426840      }
    66436841    },
     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    },
    66446852    "node_modules/tailwind-merge": {
    66456853      "version": "3.4.0",
     
    66716879        "type": "opencollective",
    66726880        "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"
    66736891      }
    66746892    },
     
    70157233      }
    70167234    },
     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    },
    70177245    "node_modules/void-elements": {
    70187246      "version": "3.1.0",
  • package.json

    rd7deae5 r4fe4582  
    1818    "clsx": "^2.1.1",
    1919    "i18next": "^25.8.0",
     20    "jspdf": "^4.1.0",
     21    "jspdf-autotable": "^5.0.7",
    2022    "lucide-react": "^0.544.0",
    2123    "next": "^16.1.6",
  • src/app/globals.css

    rd7deae5 r4fe4582  
    4646:root {
    4747  --radius: 0.625rem;
    48   --background: oklch(1 0 0);
     48  --background: #F4FAFF;
    4949  --foreground: oklch(0.145 0 0);
    5050  --card: oklch(1 0 0);
  • src/app/layout.tsx

    rd7deae5 r4fe4582  
    88import { ThemeProvider } from '../components/theme-provider';
    99import { ThemeToggle } from '../components/theme-toggle';
     10import ContactBubble from '../components/contact-bubble';
    1011
    1112config.autoAddCss = false;
     
    3536            </div>
    3637            <div className="mx-auto max-w-6xl px-4">{children}</div>
     38            <ContactBubble />
    3739          </I18nextProvider>
    3840        </ThemeProvider>
  • src/app/page.tsx

    rd7deae5 r4fe4582  
    5858                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
    5959                  <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">
    6161                    {t('login_student_example')}
    6262                  </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')}
    6565                  </div>
    6666                </div>
     
    6868                <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
    6969                  <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">
    7171                    {t('login_professor_example')}
     72                  </div>
     73                  <div className="mt-3 text-xs text-blue-600 font-medium">
     74                    {t('login_demo_note')}
    7275                  </div>
    7376                </div>
  • src/app/students/applications/page.tsx

    rd7deae5 r4fe4582  
    3030const CompletedBadge = ({ completed }: { completed: string }) => {
    3131  const { t } = useTranslation();
    32   if (completed === t('yes', 'Да')) {
     32  if (completed === 'Да') {
    3333    return (
    3434      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full" title={t('completed')}>
     
    230230              <h3 className="text-lg font-bold text-card-foreground">{t('completed', 'Завршени')}</h3>
    231231              <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}
    233233              </p>
    234234            </div>
  • src/app/students/documents/page.tsx

    rd7deae5 r4fe4582  
    66  faChevronDown,
    77  faCheckCircle,
     8  faClock,
    89  faMoneyBillWave,
    910  faFileText,
     
    1112  faChevronRight,
    1213  faAngleDoubleLeft,
    13   faAngleDoubleRight
     14  faAngleDoubleRight,
     15  faSpinner
    1416} from '@fortawesome/free-solid-svg-icons';
    1517import { useState } from 'react';
    1618import documentsData from '@/data/documents.json';
    1719import { useTranslation } from 'react-i18next';
     20import { getAccessToken } from '@/lib/auth';
     21import { downloadDocumentPDF } from '@/lib/pdf-generators';
    1822
    1923interface TableCellProps {
     
    3034const StatusBadge = ({ status }: { status: string }) => {
    3135  const { t } = useTranslation();
    32   if (status === t('approved', 'Одобрено')) {
     36  if (status === 'approved' || status === t('approved', 'Одобрено')) {
    3337    return (
    3438      <span className="inline-flex items-center justify-center w-8 h-8 bg-green-100 text-green-600 rounded-full">
    3539        <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', 'Во обработка')}
    3648      </span>
    3749    );
     
    5264};
    5365
     66interface 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
    5479export default function DocumentsPage() {
    5580  const [selectedDocumentType, setSelectedDocumentType] = useState("select_document");
     
    5883  const [currentPage, setCurrentPage] = useState(1);
    5984  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);
    6088  const { t } = useTranslation();
    6189
     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
    62142  const selectedDocument = documentsData.documentTypes.find(d => d.id === selectedDocumentType);
    63   const { documents, paymentInfo } = documentsData;
     143  const { paymentInfo } = documentsData;
    64144
    65145  const totalPages = Math.ceil(documents.length / recordsPerPage);
     
    153233        <div className="flex justify-end mt-6">
    154234          <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}
    157238          >
    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', 'Поднеси')}
    160241          </button>
    161242        </div>
     
    197278                  </TableCell>
    198279                  <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'}`}>
    200281                      {document.paid}
    201282                    </span>
    202283                  </TableCell>
    203284                  <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                      )}
    206298                    </button>
    207299                  </TableCell>
  • src/app/students/semesters/page.tsx

    rd7deae5 r4fe4582  
    5757  const { t } = useTranslation();
    5858  const baseClasses = "px-2 py-1 rounded-full text-xs font-medium";
    59   if (status === t('valid')) {
     59  if (status === 'Валидно') {
    6060    return (
    6161      <span className={`${baseClasses} bg-green-100 text-green-800 flex items-center gap-1`}>
    6262        <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" />
    63         {t('valid')}
     63        {t('Валидно', 'Валидно')}
    6464      </span>
    6565    );
     
    6767  return (
    6868    <span className={`${baseClasses} bg-accent text-gray-800`}>
    69       {t(status) || status}
     69      {t(status, status)}
    7070    </span>
    7171  );
     
    7474const YesNoBadge = ({ value }: { value: string }) => {
    7575  const { t } = useTranslation();
    76   if (value === t('yes')) {
     76  if (value === 'Да') {
    7777    return (
    7878      <span className="inline-flex items-center justify-center w-6 h-6 bg-green-100 text-green-600 rounded-full">
     
    8080      </span>
    8181    );
    82   } else if (value === t('no')) {
     82  } else if (value === 'Не') {
    8383    return (
    8484      <span className="inline-flex items-center justify-center w-6 h-6 bg-red-100 text-red-600 rounded-full">
     
    313313                      <span className="text-green-600 font-medium flex items-center gap-1">
    314314                        <FontAwesomeIcon icon={faCheckCircle} className="w-3 h-3" />
    315                         {semester.completed}
     315                        {t(semester.completed, semester.completed)}
    316316                      </span>
    317317                    ) : (
    318318                      <span className="text-red-600 font-medium flex items-center gap-1">
    319319                        <FontAwesomeIcon icon={faTimesCircle} className="w-3 h-3" />
    320                         {semester.completed}
     320                        {t('Не', 'Не')}
    321321                      </span>
    322322                    )}
  • src/components/exams.tsx

    rd7deae5 r4fe4582  
    290290                <tr key={exam.id} className="hover:bg-accent/50 transition-colors duration-150">
    291291                  <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>
    294294                  <td className="py-4 px-4 text-sm text-muted-foreground">{exam.credits}</td>
    295295                  <td className="py-4 px-4 text-sm text-muted-foreground">{exam.date}</td>
  • src/locales/en/translation.json

    rd7deae5 r4fe4582  
    77  "student_id_placeholder": "e.g. 20001",
    88  "no_students_found": "No students found.",
    9   "Забелешка": "Note",
    10   "Завршени": "Completed",
    11   "Останати": "Remaining",
    129  "taxes": "Taxes",
    1310  "signatures": "Signatures",
     
    2421  "add_comment": "Add comment",
    2522  "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",
    2630  "my_documents": "My Documents",
    2731  "archive": "Archive",
     
    4246  "mkd": "MKD",
    4347  "free": "0.00",
    44   "university": "University 'St. Cyril and Methodius' - Skopje",
     48  "download": "Download",
     49  "university": "University ‘St. Cyril and Methodius’ - Skopje",
    4550  "faculty": "Faculty of Computer Science and Engineering",
    4651  "logout": "Logout",
     
    140145  "doc1": "Document 1",
    141146  "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)",
    143172  "semester_enrollment_after_deadline": "Request for semester enrollment after deadline (from 2024) (1500)",
    144173  "certificate_issuance_various": "Request for issuance of certificates for various reasons (from 2024) (1500)",
     
    158187  "exam_recognition": "Recognition of passed exams (0)",
    159188  "student_card_old": "Student card (Old) (0)",
     189  "Уверение за положени испити ФИНКИ": "Certificate of passed exams FINKI",
     190  "УППИ образец": "UPPI form",
     191  "Уверение за редовен студент - ФИНКИ": "Regular student certificate - FINKI",
    160192  "2025_summer_session2": "2025 (Summer) Second exam session",
    161193  "2025_summer_session1": "2025 (Summer) First exam session",
     
    168200  "applications_important_note_text": "The serial number of the electronic application must be entered on the paper application that the student will submit physically.",
    169201  "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",
    172203  "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",
    174205  "login_welcome": "Welcome",
    175206  "login_subtitle": "Sign in to your IKnow account",
     
    184215  "login_no_account": "Don't have an account?",
    185216  "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."
    218260}
  • src/locales/mk/translation.json

    rd7deae5 r4fe4582  
    1414  "edit_grade": "Измени оценка",
    1515  "remove_grade": "Избриши оценка",
     16  "taxes": "Такси",
     17  "signatures": "Потписи",
     18  "valid": "Валидно",
     19  "completed_semester": "Завршени семестри",
     20  "verified_semesters": "Потврдени семестри",
     21  "total_semesters": "Вкупно семестри",
     22  "completed_semesters": "Завршени семестри",
    1623  "documents_overview": "Преглед на вашите документи и нивниот статус.",
    1724  "new_document_request": "Ново барање за документ",
    18   "select_document": "Избери документ",
     25  "select_document": "Изберете документ",
    1926  "price": "Цена",
    2027  "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 мкд)",
    4628  "add_comment": "Додај коментар",
    4729  "submit": "Поднеси",
     30  "submitting": "Се поднесува...",
     31  "document_submitted": "Барањето за документ е успешно поднесено!",
     32  "document_submit_error": "Грешка при поднесување на барањето.",
     33  "not_authenticated": "Не сте најавени. Ве молиме најавете се повторно.",
     34  "pdf_error": "Грешка при генерирање на документот. Обидете се повторно.",
     35  "generating": "Генерира...",
     36  "pending": "Во обработка",
    4837  "my_documents": "Мои документи",
    4938  "archive": "Архива",
    5039  "request": "Барање",
    51   "paid": "Платено",
    52   "ДА": "ДА",
    53   "Не": "Не",
    5440  "document": "Документ",
    55   "Download": "Преземи",
    5641  "pay_online": "Плати онлајн",
    57   "status": "Статус",
    5842  "none": "—",
    5943  "show_rows": "Прикажи редови",
     
    6549  "documents_payment_info": "За повеќе информации за плаќање документи, обратете се во администрација.",
    6650  "total_documents": "Вкупно документи",
    67   "approved": "Одобрени",
     51  "approved": "Одобрено",
    6852  "total_price": "Вкупна цена",
    6953  "mkd": "мкд",
    7054  "free": "0,00",
     55  "download": "Преземи",
    7156  "choose_document": "Избери документ",
    72   "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.",
    7357  "university": "Универзитет „Св. КИРИЛ И МЕТОДИЈ“ - Скопје",
    7458  "faculty": "Факултет за информатички науки и компјутерско инженерство",
     
    147131  "decade": "Декада",
    148132  "important_note": "Важна забелешка",
     133  "applications_important_note": "Важна забелешка за испитните пријави",
    149134  "yes": "Да",
    150135  "no": "Не",
     
    168153  "doc1": "Документ 1",
    169154  "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": "Часови",
    186178  "administrative_regulation": "Барање за административно регулирање на ретроактивен семестар (од 2024г) ( 1000)",
    187179  "exam_application_satisfaction": "Барање за задочно пријавување на испит (од 2024г) ( 1000)",
     
    220212  "Ѓорѓи Маџаров": "Ѓорѓи Маџаров",
    221213  "Соња Гиевска": "Соња Гиевска",
     214  "exams_overview": "Преглед на вашите положени испити и оценки.",
     215  "failed_to_load_semesters": "Не успеавме да ги вчитаме семестрите.",
     216  "Летна": "Летна",
     217  "Зимска": "Зимска",
     218  "Прва испитна сесија": "Прва испитна сесија",
     219  "Втора испитна сесија": "Втора испитна сесија",
     220  "Валидно": "Валидно",
     221  "Да": "Да",
     222  "Не": "Не",
    222223  "2025_summer_session2": "2025 (Летна) Втора испитна сесија",
    223224  "2025_summer_session1": "2025 (Летна) Прва испитна сесија",
     
    230231  "applications_important_note_text": "Серискиот број на електронската пријава задолжително треба да се впише на хартиената пријава која студентот ќе ја поднесе физички.",
    231232  "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",
    234234  "login_professor_title": "Најава за професор",
    235   "login_professor_example": "\"email\":\"andonov@gmail.com\",\n\"password\":\"123lol456\",",
     235  "login_professor_example": "Е-пошта: andonov@gmail.com\nЛозинка: 123lol456",
    236236  "login_welcome": "Добредојде",
    237237  "login_subtitle": "Најавете се во вашиот IKnow акаунт",
     
    246246  "login_no_account": "Немате акаунт?",
    247247  "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": "Користете ги овие податоци за да го истражите системот."
    255264}
Note: See TracChangeset for help on using the changeset viewer.