>2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
|
3
|
+# This file is distributed under the same license as the PACKAGE package.
|
|
|
4
|
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
|
5
|
+#
|
|
|
6
|
+msgid ""
|
|
|
7
|
+msgstr ""
|
|
|
8
|
+"Project-Id-Version: PACKAGE VERSION\n"
|
|
|
9
|
+"Report-Msgid-Bugs-To: \n"
|
|
|
10
|
+"POT-Creation-Date: 2025-09-25 22:51+0700\n"
|
|
|
11
|
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
|
|
12
|
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
|
|
13
|
+"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
|
14
|
+"Language: th\n"
|
|
|
15
|
+"MIME-Version: 1.0\n"
|
|
|
16
|
+"Content-Type: text/plain; charset=UTF-8\n"
|
|
|
17
|
+"Content-Transfer-Encoding: 8bit\n"
|
|
|
18
|
+"Plural-Forms: nplurals=1; plural=0;\n"
|
|
|
19
|
+
|
|
|
20
|
+#: admin_frontend/templates/admin_frontend/base.html:76
|
|
|
21
|
+#: translations/menu_labels.py:12
|
|
|
22
|
+msgid "Settings"
|
|
|
23
|
+msgstr "การตั้งค่า"
|
|
|
24
|
+
|
|
|
25
|
+#: admin_frontend/templates/admin_frontend/base.html:96
|
|
|
26
|
+msgid "CMS"
|
|
|
27
|
+msgstr "CMS"
|
|
|
28
|
+
|
|
|
29
|
+#: admin_frontend/templates/admin_frontend/base.html:117
|
|
|
30
|
+msgid "Frontend"
|
|
|
31
|
+msgstr "ส่วนหน้า"
|
|
|
32
|
+
|
|
|
33
|
+#: admin_frontend/templates/admin_frontend/base.html:137
|
|
|
34
|
+msgid "Organization"
|
|
|
35
|
+msgstr "องค์กร"
|
|
|
36
|
+
|
|
|
37
|
+#: admin_frontend/templates/admin_frontend/base.html:157
|
|
|
38
|
+msgid "Billing"
|
|
|
39
|
+msgstr "การเรียกเก็บเงิน"
|
|
|
40
|
+
|
|
|
41
|
+#: admin_frontend/templates/admin_frontend/base.html:177
|
|
|
42
|
+msgid "Recycle Ops"
|
|
|
43
|
+msgstr "การดำเนินงานรีไซเคิล"
|
|
|
44
|
+
|
|
|
45
|
+#: public_frontend/templates/public_frontend/base.html:17
|
|
|
46
|
+#: public_frontend/templates/public_frontend/materials_list.html:3
|
|
|
47
|
+#: translations/menu_labels.py:18
|
|
|
48
|
+msgid "Materials"
|
|
|
49
|
+msgstr "วัสดุ"
|
|
|
50
|
+
|
|
|
51
|
+#: public_frontend/templates/public_frontend/base.html:18
|
|
|
52
|
+#: public_frontend/templates/public_frontend/listings_list.html:3
|
|
|
53
|
+msgid "Listings"
|
|
|
54
|
+msgstr "รายการ"
|
|
|
55
|
+
|
|
|
56
|
+#: public_frontend/templates/public_frontend/base.html:19
|
|
|
57
|
+#: public_frontend/templates/public_frontend/pickup_request.html:3
|
|
|
58
|
+msgid "Request Pickup"
|
|
|
59
|
+msgstr "ขอรับของ"
|
|
|
60
|
+
|
|
|
61
|
+#: public_frontend/templates/public_frontend/base.html:20
|
|
|
62
|
+#: public_frontend/templates/public_frontend/blog_list.html:3
|
|
|
63
|
+msgid "Blog"
|
|
|
64
|
+msgstr "บล็อก"
|
|
|
65
|
+
|
|
|
66
|
+#: public_frontend/templates/public_frontend/base.html:21
|
|
|
67
|
+#: public_frontend/templates/public_frontend/contact.html:3
|
|
|
68
|
+msgid "Contact"
|
|
|
69
|
+msgstr "ติดต่อเรา"
|
|
|
70
|
+
|
|
|
71
|
+#: public_frontend/templates/public_frontend/base.html:53
|
|
|
72
|
+msgid "Staff Login"
|
|
|
73
|
+msgstr "เข้าสู่ระบบเจ้าหน้าที่"
|
|
|
74
|
+
|
|
|
75
|
+#: public_frontend/templates/public_frontend/blog_list.html:14
|
|
|
76
|
+msgid "No image"
|
|
|
77
|
+msgstr "ไม่มีรูปภาพ"
|
|
|
78
|
+
|
|
|
79
|
+#: public_frontend/templates/public_frontend/blog_list.html:24
|
|
|
80
|
+#: public_frontend/templates/public_frontend/home.html:92
|
|
|
81
|
+msgid "No posts yet."
|
|
|
82
|
+msgstr "ยังไม่มีโพสต์"
|
|
|
83
|
+
|
|
|
84
|
+#: public_frontend/templates/public_frontend/contact.html:5
|
|
|
85
|
+#: public_frontend/templates/public_frontend/home.html:99
|
|
|
86
|
+msgid "Contact Us"
|
|
|
87
|
+msgstr "ติดต่อเรา"
|
|
|
88
|
+
|
|
|
89
|
+#: public_frontend/templates/public_frontend/contact.html:10
|
|
|
90
|
+#: public_frontend/templates/public_frontend/home.html:104
|
|
|
91
|
+#: public_frontend/templates/public_frontend/materials_list.html:9
|
|
|
92
|
+#: public_frontend/templates/public_frontend/pickup_request.html:38
|
|
|
93
|
+msgid "Name"
|
|
|
94
|
+msgstr "ชื่อ"
|
|
|
95
|
+
|
|
|
96
|
+#: public_frontend/templates/public_frontend/contact.html:14
|
|
|
97
|
+#: public_frontend/templates/public_frontend/home.html:108
|
|
|
98
|
+#: public_frontend/templates/public_frontend/pickup_request.html:40
|
|
|
99
|
+msgid "Email"
|
|
|
100
|
+msgstr "อีเมล"
|
|
|
101
|
+
|
|
|
102
|
+#: public_frontend/templates/public_frontend/contact.html:20
|
|
|
103
|
+#: public_frontend/templates/public_frontend/pickup_request.html:41
|
|
|
104
|
+msgid "Phone"
|
|
|
105
|
+msgstr "โทรศัพท์"
|
|
|
106
|
+
|
|
|
107
|
+#: public_frontend/templates/public_frontend/contact.html:24
|
|
|
108
|
+#: public_frontend/templates/public_frontend/home.html:113
|
|
|
109
|
+msgid "Subject"
|
|
|
110
|
+msgstr "หัวข้อ"
|
|
|
111
|
+
|
|
|
112
|
+#: public_frontend/templates/public_frontend/contact.html:29
|
|
|
113
|
+#: public_frontend/templates/public_frontend/home.html:117
|
|
|
114
|
+msgid "Message"
|
|
|
115
|
+msgstr "ข้อความ"
|
|
|
116
|
+
|
|
|
117
|
+#: public_frontend/templates/public_frontend/contact.html:33
|
|
|
118
|
+msgid "Send"
|
|
|
119
|
+msgstr "ส่ง"
|
|
|
120
|
+
|
|
|
121
|
+#: public_frontend/templates/public_frontend/home.html:3
|
|
|
122
|
+msgid "Home"
|
|
|
123
|
+msgstr "หน้าแรก"
|
|
|
124
|
+
|
|
|
125
|
+#: public_frontend/templates/public_frontend/home.html:9
|
|
|
126
|
+msgid "Streamline Your Factory's Recycling"
|
|
|
127
|
+msgstr "ปรับปรุงการรีไซเคิลของโรงงานให้มีประสิทธิภาพ"
|
|
|
128
|
+
|
|
|
129
|
+#: public_frontend/templates/public_frontend/home.html:10
|
|
|
130
|
+msgid ""
|
|
|
131
|
+"We partner with businesses like yours to manage scrap materials efficiently. "
|
|
|
132
|
+"Browse listings, request a pickup, and turn your waste into a resource."
|
|
|
133
|
+msgstr "เราร่วมมือกับธุรกิจเช่นคุณเพื่อจัดการเศษวัสดุอย่างมีประสิทธิภาพ เรียกดูรายการ ขอรับของ และเปลี่ยนของเสียให้เป็นทรัพยากร"
|
|
|
134
|
+
|
|
|
135
|
+#: public_frontend/templates/public_frontend/home.html:12
|
|
|
136
|
+#: public_frontend/templates/public_frontend/pickup_request.html:6
|
|
|
137
|
+msgid "Request a Pickup"
|
|
|
138
|
+msgstr "ขอรับของ"
|
|
|
139
|
+
|
|
|
140
|
+#: public_frontend/templates/public_frontend/home.html:13
|
|
|
141
|
+msgid "Browse Listings"
|
|
|
142
|
+msgstr "ดูรายการ"
|
|
|
143
|
+
|
|
|
144
|
+#: public_frontend/templates/public_frontend/home.html:21
|
|
|
145
|
+msgid "Our Services"
|
|
|
146
|
+msgstr "บริการของเรา"
|
|
|
147
|
+
|
|
|
148
|
+#: public_frontend/templates/public_frontend/home.html:40
|
|
|
149
|
+msgid "Materials We Accept"
|
|
|
150
|
+msgstr "วัสดุที่รับ"
|
|
|
151
|
+
|
|
|
152
|
+#: public_frontend/templates/public_frontend/home.html:41
|
|
|
153
|
+#: public_frontend/templates/public_frontend/home.html:82
|
|
|
154
|
+msgid "View all"
|
|
|
155
|
+msgstr "ดูทั้งหมด"
|
|
|
156
|
+
|
|
|
157
|
+#: public_frontend/templates/public_frontend/home.html:51
|
|
|
158
|
+#: public_frontend/templates/public_frontend/materials_list.html:22
|
|
|
159
|
+msgid "No materials published."
|
|
|
160
|
+msgstr "ยังไม่มีวัสดุเผยแพร่"
|
|
|
161
|
+
|
|
|
162
|
+#: public_frontend/templates/public_frontend/home.html:60
|
|
|
163
|
+msgid "Open Listings"
|
|
|
164
|
+msgstr "รายการที่เปิดอยู่"
|
|
|
165
|
+
|
|
|
166
|
+#: public_frontend/templates/public_frontend/home.html:61
|
|
|
167
|
+msgid "Browse all"
|
|
|
168
|
+msgstr "ดูทั้งหมด"
|
|
|
169
|
+
|
|
|
170
|
+#: public_frontend/templates/public_frontend/home.html:66
|
|
|
171
|
+#: public_frontend/templates/public_frontend/listing_detail.html:7
|
|
|
172
|
+#: public_frontend/templates/public_frontend/listings_list.html:8
|
|
|
173
|
+msgid "Ends"
|
|
|
174
|
+msgstr "สิ้นสุด"
|
|
|
175
|
+
|
|
|
176
|
+#: public_frontend/templates/public_frontend/home.html:71
|
|
|
177
|
+msgid "No public listings at the moment."
|
|
|
178
|
+msgstr "ขณะนี้ยังไม่มีรายการสาธารณะ"
|
|
|
179
|
+
|
|
|
180
|
+#: public_frontend/templates/public_frontend/home.html:81
|
|
|
181
|
+msgid "Industry Insights"
|
|
|
182
|
+msgstr "ข้อมูลเชิงลึกอุตสาหกรรม"
|
|
|
183
|
+
|
|
|
184
|
+#: public_frontend/templates/public_frontend/home.html:121
|
|
|
185
|
+msgid "Send Message"
|
|
|
186
|
+msgstr "ส่งข้อความ"
|
|
|
187
|
+
|
|
|
188
|
+#: public_frontend/templates/public_frontend/listing_detail.html:3
|
|
|
189
|
+msgid "Listing"
|
|
|
190
|
+msgstr "รายการ"
|
|
|
191
|
+
|
|
|
192
|
+#: public_frontend/templates/public_frontend/listing_detail.html:7
|
|
|
193
|
+msgid "Status"
|
|
|
194
|
+msgstr "สถานะ"
|
|
|
195
|
+
|
|
|
196
|
+#: public_frontend/templates/public_frontend/listing_detail.html:9
|
|
|
197
|
+msgid "Items"
|
|
|
198
|
+msgstr "รายการ"
|
|
|
199
|
+
|
|
|
200
|
+#: public_frontend/templates/public_frontend/listing_detail.html:14
|
|
|
201
|
+msgid "No items listed"
|
|
|
202
|
+msgstr "ยังไม่มีรายการ"
|
|
|
203
|
+
|
|
|
204
|
+#: public_frontend/templates/public_frontend/listings_list.html:13
|
|
|
205
|
+msgid "No public listings."
|
|
|
206
|
+msgstr "ยังไม่มีรายการสาธารณะ"
|
|
|
207
|
+
|
|
|
208
|
+#: public_frontend/templates/public_frontend/materials_list.html:10
|
|
|
209
|
+msgid "Category"
|
|
|
210
|
+msgstr "หมวดหมู่"
|
|
|
211
|
+
|
|
|
212
|
+#: public_frontend/templates/public_frontend/materials_list.html:11
|
|
|
213
|
+msgid "Default Unit"
|
|
|
214
|
+msgstr "หน่วยเริ่มต้น"
|
|
|
215
|
+
|
|
|
216
|
+#: public_frontend/templates/public_frontend/pickup_request.html:36
|
|
|
217
|
+msgid "Review Your Pickup Request"
|
|
|
218
|
+msgstr "ตรวจสอบคำขอรับของของคุณ"
|
|
|
219
|
+
|
|
|
220
|
+#: public_frontend/templates/public_frontend/pickup_request.html:43
|
|
|
221
|
+msgid "Pickup Address"
|
|
|
222
|
+msgstr "ที่อยู่รับของ"
|
|
|
223
|
+
|
|
|
224
|
+#: public_frontend/templates/public_frontend/pickup_request.html:45
|
|
|
225
|
+msgid "Preferred Date/Time"
|
|
|
226
|
+msgstr "วันที่/เวลา ที่ต้องการ"
|
|
|
227
|
+
|
|
|
228
|
+#: public_frontend/templates/public_frontend/pickup_request.html:47
|
|
|
229
|
+msgid "Materials / Notes"
|
|
|
230
|
+msgstr "วัสดุ / หมายเหตุ"
|
|
|
231
|
+
|
|
|
232
|
+#: public_frontend/templates/public_frontend/pickup_request.html:49
|
|
|
233
|
+msgid "Photos"
|
|
|
234
|
+msgstr "รูปภาพ"
|
|
|
235
|
+
|
|
|
236
|
+#: public_frontend/templates/public_frontend/pickup_request.html:51
|
|
|
237
|
+msgid "No files attached."
|
|
|
238
|
+msgstr "ไม่มีไฟล์แนบ"
|
|
|
239
|
+
|
|
|
240
|
+#: public_frontend/templates/public_frontend/pickup_request.html:57
|
|
|
241
|
+msgid "Edit"
|
|
|
242
|
+msgstr "แก้ไข"
|
|
|
243
|
+
|
|
|
244
|
+#: public_frontend/templates/public_frontend/pickup_request.html:58
|
|
|
245
|
+msgid "Confirm & Send"
|
|
|
246
|
+msgstr "ยืนยันและส่ง"
|
|
|
247
|
+
|
|
|
248
|
+#: public_frontend/templates/public_frontend/pickup_request.html:62
|
|
|
249
|
+msgid "close"
|
|
|
250
|
+msgstr "ปิด"
|
|
|
251
|
+
|
|
|
252
|
+#: public_frontend/templates/public_frontend/service_detail.html:29
|
|
|
253
|
+msgid "Back to Home"
|
|
|
254
|
+msgstr "กลับสู่หน้าแรก"
|
|
|
255
|
+
|
|
|
256
|
+#: translations/menu_labels.py:4
|
|
|
257
|
+msgid "Dashboard"
|
|
|
258
|
+msgstr "แดชบอร์ด"
|
|
|
259
|
+
|
|
|
260
|
+#: translations/menu_labels.py:5
|
|
|
261
|
+msgid "Profiles"
|
|
|
262
|
+msgstr "โปรไฟล์"
|
|
|
263
|
+
|
|
|
264
|
+#: translations/menu_labels.py:6
|
|
|
265
|
+msgid "Opportunities"
|
|
|
266
|
+msgstr "โอกาส"
|
|
|
267
|
+
|
|
|
268
|
+#: translations/menu_labels.py:7
|
|
|
269
|
+msgid "Intro Requests"
|
|
|
270
|
+msgstr "คำขอแนะนำ"
|
|
|
271
|
+
|
|
|
272
|
+#: translations/menu_labels.py:8
|
|
|
273
|
+msgid "Leaderboard"
|
|
|
274
|
+msgstr "ตารางจัดอันดับ"
|
|
|
275
|
+
|
|
|
276
|
+#: translations/menu_labels.py:9
|
|
|
277
|
+msgid "Leads"
|
|
|
278
|
+msgstr "ลีด"
|
|
|
279
|
+
|
|
|
280
|
+#: translations/menu_labels.py:10
|
|
|
281
|
+msgid "CMS Posts"
|
|
|
282
|
+msgstr "โพสต์ CMS"
|
|
|
283
|
+
|
|
|
284
|
+#: translations/menu_labels.py:11
|
|
|
285
|
+msgid "CMS Categories"
|
|
|
286
|
+msgstr "หมวดหมู่ CMS"
|
|
|
287
|
+
|
|
|
288
|
+#: translations/menu_labels.py:13
|
|
|
289
|
+msgid "Advanced Settings"
|
|
|
290
|
+msgstr "การตั้งค่าขั้นสูง"
|
|
|
291
|
+
|
|
|
292
|
+#: translations/menu_labels.py:14
|
|
|
293
|
+msgid "Organization Settings"
|
|
|
294
|
+msgstr "การตั้งค่าองค์กร"
|
|
|
295
|
+
|
|
|
296
|
+#: translations/menu_labels.py:15
|
|
|
297
|
+msgid "Organization Sites"
|
|
|
298
|
+msgstr "ไซต์ขององค์กร"
|
|
|
299
|
+
|
|
|
300
|
+#: translations/menu_labels.py:16
|
|
|
301
|
+msgid "Permissions"
|
|
|
302
|
+msgstr "สิทธิ์การใช้งาน"
|
|
|
303
|
+
|
|
|
304
|
+#: translations/menu_labels.py:17
|
|
|
305
|
+msgid "Users"
|
|
|
306
|
+msgstr "ผู้ใช้"
|
|
|
307
|
+
|
|
|
308
|
+#: translations/menu_labels.py:19
|
|
|
309
|
+msgid "Customers"
|
|
|
310
|
+msgstr "ลูกค้า"
|
|
|
311
|
+
|
|
|
312
|
+#: translations/menu_labels.py:20
|
|
|
313
|
+msgid "Pickups"
|
|
|
314
|
+msgstr "การรับของ"
|
|
|
315
|
+
|
|
|
316
|
+#: translations/menu_labels.py:21
|
|
|
317
|
+msgid "Weigh Tickets"
|
|
|
318
|
+msgstr "ใบชั่งน้ำหนัก"
|
|
|
319
|
+
|
|
|
320
|
+#: translations/menu_labels.py:22
|
|
|
321
|
+msgid "Documents"
|
|
|
322
|
+msgstr "เอกสาร"
|
|
|
323
|
+
|
|
|
324
|
+#: translations/menu_labels.py:23
|
|
|
325
|
+msgid "Provided Services"
|
|
|
326
|
+msgstr "บริการที่ให้"
|
|
|
327
|
+
|
|
|
328
|
+#: translations/menu_labels.py:24
|
|
|
329
|
+msgid "Scrap Listings"
|
|
|
330
|
+msgstr "รายการเศษวัสดุ"
|
|
|
331
|
+
|
|
|
332
|
+#: translations/menu_labels.py:25
|
|
|
333
|
+msgid "Invoices"
|
|
|
334
|
+msgstr "ใบแจ้งหนี้"
|
|
|
335
|
+
|
|
|
336
|
+#: translations/menu_labels.py:26
|
|
|
337
|
+msgid "Payouts"
|
|
|
338
|
+msgstr "การจ่ายเงิน"
|
|
|
339
|
+
|
|
|
340
|
+#: recycle_core/models.py
|
|
|
341
|
+msgid "Pickup & Logistics"
|
|
|
342
|
+msgstr "การรับของและโลจิสติกส์"
|
|
|
343
|
+
|
|
|
344
|
+#: recycle_core/models.py
|
|
|
345
|
+msgid "Material Sorting"
|
|
|
346
|
+msgstr "การคัดแยกวัสดุ"
|
|
|
347
|
+
|
|
|
348
|
+#: recycle_core/models.py
|
|
|
349
|
+msgid "Weighing & Ticketing"
|
|
|
350
|
+msgstr "การชั่งน้ำหนักและออกบัตรชั่ง"
|
|
|
351
|
+
|
|
|
352
|
+#: recycle_core/models.py
|
|
|
353
|
+msgid "Invoicing & Payouts"
|
|
|
354
|
+msgstr "การวางบิลและการจ่ายเงิน"
|
|
|
355
|
+
|
|
|
356
|
+#: recycle_core/models.py
|
|
|
357
|
+msgid "Reporting & Analytics"
|
|
|
358
|
+msgstr "การรายงานและการวิเคราะห์"
|
|
|
359
|
+
|
|
|
360
|
+#: recycle_core/models.py
|
|
|
361
|
+msgid "Marketplace & Bidding"
|
|
|
362
|
+msgstr "มาร์เก็ตเพลสและการประมูล"
|
|
|
363
|
+
|
|
|
364
|
+#: recycle_core/models.py
|
|
|
365
|
+msgid "Compliance & Audits"
|
|
|
366
|
+msgstr "การปฏิบัติตามและการตรวจสอบ"
|
|
|
367
|
+
|
|
|
368
|
+#: recycle_core/models.py
|
|
|
369
|
+msgid "Consulting & Training"
|
|
|
370
|
+msgstr "ที่ปรึกษาและการฝึกอบรม"
|
|
|
@@ -14,4 +14,22 @@ class OrgsConfig(AppConfig):
|
|
14
|
14
|
except Exception:
|
|
15
|
15
|
# Avoid startup hard-fail if migrations/apps not ready; re-raise only in debug if needed
|
|
16
|
16
|
raise
|
|
|
17
|
+
|
|
|
18
|
+ # Monkey-patch User.__str__ to show "<username>, <fullname>"
|
|
|
19
|
+ # This affects labels in forms and general stringification.
|
|
|
20
|
+ try:
|
|
|
21
|
+ from django.contrib.auth import get_user_model
|
|
|
22
|
+
|
|
|
23
|
+ User = get_user_model()
|
|
|
24
|
+
|
|
|
25
|
+ def _user_str(self): # type: ignore[override]
|
|
|
26
|
+ full = self.get_full_name().strip()
|
|
|
27
|
+ return f"{self.username}, {full}" if full else f"{self.username}"
|
|
|
28
|
+
|
|
|
29
|
+ # Assign only if not already customized
|
|
|
30
|
+ if getattr(User.__str__, "__name__", None) != "_user_str": # type: ignore[attr-defined]
|
|
|
31
|
+ User.__str__ = _user_str # type: ignore[assignment]
|
|
|
32
|
+ except Exception:
|
|
|
33
|
+ # Do not block app startup if something goes wrong
|
|
|
34
|
+ pass
|
|
17
|
35
|
return super().ready()
|
|
|
@@ -165,7 +165,8 @@ def permissions_edit_group(request, pk: int):
|
|
165
|
165
|
final_ids = [pid for pid in sel_ids if pid in allowed_ids]
|
|
166
|
166
|
group.permissions.set(Permission.objects.filter(id__in=final_ids))
|
|
167
|
167
|
messages.success(request, f"Permissions updated for group '{group.name}'.")
|
|
168
|
|
- return redirect("orgs_admin:permissions_overview")
|
|
|
168
|
+ # Redirect back to the current URL (stay on the edit page)
|
|
|
169
|
+ return redirect(request.get_full_path())
|
|
169
|
170
|
|
|
170
|
171
|
current_ids = set(group.permissions.values_list("id", flat=True))
|
|
171
|
172
|
|
|
|
@@ -1,6 +1,7 @@
|
|
1
|
|
-{% load tailwind_tags static %}
|
|
|
1
|
+{% load tailwind_tags static i18n %}
|
|
2
|
2
|
<!DOCTYPE html>
|
|
3
|
|
-<html lang="en">
|
|
|
3
|
+{% get_current_language as LANGUAGE_CODE %}
|
|
|
4
|
+<html lang="{{ LANGUAGE_CODE }}">
|
|
4
|
5
|
<head>
|
|
5
|
6
|
<meta charset="utf-8" />
|
|
6
|
7
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
@@ -13,12 +14,25 @@
|
|
13
|
14
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 flex items-center justify-between h-16">
|
|
14
|
15
|
<a href="/" class="text-lg font-semibold">{% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</a>
|
|
15
|
16
|
<nav class="hidden md:flex items-center gap-6">
|
|
16
|
|
- <a href="/#materials" class="hover:text-black/70">Materials</a>
|
|
17
|
|
- <a href="/#listings" class="hover:text-black/70">Listings</a>
|
|
18
|
|
- <a href="/#pickup-request" class="hover:text-black/70">Request Pickup</a>
|
|
19
|
|
- <a href="/#blog" class="hover:text-black/70">Blog</a>
|
|
20
|
|
- <a href="/#contact" class="hover:text-black/70">Contact</a>
|
|
|
17
|
+ <a href="/#materials" class="hover:text-black/70">{% trans "Materials" %}</a>
|
|
|
18
|
+ <a href="/#listings" class="hover:text-black/70">{% trans "Listings" %}</a>
|
|
|
19
|
+ <a href="/#pickup-request" class="hover:text-black/70">{% trans "Request Pickup" %}</a>
|
|
|
20
|
+ <a href="/#blog" class="hover:text-black/70">{% trans "Blog" %}</a>
|
|
|
21
|
+ <a href="/#contact" class="hover:text-black/70">{% trans "Contact" %}</a>
|
|
21
|
22
|
</nav>
|
|
|
23
|
+ <form method="post" action="{% url 'set_language' %}" class="hidden md:block ml-4">
|
|
|
24
|
+ {% csrf_token %}
|
|
|
25
|
+ <input type="hidden" name="next" value="{{ request.get_full_path }}" />
|
|
|
26
|
+ {% get_available_languages as langs %}
|
|
|
27
|
+ {% get_language_info_list for langs as languages %}
|
|
|
28
|
+ <label for="lang" class="sr-only">Language</label>
|
|
|
29
|
+ <select id="lang" name="language" class="text-sm border border-gray-300 rounded px-2 py-1 bg-white"
|
|
|
30
|
+ onchange="this.form.submit()">
|
|
|
31
|
+ {% for lang in languages %}
|
|
|
32
|
+ <option value="{{ lang.code }}" {% if lang.code == LANGUAGE_CODE %}selected{% endif %}>{{ lang.name_local }}</option>
|
|
|
33
|
+ {% endfor %}
|
|
|
34
|
+ </select>
|
|
|
35
|
+ </form>
|
|
22
|
36
|
</div>
|
|
23
|
37
|
</header>
|
|
24
|
38
|
<main class="mx-auto max-w-7xl p-4 sm:p-6 lg:p-8 min-h-[80vh]">
|
|
|
@@ -36,7 +50,7 @@
|
|
36
|
50
|
<footer class="bg-white border-t">
|
|
37
|
51
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between text-sm text-gray-600">
|
|
38
|
52
|
<span>© {% now "Y" %} {% if current_site %}{{ current_site.name }}{% else %}Ecoloop{% endif %}</span>
|
|
39
|
|
- <a href="/webadmin/" class="hover:underline">Staff Login</a>
|
|
|
53
|
+ <a href="/webadmin/" class="hover:underline">{% trans "Staff Login" %}</a>
|
|
40
|
54
|
</div>
|
|
41
|
55
|
</footer>
|
|
42
|
56
|
</body>
|
|
|
@@ -1,4 +1,5 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
|
2
|
+{% load i18n %}
|
|
2
|
3
|
{% block title %}{{ post.title }}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
5
|
<article class="blog-article max-w-none bg-white rounded shadow p-6">
|
|
|
@@ -1,5 +1,6 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Blog{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Blog" %}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
5
|
<div class="grid gap-4">
|
|
5
|
6
|
{% for p in posts %}
|
|
|
@@ -10,7 +11,7 @@
|
|
10
|
11
|
<img src="{{ p.feature_image.url }}" alt="{{ p.title }}" loading="lazy">
|
|
11
|
12
|
</div>
|
|
12
|
13
|
{% else %}
|
|
13
|
|
- <div class="thumb placeholder">No image</div>
|
|
|
14
|
+ <div class="thumb placeholder">{% trans "No image" %}</div>
|
|
14
|
15
|
{% endif %}
|
|
15
|
16
|
<div class="min-w-0">
|
|
16
|
17
|
<div class="text-sm text-gray-500">{{ p.published_at|date:'M j, Y' }}</div>
|
|
|
@@ -20,7 +21,7 @@
|
|
20
|
21
|
</div>
|
|
21
|
22
|
</a>
|
|
22
|
23
|
{% empty %}
|
|
23
|
|
- <div class="text-gray-500">No posts yet.</div>
|
|
|
24
|
+ <div class="text-gray-500">{% trans "No posts yet." %}</div>
|
|
24
|
25
|
{% endfor %}
|
|
25
|
26
|
</div>
|
|
26
|
27
|
<div class="mt-4">{% include 'admin_frontend/_pagination.html' %}</div>
|
|
|
@@ -1,35 +1,36 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Contact{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Contact" %}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
|
-<h1 class="text-xl font-semibold mb-4">Contact Us{% if org %} — {{ org.name }}{% endif %}</h1>
|
|
|
5
|
+<h1 class="text-xl font-semibold mb-4">{% trans "Contact Us" %}{% if org %} — {{ org.name }}{% endif %}</h1>
|
|
5
|
6
|
<form method="post" class="bg-white rounded shadow p-4 grid gap-4">
|
|
6
|
7
|
{% csrf_token %}
|
|
7
|
8
|
<div class="grid md:grid-cols-2 gap-4">
|
|
8
|
9
|
<div>
|
|
9
|
|
- <label class="block text-sm font-medium mb-1">Name</label>
|
|
|
10
|
+ <label class="block text-sm font-medium mb-1">{% trans "Name" %}</label>
|
|
10
|
11
|
<input type="text" name="name" value="{{ form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
|
|
11
|
12
|
</div>
|
|
12
|
13
|
<div>
|
|
13
|
|
- <label class="block text-sm font-medium mb-1">Email</label>
|
|
|
14
|
+ <label class="block text-sm font-medium mb-1">{% trans "Email" %}</label>
|
|
14
|
15
|
<input type="email" name="email" value="{{ form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
|
|
15
|
16
|
</div>
|
|
16
|
17
|
</div>
|
|
17
|
18
|
<div class="grid md:grid-cols-2 gap-4">
|
|
18
|
19
|
<div>
|
|
19
|
|
- <label class="block text-sm font-medium mb-1">Phone</label>
|
|
|
20
|
+ <label class="block text-sm font-medium mb-1">{% trans "Phone" %}</label>
|
|
20
|
21
|
<input type="text" name="phone" value="{{ form.phone.value|default:'' }}" class="w-full border rounded px-3 py-2">
|
|
21
|
22
|
</div>
|
|
22
|
23
|
<div>
|
|
23
|
|
- <label class="block text-sm font-medium mb-1">Subject</label>
|
|
|
24
|
+ <label class="block text-sm font-medium mb-1">{% trans "Subject" %}</label>
|
|
24
|
25
|
<input type="text" name="subject" value="{{ form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
|
|
25
|
26
|
</div>
|
|
26
|
27
|
</div>
|
|
27
|
28
|
<div>
|
|
28
|
|
- <label class="block text-sm font-medium mb-1">Message</label>
|
|
|
29
|
+ <label class="block text-sm font-medium mb-1">{% trans "Message" %}</label>
|
|
29
|
30
|
<textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ form.message.value|default:'' }}</textarea>
|
|
30
|
31
|
</div>
|
|
31
|
32
|
<div>
|
|
32
|
|
- <button class="btn-primary" type="submit">Send</button>
|
|
|
33
|
+ <button class="btn-primary" type="submit">{% trans "Send" %}</button>
|
|
33
|
34
|
</div>
|
|
34
|
35
|
</form>
|
|
35
|
36
|
<style>.btn-primary{background:#1d4ed8;color:white;padding:.5rem .75rem;border-radius:.375rem}</style>
|
|
|
@@ -1,16 +1,16 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% load static %}
|
|
3
|
|
-{% block title %}Home{% endblock %}
|
|
|
2
|
+{% load static i18n %}
|
|
|
3
|
+{% block title %}{% trans "Home" %}{% endblock %}
|
|
4
|
4
|
|
|
5
|
5
|
{% block content %}
|
|
6
|
6
|
<div class="grid gap-12 md:gap-16">
|
|
7
|
7
|
{# Hero Section #}
|
|
8
|
8
|
<section id="hero" class="bg-white rounded-lg shadow-md p-6 md:p-8 text-center">
|
|
9
|
|
- <h1 class="text-3xl md:text-4xl font-bold mb-2">Streamline Your Factory's Recycling</h1>
|
|
10
|
|
- <p class="text-gray-600 max-w-2xl mx-auto mb-6">We partner with businesses like yours to manage scrap materials efficiently. Browse listings, request a pickup, and turn your waste into a resource.</p>
|
|
|
9
|
+ <h1 class="text-3xl md:text-4xl font-bold mb-2">{% trans "Streamline Your Factory's Recycling" %}</h1>
|
|
|
10
|
+ <p class="text-gray-600 max-w-2xl mx-auto mb-6">{% trans "We partner with businesses like yours to manage scrap materials efficiently. Browse listings, request a pickup, and turn your waste into a resource." %}</p>
|
|
11
|
11
|
<div class="flex gap-3 justify-center">
|
|
12
|
|
- <a href="{% url 'public_frontend:pickup_request' %}" class="btn-primary">Request a Pickup</a>
|
|
13
|
|
- <a href="#listings" class="btn-outline">Browse Listings</a>
|
|
|
12
|
+ <a href="{% url 'public_frontend:pickup_request' %}" class="btn-primary">{% trans "Request a Pickup" %}</a>
|
|
|
13
|
+ <a href="#listings" class="btn-outline">{% trans "Browse Listings" %}</a>
|
|
14
|
14
|
</div>
|
|
15
|
15
|
</section>
|
|
16
|
16
|
|
|
|
@@ -18,14 +18,14 @@
|
|
18
|
18
|
{% if services %}
|
|
19
|
19
|
<section id="services">
|
|
20
|
20
|
<div class="flex items-center justify-between mb-3">
|
|
21
|
|
- <h2 class="text-2xl font-semibold">Our Services</h2>
|
|
|
21
|
+ <h2 class="text-2xl font-semibold">{% trans "Our Services" %}</h2>
|
|
22
|
22
|
</div>
|
|
23
|
23
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
|
24
|
24
|
{% for s in services %}
|
|
25
|
25
|
<a href="{% url 'public_frontend:service_detail' s.pk %}" class="block p-4 bg-white rounded-lg shadow-md hover:bg-gray-50 transition flex items-start gap-4">
|
|
26
|
26
|
<img src="{% static 'service_icons/' %}{{ s.image_name }}" alt="{{ s.title }} icon" class="w-12 h-12 object-contain"/>
|
|
27
|
27
|
<div>
|
|
28
|
|
- <div class="font-medium">{{ s.title }}</div>
|
|
|
28
|
+ <div class="font-medium">{{ s.get_title_display }}</div>
|
|
29
|
29
|
<div class="text-sm text-gray-600">{{ s.description }}</div>
|
|
30
|
30
|
</div>
|
|
31
|
31
|
</a>
|
|
|
@@ -37,8 +37,8 @@
|
|
37
|
37
|
{# Materials Section #}
|
|
38
|
38
|
<section id="materials">
|
|
39
|
39
|
<div class="flex items-center justify-between mb-3">
|
|
40
|
|
- <h2 class="text-2xl font-semibold">Materials We Accept</h2>
|
|
41
|
|
- <a href="{% url 'public_frontend:materials_list' %}" class="text-blue-700 hover:underline">View all</a>
|
|
|
40
|
+ <h2 class="text-2xl font-semibold">{% trans "Materials We Accept" %}</h2>
|
|
|
41
|
+ <a href="{% url 'public_frontend:materials_list' %}" class="text-blue-700 hover:underline">{% trans "View all" %}</a>
|
|
42
|
42
|
</div>
|
|
43
|
43
|
<div class="bg-white rounded-lg shadow-md p-4">
|
|
44
|
44
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
@@ -48,7 +48,7 @@
|
|
48
|
48
|
<p class="text-sm text-gray-500">{{ m.category.name }}</p>
|
|
49
|
49
|
</div>
|
|
50
|
50
|
{% empty %}
|
|
51
|
|
- <p class="text-gray-500 col-span-full">No materials published.</p>
|
|
|
51
|
+ <p class="text-gray-500 col-span-full">{% trans "No materials published." %}</p>
|
|
52
|
52
|
{% endfor %}
|
|
53
|
53
|
</div>
|
|
54
|
54
|
</div>
|
|
|
@@ -57,18 +57,18 @@
|
|
57
|
57
|
{# Open Listings Section #}
|
|
58
|
58
|
<section id="listings">
|
|
59
|
59
|
<div class="flex items-center justify-between mb-3">
|
|
60
|
|
- <h2 class="text-2xl font-semibold">Open Listings</h2>
|
|
61
|
|
- <a href="{% url 'public_frontend:listings_list' %}" class="text-blue-700 hover:underline">Browse all</a>
|
|
|
60
|
+ <h2 class="text-2xl font-semibold">{% trans "Open Listings" %}</h2>
|
|
|
61
|
+ <a href="{% url 'public_frontend:listings_list' %}" class="text-blue-700 hover:underline">{% trans "Browse all" %}</a>
|
|
62
|
62
|
</div>
|
|
63
|
63
|
<div class="grid md:grid-cols-3 gap-4">
|
|
64
|
64
|
{% for l in listings %}
|
|
65
|
65
|
<a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50 transition">
|
|
66
|
|
- <div class="text-sm text-gray-500">Ends {{ l.ends_at|default:'—' }}</div>
|
|
|
66
|
+ <div class="text-sm text-gray-500">{% trans "Ends" %} {{ l.ends_at|default:'—' }}</div>
|
|
67
|
67
|
<div class="font-medium">{{ l.title }}</div>
|
|
68
|
68
|
<div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:120 }}</div>
|
|
69
|
69
|
</a>
|
|
70
|
70
|
{% empty %}
|
|
71
|
|
- <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">No public listings at the moment.</div>
|
|
|
71
|
+ <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">{% trans "No public listings at the moment." %}</div>
|
|
72
|
72
|
{% endfor %}
|
|
73
|
73
|
</div>
|
|
74
|
74
|
</section>
|
|
|
@@ -78,8 +78,8 @@
|
|
78
|
78
|
{# Blog Section #}
|
|
79
|
79
|
<section id="blog">
|
|
80
|
80
|
<div class="flex items-center justify-between mb-3">
|
|
81
|
|
- <h2 class="text-2xl font-semibold">Industry Insights</h2>
|
|
82
|
|
- <a href="{% url 'public_frontend:blog_list' %}" class="text-blue-700 hover:underline">View all</a>
|
|
|
81
|
+ <h2 class="text-2xl font-semibold">{% trans "Industry Insights" %}</h2>
|
|
|
82
|
+ <a href="{% url 'public_frontend:blog_list' %}" class="text-blue-700 hover:underline">{% trans "View all" %}</a>
|
|
83
|
83
|
</div>
|
|
84
|
84
|
<div class="grid md:grid-cols-3 gap-4">
|
|
85
|
85
|
{% for p in posts %}
|
|
|
@@ -89,36 +89,36 @@
|
|
89
|
89
|
<div class="text-sm text-gray-600 line-clamp-2">{{ p.excerpt|default:p.content|striptags|truncatechars:120 }}</div>
|
|
90
|
90
|
</a>
|
|
91
|
91
|
{% empty %}
|
|
92
|
|
- <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">No posts yet.</div>
|
|
|
92
|
+ <div class="text-gray-500 p-4 bg-white rounded-lg shadow-md col-span-full">{% trans "No posts yet." %}</div>
|
|
93
|
93
|
{% endfor %}
|
|
94
|
94
|
</div>
|
|
95
|
95
|
</section>
|
|
96
|
96
|
|
|
97
|
97
|
{# Contact Section #}
|
|
98
|
98
|
<section id="contact">
|
|
99
|
|
- <h2 class="text-2xl font-semibold mb-3">Contact Us</h2>
|
|
|
99
|
+ <h2 class="text-2xl font-semibold mb-3">{% trans "Contact Us" %}</h2>
|
|
100
|
100
|
<form method="post" action="{% url 'public_frontend:contact' %}" class="bg-white rounded-lg shadow-md p-4 grid gap-4">
|
|
101
|
101
|
{% csrf_token %}
|
|
102
|
102
|
<div class="grid md:grid-cols-2 gap-4">
|
|
103
|
103
|
<div>
|
|
104
|
|
- <label class="block text-sm font-medium mb-1">Name</label>
|
|
|
104
|
+ <label class="block text-sm font-medium mb-1">{% trans "Name" %}</label>
|
|
105
|
105
|
<input type="text" name="name" value="{{ contact_form.name.value|default:'' }}" required class="w-full border rounded px-3 py-2">
|
|
106
|
106
|
</div>
|
|
107
|
107
|
<div>
|
|
108
|
|
- <label class="block text-sm font-medium mb-1">Email</label>
|
|
|
108
|
+ <label class="block text-sm font-medium mb-1">{% trans "Email" %}</label>
|
|
109
|
109
|
<input type="email" name="email" value="{{ contact_form.email.value|default:'' }}" required class="w-full border rounded px-3 py-2">
|
|
110
|
110
|
</div>
|
|
111
|
111
|
</div>
|
|
112
|
112
|
<div>
|
|
113
|
|
- <label class="block text-sm font-medium mb-1">Subject</label>
|
|
|
113
|
+ <label class="block text-sm font-medium mb-1">{% trans "Subject" %}</label>
|
|
114
|
114
|
<input type="text" name="subject" value="{{ contact_form.subject.value|default:'' }}" class="w-full border rounded px-3 py-2">
|
|
115
|
115
|
</div>
|
|
116
|
116
|
<div>
|
|
117
|
|
- <label class="block text-sm font-medium mb-1">Message</label>
|
|
|
117
|
+ <label class="block text-sm font-medium mb-1">{% trans "Message" %}</label>
|
|
118
|
118
|
<textarea name="message" rows="5" class="w-full border rounded px-3 py-2">{{ contact_form.message.value|default:'' }}</textarea>
|
|
119
|
119
|
</div>
|
|
120
|
120
|
<div>
|
|
121
|
|
- <button class="btn-primary" type="submit">Send Message</button>
|
|
|
121
|
+ <button class="btn-primary" type="submit">{% trans "Send Message" %}</button>
|
|
122
|
122
|
</div>
|
|
123
|
123
|
</form>
|
|
124
|
124
|
</section>
|
|
|
@@ -1,16 +1,17 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Listing {{ listing.id }}{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Listing" %} {{ listing.id }}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
5
|
<article class="bg-white rounded shadow p-6">
|
|
5
|
6
|
<h1 class="text-2xl font-semibold mb-1">{{ listing.title }}</h1>
|
|
6
|
|
- <div class="text-sm text-gray-500 mb-4">Status: {{ listing.status }}{% if listing.ends_at %} • Ends {{ listing.ends_at }}{% endif %}</div>
|
|
|
7
|
+ <div class="text-sm text-gray-500 mb-4">{% trans "Status" %}: {{ listing.status }}{% if listing.ends_at %} • {% trans "Ends" %} {{ listing.ends_at }}{% endif %}</div>
|
|
7
|
8
|
<p class="prose max-w-none">{{ listing.description|linebreaks }}</p>
|
|
8
|
|
- <h2 class="font-semibold mt-6 mb-2">Items</h2>
|
|
|
9
|
+ <h2 class="font-semibold mt-6 mb-2">{% trans "Items" %}</h2>
|
|
9
|
10
|
<ul class="list-disc ml-6 text-sm text-gray-700">
|
|
10
|
11
|
{% for it in listing.items.all %}
|
|
11
|
12
|
<li>{{ it.material.name }} — {{ it.quantity_estimate }} {{ it.get_unit_display }}</li>
|
|
12
|
13
|
{% empty %}
|
|
13
|
|
- <li>No items listed</li>
|
|
|
14
|
+ <li>{% trans "No items listed" %}</li>
|
|
14
|
15
|
{% endfor %}
|
|
15
|
16
|
</ul>
|
|
16
|
17
|
</article>
|
|
|
@@ -1,19 +1,19 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Listings{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Listings" %}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
5
|
<div class="grid md:grid-cols-3 gap-4">
|
|
5
|
6
|
{% for l in listings %}
|
|
6
|
7
|
<a href="{% url 'public_frontend:listing_detail' l.id %}" class="block p-4 border rounded bg-white hover:bg-gray-50">
|
|
7
|
|
- <div class="text-sm text-gray-500">Ends {{ l.ends_at|default:'—' }}</div>
|
|
|
8
|
+ <div class="text-sm text-gray-500">{% trans "Ends" %} {{ l.ends_at|default:'—' }}</div>
|
|
8
|
9
|
<div class="font-medium">{{ l.title }}</div>
|
|
9
|
10
|
<div class="text-sm text-gray-600 line-clamp-2">{{ l.description|truncatechars:140 }}</div>
|
|
10
|
11
|
</a>
|
|
11
|
12
|
{% empty %}
|
|
12
|
|
- <div class="text-gray-500">No public listings.</div>
|
|
|
13
|
+ <div class="text-gray-500">{% trans "No public listings." %}</div>
|
|
13
|
14
|
{% endfor %}
|
|
14
|
15
|
</div>
|
|
15
|
16
|
<div class="mt-4">
|
|
16
|
17
|
{% include 'admin_frontend/_pagination.html' %}
|
|
17
|
18
|
</div>
|
|
18
|
19
|
{% endblock %}
|
|
19
|
|
-
|
|
|
@@ -1,13 +1,14 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Materials{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Materials" %}{% endblock %}
|
|
3
|
4
|
{% block content %}
|
|
4
|
5
|
<div class="bg-white rounded shadow overflow-hidden">
|
|
5
|
6
|
<table class="min-w-full text-sm">
|
|
6
|
7
|
<thead class="bg-gray-50 text-left">
|
|
7
|
8
|
<tr>
|
|
8
|
|
- <th class="px-4 py-2">Name</th>
|
|
9
|
|
- <th class="px-4 py-2">Category</th>
|
|
10
|
|
- <th class="px-4 py-2">Default Unit</th>
|
|
|
9
|
+ <th class="px-4 py-2">{% trans "Name" %}</th>
|
|
|
10
|
+ <th class="px-4 py-2">{% trans "Category" %}</th>
|
|
|
11
|
+ <th class="px-4 py-2">{% trans "Default Unit" %}</th>
|
|
11
|
12
|
</tr>
|
|
12
|
13
|
</thead>
|
|
13
|
14
|
<tbody>
|
|
|
@@ -18,7 +19,7 @@
|
|
18
|
19
|
<td class="px-4 py-2">{{ m.get_default_unit_display }}</td>
|
|
19
|
20
|
</tr>
|
|
20
|
21
|
{% empty %}
|
|
21
|
|
- <tr><td colspan="3" class="px-4 py-6 text-center text-gray-500">No materials published.</td></tr>
|
|
|
22
|
+ <tr><td colspan="3" class="px-4 py-6 text-center text-gray-500">{% trans "No materials published." %}</td></tr>
|
|
22
|
23
|
{% endfor %}
|
|
23
|
24
|
</tbody>
|
|
24
|
25
|
</table>
|
|
|
@@ -1,8 +1,9 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
2
|
|
-{% block title %}Request Pickup{% endblock %}
|
|
|
2
|
+{% load i18n %}
|
|
|
3
|
+{% block title %}{% trans "Request Pickup" %}{% endblock %}
|
|
3
|
4
|
{% load crispy_forms_tags widget_tweaks %}
|
|
4
|
5
|
{% block content %}
|
|
5
|
|
-<h1 class="text-xl font-semibold mb-4">Request a Pickup</h1>
|
|
|
6
|
+<h1 class="text-xl font-semibold mb-4">{% trans "Request a Pickup" %}</h1>
|
|
6
|
7
|
<div x-data="pickupForm()">
|
|
7
|
8
|
<div class="bg-white rounded shadow p-4" >
|
|
8
|
9
|
<form x-ref="form" id="pickup-form" method="post" enctype="multipart/form-data" class="space-y-4">
|
|
|
@@ -32,33 +33,33 @@
|
|
32
|
33
|
|
|
33
|
34
|
<dialog x-ref="dialog" id="review_modal" class="modal dark:bg-black/40">
|
|
34
|
35
|
<div class="modal-box dark:bg-gray-900 dark:text-gray-100 dark:border dark:border-gray-700">
|
|
35
|
|
- <h3 class="font-bold text-lg">Review Your Pickup Request</h3>
|
|
|
36
|
+ <h3 class="font-bold text-lg">{% trans "Review Your Pickup Request" %}</h3>
|
|
36
|
37
|
<div class="py-4 space-y-3">
|
|
37
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Name</div><div class="font-medium" x-text="rv.name || '—'"></div></div>
|
|
|
38
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Name" %}</div><div class="font-medium" x-text="rv.name || '—'"></div></div>
|
|
38
|
39
|
<div class="grid md:grid-cols-2 gap-4">
|
|
39
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Email</div><div class="font-medium" x-text="rv.email || '—'"></div></div>
|
|
40
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Phone</div><div class="font-medium" x-text="rv.phone || '—'"></div></div>
|
|
|
40
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Email" %}</div><div class="font-medium" x-text="rv.email || '—'"></div></div>
|
|
|
41
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Phone" %}</div><div class="font-medium" x-text="rv.phone || '—'"></div></div>
|
|
41
|
42
|
</div>
|
|
42
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Pickup Address</div><div class="whitespace-pre-line" x-text="rv.address || '—'"></div></div>
|
|
|
43
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Pickup Address" %}</div><div class="whitespace-pre-line" x-text="rv.address || '—'"></div></div>
|
|
43
|
44
|
<div class="grid md:grid-cols-2 gap-4">
|
|
44
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Preferred Date/Time</div><div class="font-medium" x-text="rv.preferred_at || '—'"></div></div>
|
|
|
45
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Preferred Date/Time" %}</div><div class="font-medium" x-text="rv.preferred_at || '—'"></div></div>
|
|
45
|
46
|
</div>
|
|
46
|
|
- <div><div class="text-sm text-gray-600 dark:text-gray-300">Materials / Notes</div><div class="whitespace-pre-line" x-text="rv.materials || '—'"></div></div>
|
|
|
47
|
+ <div><div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Materials / Notes" %}</div><div class="whitespace-pre-line" x-text="rv.materials || '—'"></div></div>
|
|
47
|
48
|
<div>
|
|
48
|
|
- <div class="text-sm text-gray-600 dark:text-gray-300">Photos</div>
|
|
|
49
|
+ <div class="text-sm text-gray-600 dark:text-gray-300">{% trans "Photos" %}</div>
|
|
49
|
50
|
<ul class="list-disc list-inside text-sm text-gray-700 dark:text-gray-300">
|
|
50
|
|
- <template x-if="(rv.photos||[]).length === 0"><li>No files attached.</li></template>
|
|
|
51
|
+ <template x-if="(rv.photos||[]).length === 0"><li>{% trans "No files attached." %}</li></template>
|
|
51
|
52
|
<template x-for="fname in rv.photos" :key="fname"><li x-text="fname"></li></template>
|
|
52
|
53
|
</ul>
|
|
53
|
54
|
</div>
|
|
54
|
55
|
</div>
|
|
55
|
56
|
<div class="modal-action">
|
|
56
|
|
- <button class="btn" @click="$refs.dialog.close()">Edit</button>
|
|
57
|
|
- <button class="btn btn-primary" @click.prevent="confirm">Confirm & Send</button>
|
|
|
57
|
+ <button class="btn" @click="$refs.dialog.close()">{% trans "Edit" %}</button>
|
|
|
58
|
+ <button class="btn btn-primary" @click.prevent="confirm">{% trans "Confirm & Send" %}</button>
|
|
58
|
59
|
</div>
|
|
59
|
60
|
</div>
|
|
60
|
61
|
<form method="dialog" class="modal-backdrop dark:bg-black/60">
|
|
61
|
|
- <button class="dark:text-gray-200">close</button>
|
|
|
62
|
+ <button class="dark:text-gray-200">{% trans "close" %}</button>
|
|
62
|
63
|
</form>
|
|
63
|
64
|
</dialog>
|
|
64
|
65
|
</div>
|
|
|
@@ -1,4 +1,5 @@
|
|
1
|
1
|
{% extends 'public_frontend/base.html' %}
|
|
|
2
|
+{% load i18n %}
|
|
2
|
3
|
{% load static %}
|
|
3
|
4
|
{% block title %}{{ service.title }}{% endblock %}
|
|
4
|
5
|
|
|
|
@@ -25,8 +26,7 @@
|
|
25
|
26
|
{% endif %}
|
|
26
|
27
|
|
|
27
|
28
|
<div>
|
|
28
|
|
- <a href="/" class="text-blue-700 hover:underline">← Back to Home</a>
|
|
|
29
|
+ <a href="/" class="text-blue-700 hover:underline">← {% trans "Back to Home" %}</a>
|
|
29
|
30
|
</div>
|
|
30
|
31
|
</article>
|
|
31
|
32
|
{% endblock %}
|
|
32
|
|
-
|
|
|
@@ -6,6 +6,7 @@ from django.contrib.contenttypes.fields import GenericForeignKey
|
|
6
|
6
|
from django.contrib.contenttypes.models import ContentType
|
|
7
|
7
|
from django.db import models
|
|
8
|
8
|
from django.utils import timezone
|
|
|
9
|
+from django.utils.translation import gettext_lazy as _
|
|
9
|
10
|
from orgs.models import Organization
|
|
10
|
11
|
from markdownfield.models import MarkdownField, RenderedMarkdownField
|
|
11
|
12
|
from markdownfield.validators import VALIDATOR_STANDARD
|
|
|
@@ -52,14 +53,14 @@ class MaterialCategory(TimestampedModel):
|
|
52
|
53
|
class ProvidedService(TimestampedModel):
|
|
53
|
54
|
organization = models.ForeignKey(Organization, on_delete=models.CASCADE, related_name="services")
|
|
54
|
55
|
TITLE_CHOICES = (
|
|
55
|
|
- ("Pickup & Logistics", "Pickup & Logistics"),
|
|
56
|
|
- ("Material Sorting", "Material Sorting"),
|
|
57
|
|
- ("Weighing & Ticketing", "Weighing & Ticketing"),
|
|
58
|
|
- ("Invoicing & Payouts", "Invoicing & Payouts"),
|
|
59
|
|
- ("Reporting & Analytics", "Reporting & Analytics"),
|
|
60
|
|
- ("Marketplace & Bidding", "Marketplace & Bidding"),
|
|
61
|
|
- ("Compliance & Audits", "Compliance & Audits"),
|
|
62
|
|
- ("Consulting & Training", "Consulting & Training"),
|
|
|
56
|
+ ("Pickup & Logistics", _("Pickup & Logistics")),
|
|
|
57
|
+ ("Material Sorting", _("Material Sorting")),
|
|
|
58
|
+ ("Weighing & Ticketing", _("Weighing & Ticketing")),
|
|
|
59
|
+ ("Invoicing & Payouts", _("Invoicing & Payouts")),
|
|
|
60
|
+ ("Reporting & Analytics", _("Reporting & Analytics")),
|
|
|
61
|
+ ("Marketplace & Bidding", _("Marketplace & Bidding")),
|
|
|
62
|
+ ("Compliance & Audits", _("Compliance & Audits")),
|
|
|
63
|
+ ("Consulting & Training", _("Consulting & Training")),
|
|
63
|
64
|
)
|
|
64
|
65
|
title = models.CharField(max_length=100, choices=TITLE_CHOICES)
|
|
65
|
66
|
# Short summary text
|
|
|
@@ -8,9 +8,9 @@
|
|
8
|
8
|
</div>
|
|
9
|
9
|
|
|
10
|
10
|
<div class="bg-white rounded shadow p-4 mb-4">
|
|
11
|
|
- <form method="get" class="space-y-3 flex gap-2">
|
|
|
11
|
+ <form method="get" class="flex flex-wrap gap-3 items-end">
|
|
12
|
12
|
{{ filter.form|crispy }}
|
|
13
|
|
- <div class="flex items-center gap-2">
|
|
|
13
|
+ <div class="flex items-center gap-2 w-full">
|
|
14
|
14
|
<button class="px-3 py-2 bg-blue-600 text-white rounded">Filter</button>
|
|
15
|
15
|
<a href="{% url 'recycle_core:pickups_list' %}" class="btn-outline">Reset</a>
|
|
16
|
16
|
</div>
|
|
|
@@ -20,15 +20,16 @@
|
|
20
|
20
|
{% endif %}
|
|
21
|
21
|
</div>
|
|
22
|
22
|
|
|
23
|
|
-<div class="bg-white rounded shadow overflow-hidden">
|
|
24
|
|
- <table class="min-w-full divide-y divide-gray-200">
|
|
|
23
|
+<div class="bg-white rounded shadow">
|
|
|
24
|
+ <div class="overflow-x-auto">
|
|
|
25
|
+ <table class="min-w-[1100px] divide-y divide-gray-200">
|
|
25
|
26
|
<thead class="bg-gray-50">
|
|
26
|
27
|
<tr>
|
|
27
|
28
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Customer</th>
|
|
28
|
29
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Site</th>
|
|
29
|
30
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Status</th>
|
|
30
|
31
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Scheduled</th>
|
|
31
|
|
- <th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Driver</th>
|
|
|
32
|
+ <th class="px-4 py-2 text-left text-xs font-medium text-gray-500 w-56 min-w-[220px]">Driver</th>
|
|
32
|
33
|
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500">Actions</th>
|
|
33
|
34
|
</tr>
|
|
34
|
35
|
</thead>
|
|
|
@@ -38,11 +39,11 @@
|
|
38
|
39
|
<td class="px-4 py-2">{{ p.customer.name }}</td>
|
|
39
|
40
|
<td class="px-4 py-2">{{ p.site.name }}</td>
|
|
40
|
41
|
<td class="px-4 py-2"><span class="px-2 py-1 rounded bg-gray-100 text-gray-700">{{ p.status }}</span></td>
|
|
41
|
|
- <td class="px-4 py-2 text-gray-500">{{ p.scheduled_at|default:"-" }}</td>
|
|
42
|
|
- <td class="px-4 py-2 text-gray-600">{{ p.assigned_driver.username|default:"-" }}</td>
|
|
|
42
|
+ <td class="px-4 py-2 text-gray-500">{{ p.scheduled_at|date:"SHORT_DATETIME_FORMAT"|default:"-" }}</td>
|
|
|
43
|
+ <td class="px-4 py-2 text-gray-600 w-56 min-w-[220px] whitespace-nowrap">{{ p.assigned_driver.username|default:"-" }}</td>
|
|
43
|
44
|
<td class="px-4 py-2">
|
|
44
|
45
|
<div class="flex flex-wrap gap-2 justify-end">
|
|
45
|
|
- <a class="btn-outline btn-xs" href="{% url 'recycle_core:pickup_detail' p.id %}">View</a>
|
|
|
46
|
+ <a class="text-blue-600 hover:text-blue-700 hover:underline text-xs font-medium mr-3" href="{% url 'recycle_core:pickup_detail' p.id %}">View</a>
|
|
46
|
47
|
{% if perms.recycle_core.assign_driver %}
|
|
47
|
48
|
<form method="post" action="{% url 'recycle_core:pickup_assign' p.id %}" class="flex items-center gap-2">
|
|
48
|
49
|
{% csrf_token %}
|
|
|
@@ -78,6 +79,7 @@
|
|
78
|
79
|
{% endfor %}
|
|
79
|
80
|
</tbody>
|
|
80
|
81
|
</table>
|
|
|
82
|
+ </div>
|
|
81
|
83
|
{% include 'admin_frontend/_pagination.html' %}
|
|
82
|
84
|
</div>
|
|
83
|
85
|
{% endblock %}
|
|
|
@@ -7,6 +7,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
|
7
|
7
|
import django_filters as filters
|
|
8
|
8
|
|
|
9
|
9
|
from admin_frontend.templatetags.public_urls import public_route
|
|
|
10
|
+from django import forms
|
|
10
|
11
|
from admin_frontend.nav import _nav_items
|
|
11
|
12
|
from cms.views import breadcrumbs
|
|
12
|
13
|
from orgs.decorators import permissions_required
|
|
|
@@ -421,7 +422,16 @@ def pickups_list(request):
|
|
421
|
422
|
site = filters.ModelChoiceFilter(queryset=CustomerSite.objects.all())
|
|
422
|
423
|
assigned_driver = filters.CharFilter(field_name="assigned_driver__username", lookup_expr="icontains", label="Driver")
|
|
423
|
424
|
status = filters.ChoiceFilter(field_name="status", choices=PickupOrder.STATUS_CHOICES)
|
|
424
|
|
- scheduled_at = filters.DateFromToRangeFilter(field_name="scheduled_at", label="Scheduled between")
|
|
|
425
|
+ scheduled_at = filters.DateTimeFromToRangeFilter(
|
|
|
426
|
+ field_name="scheduled_at",
|
|
|
427
|
+ label="Scheduled between",
|
|
|
428
|
+ widget=filters.widgets.RangeWidget(
|
|
|
429
|
+ attrs={
|
|
|
430
|
+ "type": "datetime-local",
|
|
|
431
|
+ "class": "border border-gray-300 rounded px-2 py-1"
|
|
|
432
|
+ }
|
|
|
433
|
+ ),
|
|
|
434
|
+ )
|
|
425
|
435
|
|
|
426
|
436
|
class Meta:
|
|
427
|
437
|
model = PickupOrder
|
|
|
@@ -448,6 +458,11 @@ def pickups_list(request):
|
|
448
|
458
|
|
|
449
|
459
|
# empty forms used in row actions
|
|
450
|
460
|
assign_form = PickupAssignForm()
|
|
|
461
|
+ # Limit driver choices to users with driver role, scoped to org if present
|
|
|
462
|
+ drivers_qs = get_user_model().objects.filter(recycle_profile__role="driver")
|
|
|
463
|
+ if org is not None:
|
|
|
464
|
+ drivers_qs = drivers_qs.filter(recycle_profile__organization=org)
|
|
|
465
|
+ assign_form.fields["driver"].queryset = drivers_qs.order_by("username")
|
|
451
|
466
|
status_form = PickupStatusForm()
|
|
452
|
467
|
|
|
453
|
468
|
context = {
|
|
|
@@ -467,6 +482,12 @@ def pickups_list(request):
|
|
467
|
482
|
def pickup_assign(request, pk: int):
|
|
468
|
483
|
pickup = get_object_or_404(PickupOrder, pk=pk)
|
|
469
|
484
|
form = PickupAssignForm(request.POST)
|
|
|
485
|
+ # Enforce driver role (and org, if present) on POST validation
|
|
|
486
|
+ org = getattr(request, "org", None)
|
|
|
487
|
+ drivers_qs = get_user_model().objects.filter(recycle_profile__role="driver")
|
|
|
488
|
+ if org is not None:
|
|
|
489
|
+ drivers_qs = drivers_qs.filter(recycle_profile__organization=org)
|
|
|
490
|
+ form.fields["driver"].queryset = drivers_qs
|
|
470
|
491
|
if form.is_valid():
|
|
471
|
492
|
pickup.assigned_driver = form.cleaned_data["driver"]
|
|
472
|
493
|
pickup.status = PickupOrder.STATUS_SCHEDULED
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+from django.utils.translation import gettext_lazy as _
|
|
|
2
|
+
|
|
|
3
|
+# Mark app menu labels for translation extraction
|
|
|
4
|
+_("Dashboard")
|
|
|
5
|
+_("Profiles")
|
|
|
6
|
+_("Opportunities")
|
|
|
7
|
+_("Intro Requests")
|
|
|
8
|
+_("Leaderboard")
|
|
|
9
|
+_("Leads")
|
|
|
10
|
+_("CMS Posts")
|
|
|
11
|
+_("CMS Categories")
|
|
|
12
|
+_("Settings")
|
|
|
13
|
+_("Advanced Settings")
|
|
|
14
|
+_("Organization Settings")
|
|
|
15
|
+_("Organization Sites")
|
|
|
16
|
+_("Permissions")
|
|
|
17
|
+_("Users")
|
|
|
18
|
+_("Materials")
|
|
|
19
|
+_("Customers")
|
|
|
20
|
+_("Pickups")
|
|
|
21
|
+_("Weigh Tickets")
|
|
|
22
|
+_("Documents")
|
|
|
23
|
+_("Provided Services")
|
|
|
24
|
+_("Scrap Listings")
|
|
|
25
|
+_("Invoices")
|
|
|
26
|
+_("Payouts")
|
|
|
27
|
+
|