{% extends "base.html" %} {% block title %}Kontakty zewnetrzne - Norda Biznes Hub{% endblock %} {% block meta_description %}Baza kontaktow zewnetrznych - urzedy, instytucje, partnerzy projektow. Dostepna dla czlonkow Norda Biznes.{% endblock %} {% block extra_css %} {% endblock %} {% block content %}

👥 Kontakty zewnetrzne

+ Dodaj kontakt
Znaleziono: {{ total }} kontaktow {% if search or org_type or project %} | Wyczysc filtry {% endif %}
{% if contacts %}
{% set contacts_by_org = {} %} {% for contact in contacts %} {% set org = contact.organization_name %} {% if org not in contacts_by_org %} {% set _ = contacts_by_org.update({org: {'contacts': [], 'type': contact.organization_type, 'logo': contact.organization_logo_url, 'website': contact.organization_website}}) %} {% endif %} {% set _ = contacts_by_org[org]['contacts'].append(contact) %} {% endfor %} {% for org_name, org_data in contacts_by_org.items() %}

{{ org_name }}

{{ org_type_labels.get(org_data.type, org_data.type) }} {{ org_data.contacts|length }} {% if org_data.contacts|length == 1 %}kontakt{% elif org_data.contacts|length < 5 %}kontakty{% else %}kontaktow{% endif %} {% if org_data.website %} 🌐 Strona WWW {% endif %}
{% for contact in org_data.contacts %}
{% if contact.photo_url %} {{ contact.full_name }} {% else %} {{ contact.first_name[0]|upper }} {% endif %}
{% if contact.position %}
{{ contact.position }}
{% endif %}
{% if contact.phone %} 📞 {{ contact.phone }} {% endif %} {% if contact.email %} ✉ Email {% endif %} Szczegoly →
{% endfor %}
{% endfor %}
{% for contact in contacts %}
{% if contact.photo_url %} {{ contact.full_name }} {% else %} {{ contact.first_name[0]|upper }} {% endif %}
{% if contact.position %}
{{ contact.position }}
{% endif %}
{{ contact.organization_name }} {{ org_type_labels.get(contact.organization_type, contact.organization_type) }}
{% if contact.phone %} {% endif %} {% if contact.email %} {% endif %}
{% if contact.has_social_media %} {% endif %} {% if contact.project_name %}
Projekt: {{ contact.project_name }}
{% endif %}
{% endfor %}
{% for contact in contacts %} {% endfor %}
Osoba Organizacja Stanowisko Kontakt Projekt
{% if contact.photo_url %} {{ contact.full_name }} {% else %} {{ contact.first_name[0]|upper }} {% endif %}
{{ contact.full_name }}
{{ contact.organization_name }} {{ org_type_labels.get(contact.organization_type, contact.organization_type) }} {{ contact.position or '-' }} {% if contact.phone %} {{ contact.phone }} {% endif %} {% if contact.phone and contact.email %}
{% endif %} {% if contact.email %} {{ contact.email }} {% endif %}
{{ contact.project_name or '-' }}
{% if total_pages > 1 %} {% endif %} {% else %}
📋

Brak kontaktow

{% if search or org_type or project %} Nie znaleziono kontaktow pasujacych do podanych kryteriow. {% else %} Baza kontaktow zewnetrznych jest pusta. Dodaj pierwszy kontakt! {% endif %}

{% endif %}
{% endblock %} {% block extra_js %} // View toggle const viewToggleBtns = document.querySelectorAll('.view-toggle button'); const viewContainers = { 'groups': document.getElementById('view-groups'), 'cards': document.getElementById('view-cards'), 'table': document.getElementById('view-table') }; // Load saved view preference const savedView = localStorage.getItem('contacts_view') || 'groups'; switchView(savedView); viewToggleBtns.forEach(btn => { btn.addEventListener('click', () => { const view = btn.dataset.view; switchView(view); localStorage.setItem('contacts_view', view); }); }); function switchView(view) { // Update buttons viewToggleBtns.forEach(b => b.classList.remove('active')); document.querySelector(`[data-view="${view}"]`)?.classList.add('active'); // Update containers Object.keys(viewContainers).forEach(v => { if (viewContainers[v]) { viewContainers[v].classList.remove('active'); if (v === view) { viewContainers[v].classList.add('active'); } } }); } // Organization group toggle function toggleOrgGroup(header) { const group = header.closest('.org-group'); group.classList.toggle('expanded'); } // AI Modal const aiModal = document.getElementById('aiModal'); const aiLoading = document.getElementById('aiLoading'); const aiInputSection = document.getElementById('aiInputSection'); const aiResults = document.getElementById('aiResults'); const aiParseBtn = document.getElementById('aiParseBtn'); const aiSaveBtn = document.getElementById('aiSaveBtn'); let parsedContacts = []; function openAiModal() { aiModal.classList.add('active'); resetAiModal(); } function closeAiModal() { aiModal.classList.remove('active'); resetAiModal(); } function resetAiModal() { document.getElementById('aiText').value = ''; removeImage(); aiInputSection.style.display = 'block'; aiResults.classList.remove('active'); aiParseBtn.style.display = 'inline-flex'; aiSaveBtn.style.display = 'none'; parsedContacts = []; } // File upload handling const fileUploadArea = document.getElementById('fileUploadArea'); const aiImageInput = document.getElementById('aiImage'); const imagePreview = document.getElementById('imagePreview'); const previewImg = document.getElementById('previewImg'); fileUploadArea.addEventListener('click', () => aiImageInput.click()); fileUploadArea.addEventListener('dragover', (e) => { e.preventDefault(); fileUploadArea.classList.add('dragover'); }); fileUploadArea.addEventListener('dragleave', () => { fileUploadArea.classList.remove('dragover'); }); fileUploadArea.addEventListener('drop', (e) => { e.preventDefault(); fileUploadArea.classList.remove('dragover'); const file = e.dataTransfer.files[0]; if (file && file.type.startsWith('image/')) { handleImageFile(file); } }); aiImageInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (file) { handleImageFile(file); } }); function handleImageFile(file) { if (file.size > 10 * 1024 * 1024) { alert('Plik jest za duzy. Maksymalny rozmiar to 10MB.'); return; } const reader = new FileReader(); reader.onload = (e) => { previewImg.src = e.target.result; imagePreview.classList.add('active'); }; reader.readAsDataURL(file); } function removeImage() { aiImageInput.value = ''; previewImg.src = ''; imagePreview.classList.remove('active'); } // AI parsing async function parseWithAi() { const text = document.getElementById('aiText').value.trim(); const imageData = previewImg.src && previewImg.src.startsWith('data:') ? previewImg.src : null; if (!text && !imageData) { alert('Wklej tekst lub dodaj obrazek do analizy.'); return; } aiLoading.classList.add('active'); try { const response = await fetch('/api/contacts/ai-parse', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token() }}' }, body: JSON.stringify({ text: text || null, image_data: imageData }) }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Blad analizy AI'); } displayAiResults(data); } catch (error) { alert('Blad: ' + error.message); } finally { aiLoading.classList.remove('active'); } } function displayAiResults(data) { parsedContacts = data.contacts || []; if (parsedContacts.length === 0) { alert('AI nie znalazlo zadnych kontaktow w podanych danych.'); return; } // Show results section aiInputSection.style.display = 'none'; aiResults.classList.add('active'); aiParseBtn.style.display = 'none'; aiSaveBtn.style.display = 'inline-flex'; // Display analysis document.getElementById('aiAnalysis').innerHTML = `Analiza AI: ${data.analysis || 'Znaleziono ' + parsedContacts.length + ' kontaktow.'}`; // Display contact proposals const proposalsHtml = parsedContacts.map((contact, index) => `
${getOrgTypeLabel(contact.organization_type)}
Organizacja: ${contact.organization_name || '-'}
${contact.position ? `
Stanowisko: ${contact.position}
` : ''} ${contact.phone ? `
Telefon: ${contact.phone}
` : ''} ${contact.email ? `
Email: ${contact.email}
` : ''} ${contact.project_name ? `
Projekt: ${contact.project_name}
` : ''}
`).join(''); document.getElementById('aiProposals').innerHTML = proposalsHtml; } function getOrgTypeLabel(type) { const labels = { 'government': 'Urzad', 'agency': 'Agencja', 'company': 'Firma', 'ngo': 'NGO', 'university': 'Uczelnia', 'other': 'Inne' }; return labels[type] || type || 'Inne'; } async function saveSelectedContacts() { const checkboxes = document.querySelectorAll('#aiProposals input[type="checkbox"]:checked'); const selectedIndices = Array.from(checkboxes).map(cb => parseInt(cb.dataset.index)); if (selectedIndices.length === 0) { alert('Wybierz co najmniej jeden kontakt do zapisania.'); return; } const contactsToSave = selectedIndices.map(i => parsedContacts[i]); aiLoading.classList.add('active'); try { const response = await fetch('/api/contacts/bulk-create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': '{{ csrf_token() }}' }, body: JSON.stringify({ contacts: contactsToSave }) }); const data = await response.json(); if (!response.ok) { throw new Error(data.error || 'Blad zapisywania kontaktow'); } alert(`Zapisano ${data.created} kontaktow!`); closeAiModal(); window.location.reload(); } catch (error) { alert('Blad: ' + error.message); } finally { aiLoading.classList.remove('active'); } } // Close modal on overlay click aiModal.addEventListener('click', (e) => { if (e.target === aiModal) { closeAiModal(); } }); // Close modal on Escape document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && aiModal.classList.contains('active')) { closeAiModal(); } }); {% endblock %}