Skip to content

Commit 157aaf1

Browse files
committed
Fixevi
1 parent 4e43130 commit 157aaf1

8 files changed

Lines changed: 794 additions & 115 deletions

File tree

backend/index.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -780,30 +780,41 @@ app.put("/api/receipts/:id", requireAuth, async (req, res) => {
780780

781781
// ========== TRANSACTIONS API ==========
782782
app.get("/api/transactions", requireAuth, async (req, res) => {
783+
const { from, to } = req.query;
784+
783785
console.log("\n========================================");
784-
console.log("📍 GET /api/transactions endpoint hit");
785-
console.log("🔐 User:", req.user?.email || "NOT AUTHENTICATED");
786-
console.log("========================================");
787786

788787
try {
789-
console.log("🔍 Fetching transactions from database...");
788+
789+
let whereClause = {};
790+
if (from && to) {
791+
const startDate = new Date(from);
792+
startDate.setHours(6, 0, 0, 0);
793+
794+
const endDate = new Date(to);
795+
endDate.setDate(endDate.getDate() + 1);
796+
endDate.setHours(5, 59, 59, 999);
797+
798+
whereClause.createdAt = {
799+
gte: startDate,
800+
lte: endDate
801+
};
802+
803+
}
804+
790805
const transactions = await prisma.transaction.findMany({
806+
where: whereClause,
791807
include: {
792808
receipt: { include: { items: { include: { article: true } } } },
793809
user: { select: { id: true, name: true, email: true } }
794810
},
795811
orderBy: { createdAt: "desc" },
796812
});
797813

798-
console.log(`✅ Successfully fetched ${transactions.length} transactions`);
799-
console.log("📊 First transaction sample:", JSON.stringify(transactions[0], null, 2));
800-
console.log("========================================\n");
801814

802815
res.json(transactions);
803816
} catch (error) {
804-
console.error("❌ ERROR in /api/transactions endpoint:");
805-
console.error("Error message:", error.message);
806-
console.error("Error stack:", error.stack);
817+
807818
console.error("========================================\n");
808819
res.status(500).json({ error: error.message, details: error.toString() });
809820
}

frontend/src/components/Sidebar.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export default function Sidebar() {
8080
isActive("/admin/ukupni-izvjestaj") ? "active" : ""
8181
}`}
8282
>
83-
Ukupni izvještaj
83+
Arhiva izvještaja
8484
</Link>
8585
<Link
8686
to="/admin/artikli"
@@ -108,7 +108,7 @@ export default function Sidebar() {
108108
isActive("/admin/povijest") ? "active" : ""
109109
}`}
110110
>
111-
Povijest transakcija
111+
Povijest računa
112112
</Link>
113113
<Link
114114
to="/admin/korisnici"

frontend/src/pages/Izvjestaj.jsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import "../styles/Pages.css";
33

44
const fmtDate = (d) => d ? d.toLocaleDateString("hr-HR", { day: '2-digit', month: '2-digit', year: 'numeric' }) + " " + d.toLocaleTimeString("hr-HR", { hour: '2-digit', minute: '2-digit' }) : "N/A";
55

6-
const IzvjestajReceipt = ({ data, pologValue }) => {
6+
const IzvjestajReceipt = ({ data, pologValue, paymentStornoCounts }) => {
77
const W = 42;
88
const line = "─".repeat(W);
99

@@ -36,9 +36,10 @@ const IzvjestajReceipt = ({ data, pologValue }) => {
3636

3737
const paymentLines = Object.entries(articlesByPayment).map(([method, articles]) => {
3838
const count = paymentCounts[method] || 0;
39+
const stornoCount = paymentStornoCounts?.[method] || 0;
3940
const totalSum = Object.values(articles).reduce((sum, item) => sum + item.total, 0);
40-
const methodLabel = rpad(method, COL_NAME);
41-
return methodLabel + lpad(String(count), COL_QTY) + " " + lpad(totalSum.toFixed(2) + " \u20ac", COL_TOT);
41+
const methodLabel = rpad(method, COL_NAME - 5);
42+
return methodLabel + lpad(String(count), 3) + "/" + lpad(String(stornoCount), 2) + " " + lpad(totalSum.toFixed(2) + " \u20ac", COL_TOT);
4243
}).join("\n");
4344

4445
const firstMethod = Object.keys(paymentTotals)[0];
@@ -75,7 +76,7 @@ ${articleLines}
7576
${line}
7677
${rpad("UKUPNO", W - COL_TOT - 1)}${lpad(grandTotal.toFixed(2) + " \u20ac", COL_TOT + 1)}
7778
${line}
78-
${rpad("Način plaćanja", COL_NAME)}${lpad("Kol.", COL_QTY)} ${lpad("Iznos", COL_TOT)}
79+
${rpad("Način plaćanja", COL_NAME - 5)}${lpad("Kol.", 3)}/${lpad("St.", 2)} ${lpad("Iznos", COL_TOT)}
7980
${line}
8081
${paymentLines}
8182
${line}
@@ -138,10 +139,19 @@ export default function Izvjestaj() {
138139
const articlesByPayment = {};
139140
const allArticles = {};
140141
const paymentCounts = {};
142+
const paymentStornoCounts = {};
141143

142-
positiveReceipts.forEach(receipt => {
143-
const method = receipt.paymentType;
144-
paymentCounts[method] = (paymentCounts[method] || 0) + 1;
144+
// Broji samo RACUN i STORNO (isključuje RACUN_STORNIRAN)
145+
dayReceipts.forEach(receipt => {
146+
if (receipt.status !== 'RACUN_STORNIRAN') {
147+
const method = receipt.paymentType;
148+
paymentCounts[method] = (paymentCounts[method] || 0) + 1;
149+
150+
// Broji samo STORNO
151+
if (receipt.status === 'STORNO') {
152+
paymentStornoCounts[method] = (paymentStornoCounts[method] || 0) + 1;
153+
}
154+
}
145155
});
146156

147157
dayReceipts.forEach(receipt => {
@@ -235,6 +245,7 @@ return (
235245
<tr style={{background: '#f2f2f2'}}>
236246
<th style={{padding: '8px 15px', textAlign: 'left'}}>Način plaćanja</th>
237247
<th style={{padding: '8px 15px', textAlign: 'center'}}>Kol. računa</th>
248+
<th style={{padding: '8px 15px', textAlign: 'center'}}>Kol. storno</th>
238249
<th style={{padding: '8px 15px', textAlign: 'right'}}>Iznos</th>
239250
</tr>
240251
</thead>
@@ -243,18 +254,20 @@ return (
243254
<tr key={method} style={{borderBottom: '1px solid #ddd'}}>
244255
<td style={{padding: '8px 15px', fontWeight: '500'}}>{method}</td>
245256
<td style={{padding: '8px 15px', textAlign: 'center'}}>{paymentCounts[method] || 0}</td>
257+
<td style={{padding: '8px 15px', textAlign: 'center', color: '#d32f2f', fontWeight: 'bold'}}>{paymentStornoCounts[method] || 0}</td>
246258
<td style={{padding: '8px 15px', textAlign: 'right', fontWeight: 'bold'}}>{total.toFixed(2)}</td>
247259
</tr>
248260
))}
249261
</tbody>
250262
</table>
251263

252-
<h3 style={{marginBottom: '10px', color: '#444'}}>Prodani artikli</h3>
264+
<h3 style={{marginBottom: '10px', color: '#444'}}>Kol. artikala</h3>
253265
<table style={{width: '100%', borderCollapse: 'collapse', marginBottom: '10px', background: 'white'}}>
254266
<thead>
255267
<tr style={{background: '#ddd'}}>
256268
<th style={{padding: '8px 15px', textAlign: 'left'}}>Artikl</th>
257269
<th style={{padding: '8px 15px', textAlign: 'center'}}>Kol.</th>
270+
<th style={{padding: '8px 15px', textAlign: 'center'}}>Cijena</th>
258271
<th style={{padding: '8px 15px', textAlign: 'right'}}>Suma</th>
259272
</tr>
260273
</thead>
@@ -263,6 +276,7 @@ return (
263276
<tr key={name} style={{borderBottom: '1px solid #ddd'}}>
264277
<td style={{padding: '8px 15px'}}>{name}</td>
265278
<td style={{padding: '8px 15px', textAlign: 'center'}}>{data.quantity}</td>
279+
<td style={{padding: '8px 15px', textAlign: 'center'}}>{parseFloat(data.price).toFixed(2)}</td>
266280
<td style={{padding: '8px 15px', textAlign: 'right'}}>{data.total.toFixed(2)}</td>
267281
</tr>
268282
))}
@@ -288,7 +302,7 @@ return (
288302
{/* ISPIS SVEGA */}
289303
<div style={{display: 'none'}}>
290304
<div ref={receiptRef}>
291-
<IzvjestajReceipt data={reportData} pologValue={polog} />
305+
<IzvjestajReceipt data={reportData} pologValue={polog} paymentStornoCounts={paymentStornoCounts} />
292306
</div>
293307
</div>
294308
</div>

frontend/src/pages/Prodaja.jsx

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ const CroatianDateTime = () => {
1111

1212
export default function Prodaja() {
1313
const [articles, setArticles] = useState([]);
14+
const [categories, setCategories] = useState([]);
1415
const [selectedItems, setSelectedItems] = useState([]);
1516
const [paymentMethod, setPaymentMethod] = useState("Gotovina");
17+
const [locations, setLocations] = useState([]);
18+
const [selectedLocationId, setSelectedLocationId] = useState("");
1619
const [loading, setLoading] = useState(true);
1720
const [searchParams] = useSearchParams();
1821
const [offlineCount, setOfflineCount] = useState(0);
@@ -32,8 +35,27 @@ export default function Prodaja() {
3235
const offline = JSON.parse(localStorage.getItem("offline_receipts") || "[]");
3336
setOfflineCount(offline.length);
3437
fetchArticles();
38+
fetchLocations();
39+
fetchCategories();
3540
}, []);
3641

42+
const fetchLocations = async () => {
43+
try {
44+
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/prodajna-mjesta`, { credentials: "include" });
45+
const data = await response.json();
46+
if (response.ok && Array.isArray(data)) {
47+
setLocations(data.filter(loc => loc.active));
48+
if (data.length > 0 && !selectedLocationId) {
49+
setSelectedLocationId(String(data[0].id));
50+
}
51+
} else {
52+
setLocations([]);
53+
}
54+
} catch {
55+
setLocations([]);
56+
}
57+
};
58+
3759
const fetchArticles = async () => {
3860
try {
3961
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/articles`, { credentials: "include" });
@@ -46,6 +68,16 @@ export default function Prodaja() {
4668
}
4769
};
4870

71+
const fetchCategories = async () => {
72+
try {
73+
const response = await fetch(`${import.meta.env.VITE_API_URL}/api/categories`, { credentials: "include" });
74+
const data = await response.json();
75+
setCategories(response.ok && Array.isArray(data) ? data.filter(c => c.active) : []);
76+
} catch {
77+
setCategories([]);
78+
}
79+
};
80+
4981
const syncOfflineReceipts = async () => {
5082
const offline = JSON.parse(localStorage.getItem("offline_receipts") || "[]");
5183
if (offline.length === 0) return;
@@ -109,6 +141,7 @@ export default function Prodaja() {
109141

110142
const receiptData = {
111143
receiptNumber: `RCN-${Date.now()}`,
144+
prodajnoMjestoId: selectedLocationId ? Number(selectedLocationId) : null,
112145
invoiceType: "RAČUN",
113146
paymentType: paymentMethod === "Kartica" ? "KARTICA" : "GOTOVINA",
114147
brutto: totalBrutto,
@@ -174,6 +207,7 @@ export default function Prodaja() {
174207
tax: receipt.taxValue ?? totalTax,
175208
jir: receipt.jir ?? "",
176209
zki: receipt.zki ?? "",
210+
location: locations.find((loc) => String(loc.id) === String(selectedLocationId))?.name || "",
177211
link: buildPoreznaLink(receipt.jir, receipt.invoiceDate || receipt.createdAt || new Date(), receipt.brutto ?? totalBrutto),
178212
phone: "0916043415",
179213
email: "info@kset.org",
@@ -195,7 +229,7 @@ export default function Prodaja() {
195229
@media (min-width: 768px) {
196230
.prodaja-layout {
197231
display: grid;
198-
grid-template-columns: 1fr 380px;
232+
grid-template-columns: 1fr 450px;
199233
gap: 20px;
200234
height: calc(100vh - 120px);
201235
align-items: start;
@@ -209,12 +243,12 @@ export default function Prodaja() {
209243
210244
.grid {
211245
display: grid;
212-
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)) !important;
246+
grid-template-columns: repeat(2, 1fr) !important;
213247
gap: 15px !important;
214248
}
215249
216250
.article-card {
217-
min-height: 120px;
251+
min-height: 140px;
218252
display: flex;
219253
flex-direction: column;
220254
justify-content: center;
@@ -224,6 +258,15 @@ export default function Prodaja() {
224258
border-radius: 12px !important;
225259
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
226260
transition: transform 0.1s, border-color 0.1s;
261+
word-break: break-word;
262+
overflow-wrap: break-word;
263+
}
264+
265+
.article-card h3 {
266+
word-break: break-word;
267+
overflow-wrap: break-word;
268+
white-space: normal;
269+
margin: 0 0 8px 0;
227270
}
228271
229272
.article-card:active {
@@ -265,9 +308,9 @@ export default function Prodaja() {
265308
display: flex !important;
266309
align-items: center !important;
267310
justify-content: center !important;
268-
line-height: 0 !important; /* Prevents vertical clipping */
311+
line-height: 0 !important;
269312
padding: 0 !important;
270-
padding-bottom: 4px !important; /* Visual optical center adjustment */
313+
padding-bottom: 4px !important;
271314
cursor: pointer;
272315
background-color: #f8f9fa;
273316
border: 1px solid #ddd;
@@ -295,16 +338,30 @@ export default function Prodaja() {
295338
}
296339
`}</style>
297340

298-
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px' }}>
299-
<h1>
300-
Prodaja
301-
{offlineCount > 0 && (
341+
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '20px', flexWrap: 'wrap', gap: '10px' }}>
342+
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
343+
<h1 style={{ margin: 0 }}>
344+
Prodaja
345+
</h1>
346+
<label style={{ fontSize: '14px', color: '#333' }}>
347+
Prodajno mjesto:
348+
<select
349+
value={selectedLocationId}
350+
onChange={(e) => setSelectedLocationId(e.target.value)}
351+
style={{ marginLeft: '8px', padding: '5px 8px', borderRadius: '4px', border: '1px solid #ccc' }}
352+
>
353+
<option value="">(nije odabrano)</option>
354+
{locations.map(loc => (
355+
<option key={loc.id} value={loc.id}>{loc.name} ({loc.businessSpace})</option>
356+
))}
357+
</select>
358+
</label>
359+
</div>
360+
{offlineCount > 0 && (
302361
<span style={{color: 'red', fontSize: '14px', marginLeft: '10px'}}>
303362
({offlineCount} čekaju sinkronizaciju)
304363
</span>
305364
)}
306-
</h1>
307-
308365
{offlineCount > 0 && (
309366
<button
310367
onClick={syncOfflineReceipts}
@@ -327,7 +384,7 @@ export default function Prodaja() {
327384

328385
<div className="prodaja-layout">
329386
<div className="articles-grid">
330-
<h2>{categoryId ? "Artikli u kategoriji" : "Svi artikli"}</h2>
387+
<h2>{categoryId ? `Artikli u kategoriji ${categories.find(c => c.id === categoryId)?.name}` : "Svi artikli"}</h2>
331388
<div className="grid">
332389
{filteredArticles.map(article => (
333390
<div key={article.id} className="article-card" onClick={() => addItem(article)} style={{ cursor: 'pointer' }}>

frontend/src/pages/Racuni.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export default function Racuni() {
9999

100100
<input
101101
type="text"
102-
placeholder="Pretraži račune u ovoj sesiji..."
102+
placeholder="Pretraži račune u ovoj sesiji po bilo čemu (Broj, Plaćanje, Cijena, Prodavač)..."
103103
value={searchTerm}
104104
onChange={(e) => setSearchTerm(e.target.value)}
105105
style={{
@@ -123,6 +123,7 @@ export default function Racuni() {
123123
<tr>
124124
<th>Broj računa</th>
125125
<th>Vrijeme</th>
126+
<th>Prodajno mjesto</th>
126127
<th>Plaćanje</th>
127128
<th>Ukupno</th>
128129
<th>Prodavač</th>
@@ -141,6 +142,7 @@ export default function Racuni() {
141142
<td>
142143
{new Date(receipt.createdAt).toLocaleTimeString("hr-HR", {hour: '2-digit', minute: '2-digit', second: '2-digit'})}
143144
</td>
145+
<td>{receipt.prodajnoMjesto?.name || "N/A"}</td>
144146
<td>{receipt.paymentType}</td>
145147
<td><span className="currency">{parseFloat(receipt.brutto).toFixed(2)}</span></td>
146148
<td>

frontend/src/pages/admin/Racun.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ ${line}
115115
Račun br: ${order.num}
116116
Vrijeme: ${order.time}
117117
Blagajnik:${order.cashier}
118+
Lokacija: ${order.location || "N/A"}
118119
${line}
119120
${rpad("Naziv", COL_NAME)} ${lpad("Kol.", COL_QTY)} ${lpad("Cij.", COL_PRC)} ${lpad("Iznos", COL_TOT)}
120121
${line}

0 commit comments

Comments
 (0)