fix: restore archive downloads and task report context
Restore the monthly fleet archive task and routes so generated ZIP downloads work again. Also populate service report DOCX headers from the selected task/work-order context and cover the direct and archived export flows with regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -836,23 +836,72 @@ def process_work_order_invoice_ocr(invoice_id):
|
||||
|
||||
|
||||
@shared_task
|
||||
def cleanup_expired_generated_pdfs_task():
|
||||
now = timezone.now()
|
||||
expired = GeneratedWorkOrderPdf.objects.filter(
|
||||
is_active=True,
|
||||
expires_at__isnull=False,
|
||||
expires_at__lte=now,
|
||||
def build_monthly_archive_cached_task(generated_archive_id):
|
||||
from .models import GeneratedFleetArchive
|
||||
from .services import NotificationService
|
||||
from .views import (
|
||||
_build_monthly_service_tasks_archive_content,
|
||||
_build_monthly_work_orders_archive_content,
|
||||
_notify_monthly_archive_request,
|
||||
)
|
||||
deleted = 0
|
||||
for item in expired:
|
||||
if item.file:
|
||||
item.file.delete(save=False)
|
||||
item.is_active = False
|
||||
item.status = 'failed'
|
||||
item.error_message = 'PDF cache istekao.'
|
||||
item.save(update_fields=['is_active', 'status', 'error_message', 'updated_at'])
|
||||
deleted += 1
|
||||
return {"deleted": deleted}
|
||||
|
||||
generated = (
|
||||
GeneratedFleetArchive.objects
|
||||
.select_related('requested_by')
|
||||
.filter(pk=generated_archive_id, is_active=True)
|
||||
.first()
|
||||
)
|
||||
if generated is None:
|
||||
return {"status": "failed", "error": "Generated archive record not found"}
|
||||
|
||||
try:
|
||||
if generated.archive_type == 'service_tasks':
|
||||
archive_content = _build_monthly_service_tasks_archive_content(
|
||||
user=generated.requested_by,
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
)
|
||||
else:
|
||||
archive_content = _build_monthly_work_orders_archive_content(
|
||||
user=generated.requested_by,
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
)
|
||||
|
||||
if not archive_content:
|
||||
raise ValueError('ZIP arhiva je prazna.')
|
||||
|
||||
filename = generated.filename or f"{generated.archive_type}-{generated.year}-{generated.month}.zip"
|
||||
generated.file.save(filename, ContentFile(archive_content), save=False)
|
||||
generated.status = 'ready'
|
||||
generated.error_message = ''
|
||||
generated.save(update_fields=['file', 'status', 'error_message', 'updated_at'])
|
||||
|
||||
if generated.requested_by:
|
||||
_notify_monthly_archive_request(
|
||||
user=generated.requested_by,
|
||||
archive_type=generated.archive_type,
|
||||
stage='completed',
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
generated_archive=generated,
|
||||
)
|
||||
return {"status": "ready", "generated_archive_id": str(generated.pk)}
|
||||
except Exception as exc:
|
||||
generated.status = 'failed'
|
||||
generated.error_message = str(exc)
|
||||
generated.save(update_fields=['status', 'error_message', 'updated_at'])
|
||||
if generated.requested_by:
|
||||
_notify_monthly_archive_request(
|
||||
user=generated.requested_by,
|
||||
archive_type=generated.archive_type,
|
||||
stage='failed',
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
generated_archive=generated,
|
||||
)
|
||||
logger.exception("Greška kod build_monthly_archive_cached_task: %s", exc)
|
||||
return {"status": "failed", "error": str(exc)}
|
||||
|
||||
|
||||
@shared_task
|
||||
@@ -875,6 +924,26 @@ def cleanup_expired_generated_archives_task():
|
||||
return {"deleted": deleted}
|
||||
|
||||
|
||||
@shared_task
|
||||
def cleanup_expired_generated_pdfs_task():
|
||||
now = timezone.now()
|
||||
expired = GeneratedWorkOrderPdf.objects.filter(
|
||||
is_active=True,
|
||||
expires_at__isnull=False,
|
||||
expires_at__lte=now,
|
||||
)
|
||||
deleted = 0
|
||||
for item in expired:
|
||||
if item.file:
|
||||
item.file.delete(save=False)
|
||||
item.is_active = False
|
||||
item.status = 'failed'
|
||||
item.error_message = 'PDF cache istekao.'
|
||||
item.save(update_fields=['is_active', 'status', 'error_message', 'updated_at'])
|
||||
deleted += 1
|
||||
return {"deleted": deleted}
|
||||
|
||||
|
||||
@shared_task
|
||||
def build_work_order_pdf_cached_task(generated_pdf_id):
|
||||
from .views import _build_work_order_pdf, _build_work_order_service_records_pdf
|
||||
@@ -954,98 +1023,4 @@ def build_work_order_pdf_cached_task(generated_pdf_id):
|
||||
},
|
||||
)
|
||||
logger.exception("Greška kod build_work_order_pdf_cached_task: %s", exc)
|
||||
return {"status": "failed", "error": str(exc)}
|
||||
|
||||
|
||||
@shared_task
|
||||
def build_monthly_archive_cached_task(generated_archive_id):
|
||||
from .services import NotificationService
|
||||
from .views import (
|
||||
_build_monthly_service_tasks_archive_content,
|
||||
_build_monthly_work_orders_archive_content,
|
||||
_generated_archive_filename_for_user,
|
||||
)
|
||||
|
||||
generated = (
|
||||
GeneratedFleetArchive.objects
|
||||
.select_related('requested_by')
|
||||
.filter(pk=generated_archive_id, is_active=True)
|
||||
.first()
|
||||
)
|
||||
if generated is None:
|
||||
return {'error': 'Generated ZIP zapis nije pronađen.'}
|
||||
|
||||
requested_by = generated.requested_by
|
||||
if requested_by is None:
|
||||
generated.status = 'failed'
|
||||
generated.error_message = 'Korisnik koji je zatražio ZIP arhivu nije dostupan.'
|
||||
generated.save(update_fields=['status', 'error_message', 'updated_at'])
|
||||
return {'status': 'failed', 'error': generated.error_message}
|
||||
|
||||
try:
|
||||
if generated.archive_type == 'work_orders':
|
||||
archive_bytes = _build_monthly_work_orders_archive_content(
|
||||
user=requested_by,
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
)
|
||||
else:
|
||||
archive_bytes = _build_monthly_service_tasks_archive_content(
|
||||
user=requested_by,
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
)
|
||||
|
||||
filename = generated.filename or _generated_archive_filename_for_user(
|
||||
requested_by,
|
||||
year=generated.year,
|
||||
month=generated.month,
|
||||
archive_type=generated.archive_type,
|
||||
)
|
||||
generated.file.save(filename, ContentFile(archive_bytes), save=False)
|
||||
generated.status = 'ready'
|
||||
generated.error_message = ''
|
||||
generated.save(update_fields=['file', 'status', 'error_message', 'updated_at'])
|
||||
|
||||
NotificationService.create_notification(
|
||||
recipient=requested_by,
|
||||
title='ZIP arhiva spremna',
|
||||
message=f"ZIP arhiva je spremna za preuzimanje ({generated.month:02d}.{generated.year}.).",
|
||||
level='success',
|
||||
send_email=False,
|
||||
metadata={
|
||||
'entity_type': 'fleet_archive',
|
||||
'archive_type': generated.archive_type,
|
||||
'stage': 'completed',
|
||||
'year': generated.year,
|
||||
'month': generated.month,
|
||||
'generated_archive_id': str(generated.pk),
|
||||
'download_url': f"fleet/reports/generated-archives/{generated.pk}/download/",
|
||||
'filename': generated.filename or filename,
|
||||
'expires_at': generated.expires_at.isoformat() if generated.expires_at else None,
|
||||
'section': 'service-records',
|
||||
},
|
||||
)
|
||||
return {'status': 'ready', 'generated_archive_id': str(generated.pk)}
|
||||
except Exception as exc:
|
||||
generated.status = 'failed'
|
||||
generated.error_message = str(exc)
|
||||
generated.save(update_fields=['status', 'error_message', 'updated_at'])
|
||||
NotificationService.create_notification(
|
||||
recipient=requested_by,
|
||||
title='Greška kod ZIP arhive',
|
||||
message=f"Generiranje ZIP arhive nije uspjelo ({generated.month:02d}.{generated.year}.).",
|
||||
level='warning',
|
||||
send_email=False,
|
||||
metadata={
|
||||
'entity_type': 'fleet_archive',
|
||||
'archive_type': generated.archive_type,
|
||||
'stage': 'failed',
|
||||
'year': generated.year,
|
||||
'month': generated.month,
|
||||
'generated_archive_id': str(generated.pk),
|
||||
'section': 'service-records',
|
||||
},
|
||||
)
|
||||
logger.exception("Greška kod build_monthly_archive_cached_task: %s", exc)
|
||||
return {'status': 'failed', 'error': str(exc)}
|
||||
return {"status": "failed", "error": str(exc)}
|
||||
@@ -60,7 +60,12 @@ class WorkOrderImagesEndpointTests(TestCase):
|
||||
self.vehicle = Vehicle.objects.create(
|
||||
asset_type='crane',
|
||||
registration_number='WO-IMG-001',
|
||||
make='Liebherr',
|
||||
model='LTM 1090',
|
||||
crane_serial_number='123456',
|
||||
superstructure_working_hours=111,
|
||||
chassis_working_hours=222,
|
||||
current_mileage=33344,
|
||||
)
|
||||
self.work_order = WorkOrder.objects.create(
|
||||
vehicle=self.vehicle,
|
||||
@@ -301,6 +306,23 @@ class WorkOrderImagesEndpointTests(TestCase):
|
||||
self.assertIn('24.12.2033', document_xml)
|
||||
self.assertEqual(document_xml.count('w:type="page"'), 1)
|
||||
|
||||
def test_task_specific_service_records_docx_uses_dynamic_docx_header_values(self):
|
||||
response = self.client.get(
|
||||
f"/api/fleet/work-orders/{self.work_order.pk}/service-records-docx/?task_id={self.task.pk}"
|
||||
)
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
archive = zipfile.ZipFile(BytesIO(response.content))
|
||||
header_xml = archive.read('word/header1.xml').decode('utf-8')
|
||||
self.assertIn('Liebherr LTM 1090', header_xml)
|
||||
self.assertIn('123456', header_xml)
|
||||
self.assertIn('111', header_xml)
|
||||
self.assertIn('222', header_xml)
|
||||
self.assertIn('33344', header_xml)
|
||||
self.assertIn('MT150726', header_xml)
|
||||
self.assertNotIn('MT250226', header_xml)
|
||||
self.assertNotIn('LTM-1350', header_xml)
|
||||
|
||||
def test_monthly_service_tasks_archive_returns_zip_with_task_docx(self):
|
||||
response = self.client.get('/api/fleet/reports/monthly-service-tasks-archive/?year=2033&month=12')
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
@@ -310,6 +332,26 @@ class WorkOrderImagesEndpointTests(TestCase):
|
||||
names = archive.namelist()
|
||||
self.assertIn('MT150726.SN-Test_servisni_zadatak.docx', names)
|
||||
|
||||
def test_monthly_service_tasks_archive_docx_uses_dynamic_header_values(self):
|
||||
response = self.client.get('/api/fleet/reports/monthly-service-tasks-archive/?year=2033&month=12')
|
||||
self.assertEqual(response.status_code, 200, response.content)
|
||||
|
||||
archive = zipfile.ZipFile(BytesIO(response.content))
|
||||
docx_name = 'MT150726.SN-Test_servisni_zadatak.docx'
|
||||
self.assertIn(docx_name, archive.namelist())
|
||||
|
||||
docx_bytes = archive.read(docx_name)
|
||||
docx_archive = zipfile.ZipFile(BytesIO(docx_bytes))
|
||||
header_xml = docx_archive.read('word/header1.xml').decode('utf-8', errors='ignore')
|
||||
self.assertIn('Liebherr LTM 1090', header_xml)
|
||||
self.assertIn('123456', header_xml)
|
||||
self.assertIn('111', header_xml)
|
||||
self.assertIn('222', header_xml)
|
||||
self.assertIn('33344', header_xml)
|
||||
self.assertIn('MT150726', header_xml)
|
||||
self.assertNotIn('MT250226', header_xml)
|
||||
self.assertNotIn('LTM-1350', header_xml)
|
||||
|
||||
def test_monthly_work_orders_archive_contains_work_orders_and_invoices_folder(self):
|
||||
WorkOrderInvoice.objects.create(
|
||||
work_order=self.work_order,
|
||||
|
||||
@@ -3,8 +3,10 @@ from django.urls import path
|
||||
from .views import (
|
||||
CraneViewSet, VehicleViewSet, WorkOrderViewSet, VehicleServiceRecordViewSet,
|
||||
WorkOrderInvoiceViewSet, ServiceContextNoteViewSet, VehicleNotificationViewSet, VehicleServicePhotoViewSet, VehicleServiceAttachmentViewSet, pusher_auth,
|
||||
monthly_servicer_report_docx, monthly_costs_report_docx, monthly_service_tasks_archive, monthly_work_orders_archive, monthly_service_tasks_archive_request,
|
||||
monthly_work_orders_archive_request, generated_archive_download, MonthlyServicerDayEntryViewSet,
|
||||
monthly_servicer_report_docx, monthly_costs_report_docx, MonthlyServicerDayEntryViewSet,
|
||||
monthly_service_tasks_archive, monthly_service_tasks_archive_request,
|
||||
monthly_work_orders_archive, monthly_work_orders_archive_request,
|
||||
generated_archive_download,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
@@ -26,8 +28,8 @@ urlpatterns += [
|
||||
path('reports/monthly-servicer/', monthly_servicer_report_docx, name='monthly-servicer-report'),
|
||||
path('reports/monthly-costs/', monthly_costs_report_docx, name='monthly-costs-report'),
|
||||
path('reports/monthly-service-tasks-archive/', monthly_service_tasks_archive, name='monthly-service-tasks-archive'),
|
||||
path('reports/monthly-work-orders-archive/', monthly_work_orders_archive, name='monthly-work-orders-archive'),
|
||||
path('reports/monthly-service-tasks-archive-request/', monthly_service_tasks_archive_request, name='monthly-service-tasks-archive-request'),
|
||||
path('reports/monthly-work-orders-archive/', monthly_work_orders_archive, name='monthly-work-orders-archive'),
|
||||
path('reports/monthly-work-orders-archive-request/', monthly_work_orders_archive_request, name='monthly-work-orders-archive-request'),
|
||||
path('reports/generated-archives/<uuid:archive_id>/download/', generated_archive_download, name='generated-archive-download'),
|
||||
path('reports/generated-archives/<str:archive_id>/download/', generated_archive_download, name='generated-archive-download'),
|
||||
]
|
||||
@@ -1839,6 +1839,59 @@ def _docx_remove_empty_page_break_paragraphs(document):
|
||||
body.remove(element)
|
||||
|
||||
|
||||
def _build_service_report_docx_context(work_order, related_tasks):
|
||||
primary_task = related_tasks[0] if len(related_tasks) == 1 else None
|
||||
context_work_order = getattr(primary_task, 'work_order', None) if primary_task else None
|
||||
if context_work_order is None:
|
||||
context_work_order = work_order
|
||||
|
||||
context_vehicle = getattr(primary_task, 'vehicle', None) if primary_task else None
|
||||
if context_vehicle is None:
|
||||
context_vehicle = getattr(context_work_order, 'vehicle', None) or work_order.vehicle
|
||||
|
||||
model_parts = [
|
||||
str(getattr(context_vehicle, 'make', '') or '').strip(),
|
||||
str(getattr(context_vehicle, 'model', '') or '').strip(),
|
||||
]
|
||||
model_label = " ".join(part for part in model_parts if part) or '-'
|
||||
return {
|
||||
'work_order': context_work_order,
|
||||
'vehicle': context_vehicle,
|
||||
'client_name': getattr(getattr(context_vehicle, 'client', None), 'name', None) or '-',
|
||||
'location': getattr(context_work_order, 'location', None) or '-',
|
||||
'servicer_name': _user_display_name(getattr(context_work_order, 'creator', None)) or '-',
|
||||
'servicer_vehicle_make_model': getattr(context_work_order, 'servicer_vehicle_make_model', None) or '-',
|
||||
'servicer_vehicle_registration': getattr(context_work_order, 'servicer_vehicle_registration', None) or '-',
|
||||
'completion_label': "Da ☒ Ne ☐" if getattr(context_work_order, 'status', None) == 'closed' else "Da ☐ Ne ☒",
|
||||
'model': model_label,
|
||||
'serial': str(getattr(context_vehicle, 'crane_serial_number', None) or '-'),
|
||||
'upgrade_hours': str(getattr(context_vehicle, 'superstructure_working_hours', None) or '-'),
|
||||
'chassis_hours': str(getattr(context_vehicle, 'chassis_working_hours', None) or '-'),
|
||||
'mileage': str(getattr(context_vehicle, 'current_mileage', None) or '-'),
|
||||
'work_order_display_code': _work_order_display_code(context_work_order),
|
||||
}
|
||||
|
||||
|
||||
def _populate_service_report_docx_header(document, context):
|
||||
for section in getattr(document, 'sections', []):
|
||||
header = getattr(section, 'header', None)
|
||||
if header is None:
|
||||
continue
|
||||
header_tables = list(getattr(header, 'tables', []))
|
||||
if len(header_tables) >= 2:
|
||||
table_meta = header_tables[0]
|
||||
_set_docx_cell_text(table_meta, 1, 0, context['model'])
|
||||
_set_docx_cell_text(table_meta, 1, 2, context['serial'])
|
||||
_set_docx_cell_text(table_meta, 1, 4, context['upgrade_hours'])
|
||||
_set_docx_cell_text(table_meta, 1, 5, context['chassis_hours'])
|
||||
_set_docx_cell_text(table_meta, 1, 6, context['mileage'])
|
||||
_set_docx_cell_text(table_meta, 1, 8, context['work_order_display_code'])
|
||||
|
||||
table_title = header_tables[1]
|
||||
_set_docx_cell_text(table_title, 0, 0, context['client_name'])
|
||||
_set_docx_cell_text(table_title, 0, 1, 'Izvještaj servisera')
|
||||
|
||||
|
||||
def _build_work_order_docx_bytes(work_order):
|
||||
vehicle = work_order.vehicle
|
||||
creator = work_order.creator
|
||||
@@ -1893,15 +1946,13 @@ def _build_work_order_service_records_docx_bytes(work_order, related_tasks=None)
|
||||
from docx.shared import Cm
|
||||
|
||||
doc = _create_docx_document(SERVICE_REPORT_DOCX_TEMPLATE_NAME)
|
||||
vehicle = work_order.vehicle
|
||||
client_name = getattr(getattr(vehicle, 'client', None), 'name', None) or '-'
|
||||
servicer_name = _user_display_name(work_order.creator) or '-'
|
||||
completion_label = "Da ☒ Ne ☐" if work_order.status == 'closed' else "Da ☐ Ne ☒"
|
||||
|
||||
if related_tasks is None:
|
||||
related_tasks = list(_work_order_related_tasks_queryset(work_order).select_related('work_hours_table'))
|
||||
else:
|
||||
related_tasks = list(related_tasks)
|
||||
context = _build_service_report_docx_context(work_order, related_tasks)
|
||||
_populate_service_report_docx_header(doc, context)
|
||||
normalized_rows = []
|
||||
for task in related_tasks:
|
||||
table_data = getattr(getattr(task, 'work_hours_table', None), 'data', None)
|
||||
@@ -1961,17 +2012,17 @@ def _build_work_order_service_records_docx_bytes(work_order, related_tasks=None)
|
||||
|
||||
if len(doc.tables) >= 3:
|
||||
info_table = doc.tables[0]
|
||||
_set_docx_cell_text(info_table, 1, 0, client_name)
|
||||
_set_docx_cell_text(info_table, 1, 1, work_order.location or '-')
|
||||
_set_docx_cell_text(info_table, 3, 0, servicer_name)
|
||||
_set_docx_cell_text(info_table, 1, 0, context['client_name'])
|
||||
_set_docx_cell_text(info_table, 1, 1, context['location'])
|
||||
_set_docx_cell_text(info_table, 3, 0, context['servicer_name'])
|
||||
if len(info_table.rows[3].cells) > 1:
|
||||
_set_docx_cell_text(info_table, 3, 1, '-')
|
||||
|
||||
transport_table = doc.tables[1]
|
||||
_set_docx_cell_text(transport_table, 1, 0, work_order.servicer_vehicle_make_model or '-')
|
||||
_set_docx_cell_text(transport_table, 1, 1, work_order.servicer_vehicle_registration or '-')
|
||||
_set_docx_cell_text(transport_table, 1, 0, context['servicer_vehicle_make_model'])
|
||||
_set_docx_cell_text(transport_table, 1, 1, context['servicer_vehicle_registration'])
|
||||
_set_docx_cell_text(transport_table, 1, 2, '-')
|
||||
_set_docx_cell_text(transport_table, 1, 4, completion_label)
|
||||
_set_docx_cell_text(transport_table, 1, 4, context['completion_label'])
|
||||
|
||||
all_task_records = [record for task in related_tasks for record in records_by_task.get(task.id, [])]
|
||||
summary_table = doc.tables[2]
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useEffect, useMemo, useState } from 'preact/hooks';
|
||||
import { getStatusLabel } from '../../stores/taskStore';
|
||||
import {
|
||||
downloadMonthlyServiceTasksArchive,
|
||||
downloadMonthlyCostsReport,
|
||||
downloadMonthlyServiserReport,
|
||||
downloadMonthlyWorkOrdersArchive,
|
||||
fetchMonthlyCostInvoices,
|
||||
fetchMonthlyServicerEntries,
|
||||
upsertMonthlyServicerEntry,
|
||||
@@ -91,9 +89,6 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders
|
||||
const [manualEntryDraft, setManualEntryDraft] = useState(null);
|
||||
const [manualEntryError, setManualEntryError] = useState('');
|
||||
const [savingManualEntry, setSavingManualEntry] = useState(false);
|
||||
const [bulkDownloadOpen, setBulkDownloadOpen] = useState(false);
|
||||
const [downloadingAllTasks, setDownloadingAllTasks] = useState(false);
|
||||
const [downloadingAllWorkOrders, setDownloadingAllWorkOrders] = useState(false);
|
||||
|
||||
const reportOpen = reportType !== null;
|
||||
const panelWidth = reportOpen ? 920 : 300;
|
||||
@@ -346,9 +341,6 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders
|
||||
function toggleReport(type) {
|
||||
setReportType((prev) => (prev === type ? null : type));
|
||||
setManualEntryTarget(null);
|
||||
if (type !== 'servicer') {
|
||||
setBulkDownloadOpen(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownload() {
|
||||
@@ -365,28 +357,6 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadAllTasksArchive() {
|
||||
if (downloadingAllTasks) return;
|
||||
setDownloadingAllTasks(true);
|
||||
try {
|
||||
await downloadMonthlyServiceTasksArchive(viewYear, viewMonth + 1);
|
||||
setBulkDownloadOpen(false);
|
||||
} finally {
|
||||
setDownloadingAllTasks(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDownloadAllWorkOrdersArchive() {
|
||||
if (downloadingAllWorkOrders) return;
|
||||
setDownloadingAllWorkOrders(true);
|
||||
try {
|
||||
await downloadMonthlyWorkOrdersArchive(viewYear, viewMonth + 1);
|
||||
setBulkDownloadOpen(false);
|
||||
} finally {
|
||||
setDownloadingAllWorkOrders(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleServicerRowClick(row) {
|
||||
if (!row?.clickable) return;
|
||||
setManualEntryTarget(row);
|
||||
@@ -661,17 +631,8 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders
|
||||
disabled={downloading}
|
||||
className="rounded-md bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-indigo-700 disabled:opacity-60"
|
||||
>
|
||||
{downloading ? 'Preuzimanje...' : reportType === 'servicer' ? 'Preuzmi mjesečni izvještaj' : 'Preuzmi DOCX'}
|
||||
{downloading ? 'Preuzimanje...' : 'Preuzmi DOCX'}
|
||||
</button>
|
||||
{reportType === 'servicer' && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkDownloadOpen(true)}
|
||||
className="ml-2 rounded-md border border-border-hairline px-3 py-1.5 text-xs font-medium text-text-main hover:bg-canvas-deep"
|
||||
>
|
||||
Preuzmi sve
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="border-b border-border-hairline bg-canvas-deep px-4 py-1.5 text-[11px] text-text-muted">
|
||||
@@ -864,48 +825,6 @@ export default function TaskCalendarWidget({ tasks = [], notes = [], workOrders
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{bulkDownloadOpen && reportType === 'servicer' && (
|
||||
<div className="absolute inset-0 z-20 flex items-center justify-center bg-black/30 px-4">
|
||||
<div className="w-full max-w-md rounded-xl border border-border-hairline bg-canvas-elevated p-4 shadow-2xl">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-text-main">Preuzmi sve</h3>
|
||||
<p className="mt-1 text-xs text-text-muted">
|
||||
{MONTH_NAMES[viewMonth]} {viewYear}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setBulkDownloadOpen(false)}
|
||||
className="rounded p-1 text-text-muted hover:bg-canvas-deep"
|
||||
aria-label="Zatvori"
|
||||
>
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 grid gap-2">
|
||||
<button
|
||||
type="button"
|
||||
disabled={downloadingAllTasks || downloadingAllWorkOrders}
|
||||
onClick={handleDownloadAllTasksArchive}
|
||||
className="rounded-lg border border-border-hairline px-3 py-2 text-left text-sm hover:bg-indigo-50 disabled:opacity-60"
|
||||
>
|
||||
{downloadingAllTasks ? 'Generiranje...' : 'Preuzmi sve pojedinačne servisne taskove (SN)'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={downloadingAllTasks || downloadingAllWorkOrders}
|
||||
onClick={handleDownloadAllWorkOrdersArchive}
|
||||
className="rounded-lg border border-border-hairline px-3 py-2 text-left text-sm hover:bg-indigo-50 disabled:opacity-60"
|
||||
>
|
||||
{downloadingAllWorkOrders ? 'Generiranje...' : 'Preuzmi sve putne naloge + račune (PN)'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</LeftSlideDrawer>
|
||||
|
||||
Reference in New Issue
Block a user