Digitalni portal za elektronske administrativne usluge — građani podnose i prate zahteve online, službenici ih obrađuju, a administratori upravljaju institucijama, servisima i korisnicima.
🌐 Live aplikacija: https://portal-euprava.onrender.com — ✅ deploy-ovano na Render-u (jedan Docker servis: React + Laravel).
Aplikacija se pušta u rad i jednim klikom (dugme ispod) — Render pročita
render.yaml, build-uje Docker image, izvršimigrate --seedi pokrene servis.⏳ Napomena: na besplatnom planu servis „zaspi” posle neaktivnosti, pa prvo učitavanje može potrajati ~30–60 s dok se ne probudi.
Deploy-ovana instanca dolazi sa već popunjenim (seed-ovanim) podacima: 3 institucije, 3 servisa sa dinamičkim poljima, 13 korisnika (3 fiksna demo naloga + 10 Faker korisnika) i ~22 demo zahteva raspoređenih po svim statusima. Možete se odmah prijaviti pomoću demo naloga.
- O projektu
- Tehnologije
- Arhitektura
- Model podataka (ER dijagram)
- Tok obrade zahteva
- Ključni koncept: dinamička forma (
form_data) - Korisničke uloge i slučajevi korišćenja
- API reference
- Demo nalozi
- Lokalno pokretanje
- Deployment
- Struktura projekta
Portal eUprava je web aplikacija koja digitalizuje osnovne administrativne usluge i omogućava građanima da podnose zahteve online, prate njihov status, preuzimaju dokumenta u PDF formatu i razmenjuju napomene vezane za obradu. Aplikacija je organizovana po ulogama korisnika (CITIZEN, OFFICER, ADMIN) i koristi jasan tok zahteva:
DRAFT → SUBMITTED → IN_REVIEW → APPROVED / REJECTED
Sistem se oslanja na koncept Servisa (npr. izdavanje izvoda, uverenja, zakazivanje termina) koji pripadaju određenim Institucijama. Svaki servis ima dinamički definisana polja (Service Fields) koja određuju šta korisnik unosi u formu. Uneseni podaci čuvaju se u koloni form_data kao JSON mapa (ključ → vrednost), što omogućava univerzalnu podršku za različite usluge bez tvrdog kodiranja svake forme.
Projekat je podeljen na dva nezavisna sloja — REST API (Laravel) i Single Page Application (React) — koja u produkciji isporučuje jedan Docker kontejner.
| Tehnologija | Verzija | Uloga u projektu |
|---|---|---|
| PHP | ^8.2 |
Runtime jezik |
| Laravel | ^12.0 |
Web framework, routing, Eloquent ORM, migracije, seederi |
| Laravel Sanctum | ^4.2 |
Token autentifikacija (Bearer / personal access tokens) |
| barryvdh/laravel-dompdf | ^3.1 |
Generisanje PDF dokumenata zahteva |
| Laravel Tinker | ^2.10 |
REPL za rad sa modelima |
| SQLite | — | Lagana relaciona baza (datoteka, bez servera) |
| fakerphp/faker | ^1.23 |
Generisanje realističnih (američkih) demo podataka u seed-u |
| PHPUnit / Pest | ^11.5 |
Testovi |
| Laravel Pint | ^1.24 |
Code style (PSR-12) |
Korišćeni Laravel koncepti: Eloquent modeli sa relacijama (app/Models), API Resources za serializaciju (app/Http/Resources), Form Request validacija u kontrolerima, slojevite migracije (kreiranje tabela → FK → default vrednosti → nullable ograničenja) i Database Factories + Seeders.
| Tehnologija | Verzija | Uloga u projektu |
|---|---|---|
| React | ^19.2 |
UI biblioteka (SPA) |
Create React App (react-scripts) |
5.0.1 |
Build alat / dev server |
Mantine (@mantine/core, dates, dropzone, hooks, notifications) |
^8.3 |
UI komponente, forme, modali, notifikacije, upload |
| React Router DOM | ^7.11 |
Klijentsko rutiranje i zaštićene rute po ulozi |
| Axios | ^1.13 |
HTTP klijent sa interceptor-om za Bearer token |
| Recharts | ^3.6 |
Grafikoni na stranicama statistike |
| Tabler Icons | ^3.36 |
Ikonice |
| Day.js | ^1.11 |
Rad sa datumima |
| Tehnologija | Uloga |
|---|---|
| Docker (multi-stage build) | Build React-a + pakovanje Laravel aplikacije u jedan image |
| Render | Hosting (Docker web service, render.yaml Blueprint) |
| Git / GitHub | Verzionisanje |
U produkciji Laravel servira i React build i /api rute sa istog porekla (origin), pa nema CORS-a niti odvojenog frontend hostinga. CRA build se kopira u public/, a sve rute koje nisu /api/* vraćaju index.html (routes/web.php) kako bi React Router preuzeo rutiranje.
flowchart LR
subgraph Browser["🌐 Browser (SPA)"]
UI["React 19 + Mantine 8<br/>React Router 7"]
AX["Axios klijent<br/>baseURL = /api<br/>Bearer token interceptor"]
UI --> AX
end
subgraph Server["🐳 Docker kontejner (Render)"]
direction TB
WEB["routes/web.php<br/>servira index.html (SPA)"]
API["routes/api.php<br/>REST kontroleri"]
SANCTUM["Laravel Sanctum<br/>auth:sanctum"]
ELO["Eloquent modeli<br/>+ API Resources"]
PDF["DomPDF<br/>PDF izveštaji"]
API --> SANCTUM --> ELO
API --> PDF
end
DB[("🗄️ SQLite<br/>database.sqlite")]
AX -->|"HTTPS /api/*"| API
UI -.->|"navigacija /home-*, /admin/*"| WEB
ELO --> DB
classDef store fill:#003B57,stroke:#012,color:#fff;
class DB store;
Tok autentifikacije: POST /api/auth/login vraća Sanctum token → frontend ga čuva u sessionStorage → Axios interceptor dodaje Authorization: Bearer <token> na svaki naredni zahtev (src/api/api.js).
Model ima 5 entiteta. Relacije su jednostavne i hijerarhijske (bez ukrštanja linija): institucija sadrži servise, servis ima polja i zahteve, a korisnik figurira u zahtevu u dve uloge — kao podnosilac (user_id) i kao službenik koji obrađuje (processed_by).
erDiagram
INSTITUTIONS ||--o{ SERVICES : "ima"
SERVICES ||--o{ SERVICE_FIELDS : "definiše"
SERVICES ||--o{ SERVICE_REQUESTS : "generiše"
USERS ||--o{ SERVICE_REQUESTS : "podnosi (citizen)"
USERS ||--o{ SERVICE_REQUESTS : "obrađuje (officer)"
USERS {
bigint id PK
string name
string email UK
string password
date date_of_birth
string jmbg UK "13 cifara"
string role "CITIZEN | OFFICER | ADMIN"
datetime created_at
}
INSTITUTIONS {
bigint id PK
string name
string city
string address
string email
datetime created_at
}
SERVICES {
bigint id PK
bigint institution_id FK
string name UK
text description
decimal fee "iznos takse"
boolean requires_attachment
string status "ACTIVE | INACTIVE"
datetime created_at
}
SERVICE_FIELDS {
bigint id PK
bigint service_id FK
string key "deo jedinstvenog para (service_id, key)"
string label
string data_type "STRING|NUMBER|DATE|BOOLEAN|SELECT|FILE"
boolean is_required
json options
json validation_rules
int sort_order
}
SERVICE_REQUESTS {
bigint id PK
bigint user_id FK "podnosilac (citizen)"
bigint service_id FK
bigint processed_by FK "službenik, nullable"
string status "DRAFT|SUBMITTED|IN_REVIEW|APPROVED|REJECTED"
text citizen_note
text officer_note
string attachment
json form_data "popunjena polja (key -> value)"
string payment_status "NOT_REQUIRED|NOT_PAID|PAID"
datetime payment_date
}
users— nalozi sa ulogom (role). Jedan korisnik može da podnese više zahteva i, ako je službenik, da obradi više zahteva. Poljajmbgiemailsu jedinstvena.institutions— institucije koje nude servise (npr. Matična služba Grada Beograda).services— usluge koje pripadaju instituciji; nose taksu (fee), zastavicu da li traže prilog i status. Brisanje institucije kaskadno briše servise.service_fields— definicija dinamičke forme za servis (koja polja, kog tipa, validacija, redosled). Jedinstven par(service_id, key).service_requests— zahtev građana za servis; čuva popunjenu formu uform_data, status u toku obrade, napomene, prilog i status plaćanja.
📌 Sve relacije su definisane sa odgovarajućim FK ograničenjima u
...add_foreign_keys.php:cascadeOnDeleteza vlasničke veze, anullOnDeletezaprocessed_by(brisanje službenika ne briše zahtev).
stateDiagram-v2
direction LR
[*] --> DRAFT: građanin kreira zahtev
DRAFT --> SUBMITTED: pošalji na obradu
SUBMITTED --> IN_REVIEW: službenik preuzme (assign)
IN_REVIEW --> APPROVED: odobreno
IN_REVIEW --> REJECTED: odbijeno
APPROVED --> [*]
REJECTED --> [*]
| Status | Značenje |
|---|---|
| DRAFT | Zahtev je kreiran, ali još nije poslat. |
| SUBMITTED | Poslat; čeka da ga neki službenik preuzme (inbox). |
| IN_REVIEW | Dodeljen službeniku i u obradi. |
| APPROVED | Odobren (može zahtevati plaćanje takse → payment_status = PAID). |
| REJECTED | Odbijen (npr. nedostaje dokumentacija). |
Za razliku od fiksnih formi, Portal eUprava koristi Service Fields kao izvor istine o tome koja se polja prikazuju i unose. Kada građanin popuni formu, vrednosti se čuvaju u form_data kao mapirani JSON objekat. Time:
- novi servis i nova polja se dodaju bez izmene frontenda,
- prikaz detalja zahteva uvek može univerzalno da prikaže sva polja (vidi
FormDataPretty.jsx), - aplikacija ostaje skalabilna i lako proširiva na veliki broj usluga.
Primer (form_data za servis eIzvod iz matične knjige rođenih):
{
"delivery_method": "EMAIL",
"purpose": "za potrebe zaposlenja",
"contact_phone": "0641234567"
}- Registracija i prijava (token autentifikacija); registracija je rezervisana za građane.
- Pregled servisa i detalja (opis, iznos takse).
- Pokretanje zahteva — bira servis i popunjava dinamička polja (Service Fields).
- DRAFT → SUBMITTED — kreira nacrt pa ga šalje na obradu.
- Upload priloga (opciono, ako servis to traži).
- Praćenje zahteva — lista, filtriranje, promene statusa.
- Preuzimanje PDF dokumenta zahteva.
- Inbox — zahtevi u statusu SUBMITTED koji nisu dodeljeni.
- Preuzimanje (assign) zahteva → postaje zadužen.
- Dodeljeni meni — lista zahteva koje obrađuje.
- Promena statusa (IN_REVIEW → APPROVED/REJECTED) uz napomenu.
- PDF i Statistika (broj zahteva po statusu, metrike).
- CRUD institucija i pregled njihovih servisa.
- CRUD servisa (taksa, status, povezivanje sa institucijom).
- CRUD polja servisa (Service Fields — key, label, tip, obavezno, opcije, redosled).
- Upravljanje korisnicima — promena uloge, brisanje (uz zaštitu sopstvenog naloga).
- Globalna statistika — agregati, distribucija po statusima, trendovi.
Bazni prefiks: /api. Sve rute osim registracije i prijave zahtevaju Authorization: Bearer <token> (Laravel Sanctum). Definicije: routes/api.php.
| Metoda | Putanja | Opis |
|---|---|---|
POST |
/api/auth/register |
Registracija građanina (vraća token) |
POST |
/api/auth/login |
Prijava (vraća token + korisnika) |
GET |
/api/auth/me |
Trenutno prijavljeni korisnik |
POST |
/api/auth/logout |
Odjava (poništava token) |
| Metoda | Putanja | Opis |
|---|---|---|
GET |
/api/services |
Lista servisa |
GET |
/api/services/{id} |
Detalji servisa |
POST / PUT / DELETE |
/api/services{/id} |
CRUD (ADMIN) |
GET |
/api/services/{id}/fields |
Polja servisa |
POST |
/api/services/{id}/fields |
Dodaj polje (ADMIN) |
PUT / DELETE |
/api/service-fields/{id} |
Izmena/brisanje polja (ADMIN) |
| Metoda | Putanja | Opis |
|---|---|---|
GET/POST/PUT/DELETE |
/api/institutions{/id} |
apiResource CRUD (ADMIN) |
| Metoda | Putanja | Opis |
|---|---|---|
GET |
/api/service-requests |
Lista (filtrirana po ulozi) |
GET |
/api/service-requests/{id} |
Detalji zahteva |
POST |
/api/service-requests |
Kreiraj (DRAFT) |
PUT |
/api/service-requests/{id} |
Izmena |
PATCH |
/api/service-requests/{id}/submit |
DRAFT → SUBMITTED |
PATCH |
/api/service-requests/{id}/assign |
Preuzmi (officer) |
PATCH |
/api/service-requests/{id}/status |
Promena statusa |
PATCH |
/api/service-requests/{id}/payment |
Ažuriranje plaćanja |
DELETE |
/api/service-requests/{id} |
Brisanje |
GET |
/api/service-requests/{id}/pdf |
PDF izveštaj (DomPDF) |
| Metoda | Putanja | Opis |
|---|---|---|
GET |
/api/users{/id} |
Lista / detalji (ADMIN) |
PATCH |
/api/users/{id}/role |
Promena uloge (ADMIN) |
DELETE |
/api/users/{id} |
Brisanje korisnika (ADMIN) |
GET |
/api/stats |
Metrike (zavise od uloge) |
POST |
/api/uploads/filebin |
Upload priloga |
Seed kreira fiksni demo nalog za svaku ulogu (lozinke su iste i na lokalu i na deploy-u), da bi se aplikacija mogla isprobati iz svakog ugla:
| Uloga | Ime | Lozinka | |
|---|---|---|---|
| 👤 CITIZEN | Ksenija Cvjetan | ksenija@euprava.test |
ksenija123 |
| 🧑💼 OFFICER | Michael Johnson | officer@euprava.test |
officer123 |
| 🛠️ ADMIN | Admin Portal | admin123@euprava.test |
admin123 |
Seeder je osmišljen kao demo cele aplikacije:
- Ksenija (građanin) ima po jedan zahtev u svakom statusu (DRAFT, SUBMITTED, IN_REVIEW, APPROVED, REJECTED) — vide se svi prikazi i ceo tok obrade.
- Michael Johnson (službenik) ima popunjen inbox (nedodeljeni SUBMITTED zahtevi) i listu „Assigned to me” (već obrađeni zahtevi).
- Admin vidi sve institucije, servise, korisnike i agregiranu statistiku.
Jedino ručno definisano lično ime u seed-u je Ksenija Cvjetan. Ostali korisnici (uključujući službenika Michael Johnson i dodatne građane) dobijaju američka imena preko Faker-a (
APP_FAKER_LOCALE=en_US), vidiUserSeeder.php,ServiceRequestSeeder.phpiUserFactory.php. Lozinka dodatnih (Faker) naloga jepassword.
Preduslovi: PHP 8.2+, Composer, Node 18+ (preporučeno 22), npm.
cd portal-euprava
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate:fresh --seed # kreira SQLite bazu i puni demo podatke
php artisan servecd portal-euprava-frontend
npm install
npm startCRA proxy (http://127.0.0.1:8000) prosleđuje /api pozive backendu tokom razvoja, pa ne morate da podešavate CORS.
docker build -t portal-euprava .
docker run -p 8080:10000 portal-euprava # otvori http://localhost:8080Aplikacija se deploy-uje na Render kao jedan Docker web service. Repozitorijum sadrži sve potrebno:
Dockerfile— multi-stage build: (1)node:22build React-a → (2)php:8.2instalira zavisnosti i kopira React build upublic/.docker-entrypoint.sh— pri pokretanju generišeAPP_KEY, kreira SQLite bazu i izvršavamigrate:fresh --seed(zato deploy uvek dolazi sa popunjenim demo podacima), pa pokreće server na$PORT.render.yaml— Render Blueprint (planfree, env varijable, health check/).
Najbrže (jedan klik): kliknite na dugme iznad — Render automatski pročita
render.yaml i ponudi deploy ovog repozitorijuma.
Ručno:
- Push koda na GitHub.
- Na dashboard.render.com → New + → Blueprint → poveži ovaj repozitorijum.
- Render pročita
render.yaml, build-uje Docker image i pokrene servis. - Po završetku, javni URL dodajte u sekciju Live demo.
ℹ️ Na besplatnom planu fajl sistem je efemeran, pa se demo podaci ponovo seed-uju pri svakom pokretanju — što je idealno za prezentacionu instancu.
EUPRAVA/
├── Dockerfile # multi-stage: React build + Laravel runtime
├── docker-entrypoint.sh # migrate --seed + start servera
├── render.yaml # Render Blueprint
├── README.md
│
├── portal-euprava/ # 🐘 Backend (Laravel 12 REST API)
│ ├── app/
│ │ ├── Http/Controllers/ # Auth, Service, ServiceRequest, Institution, Stats, PDF...
│ │ ├── Http/Resources/ # API Resources (serializacija)
│ │ └── Models/ # User, Institution, Service, ServiceField, ServiceRequest
│ ├── database/
│ │ ├── migrations/ # slojevite migracije (tabele → FK → defaults → nullable)
│ │ ├── factories/ # Faker fabrike
│ │ └── seeders/ # demo podaci (Ksenija Cvjetan + američka imena)
│ └── routes/{api.php,web.php} # REST rute + SPA fallback
│
└── portal-euprava-frontend/ # ⚛️ Frontend (React 19 + Mantine 8)
└── src/
├── api/api.js # Axios inst, Bearer interceptor
├── pages/ # ekrani po ulozi (Citizen / Officer / Admin)
├── components/ # ProtectedRoute, FormDataPretty, Slider
├── hooks/ # useLatestSerbianNews
└── utils/auth.js # session/role helperi
Napravljeno sa ❤️ koristeći Laravel i React.