-
-
Save CodeNinjaUG/d2ffccbf727ff6a3836f0f5fa05dc087 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const CustomSelect = { | |
template: ` | |
<div> | |
<div class="custom-selection" @click="toggleDropdown"> | |
<div class="selected-pills"> | |
<span v-for="(category, index) in selectedCategories" :key="index" class="small-pill"> | |
{{ category }} | |
<button type="button" class="close-btn" @click.stop="removeCategory(category)">×</button> | |
</span> | |
</div> | |
<div v-if="dropdownVisible" class="dropdown-options"> | |
<ul> | |
<li | |
v-for="subcategory in subcategories" | |
:key="subcategory.subcategory" | |
@click.stop="selectCategory(subcategory.subcategory)" | |
:class="{ selected: isSelected(subcategory.subcategory) }" | |
> | |
{{ subcategory.subcategory }} | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
`, | |
props: { | |
thematic_id: { | |
type: String, | |
required: true | |
}, | |
value: { | |
type: Array, | |
default: () => [] | |
}, | |
subcategories: { | |
type: Array, | |
required: true | |
} | |
}, | |
data() { | |
return { | |
selectedCategories: [...this.value], | |
dropdownVisible: false, | |
}; | |
}, | |
watch: { | |
value(newValue) { | |
this.selectedCategories = [...newValue]; | |
} | |
}, | |
computed: { | |
filters() { | |
return this.selectedCategories.length; | |
}, | |
filteredSubcategories() { | |
return this.subcategories.filter(sub => sub.thematic_area_id === this.thematic_id); | |
} | |
}, | |
methods: { | |
toggleDropdown() { | |
this.dropdownVisible = !this.dropdownVisible; | |
}, | |
selectCategory(category) { | |
const index = this.selectedCategories.indexOf(category); | |
if (index !== -1) { | |
this.selectedCategories = this.selectedCategories.filter((c) => c !== category); | |
} else { | |
this.selectedCategories.push(category); | |
} | |
this.emitChange(); | |
}, | |
removeCategory(category) { | |
this.selectedCategories = this.selectedCategories.filter((c) => c !== category); | |
this.emitChange(); | |
}, | |
isSelected(category) { | |
return this.selectedCategories.includes(category); | |
}, | |
emitChange() { | |
this.$emit('change', this.selectedCategories); // Emit the updated categories | |
} | |
} | |
}; | |
const app = Vue.createApp( | |
{ | |
components: { | |
CustomSelect, | |
}, | |
data() | |
{ | |
return { | |
radius: 240, | |
details: details, | |
selectedElements: [], | |
items: thematic_areas, | |
elementtitle: "", | |
entity_url: entity_url, | |
thematic_map_id: map_id, | |
map_sub_categories: subcategories, | |
EntityModal: false, | |
hoveredElement: null, | |
fetchedsubs: [], | |
allSubcategories : [], | |
selectedSection: null, | |
SelectedLogoFile: null, | |
selectedCategory: null, | |
tempSelectedSubcategories: [], | |
selectedSubcategories:{}, | |
subcategories: [], | |
checkedSubcategories: [], | |
logo_url: null, | |
email: "", | |
location: "", | |
telephone: "", | |
description: "", | |
organisation: "", | |
acronym: "", | |
youtube: "", | |
instagram: "", | |
twitter: "", | |
facebook: "", | |
linkedin: "", | |
searchTerm: "", | |
thematic_areas_arr: thematic_areas, | |
filteredentities: null, | |
showModal: false, | |
tableCurrentPage: 1, | |
tableItemsPerPage: 5, | |
currentPage: 1, | |
itemsPerPage: 4, | |
allentities: entities, | |
svgarray: phpData, | |
subcategories: subcategories, | |
svgElements: [], | |
searchExpanded: false, | |
windowWidth: window.innerWidth, | |
wasMobile: window.innerWidth <= 768, | |
showEntityModal: false, // Controls modal visibility | |
selectedEntity: {}, // Holds the selected entity's data | |
entities: [], | |
}; | |
}, | |
computed: | |
{ | |
isMobile() | |
{ | |
return this.windowWidth <= 768; // Threshold for mobile devices | |
}, | |
groupedCategories() | |
{ | |
const grouped = this.filteredElements | |
.flatMap((entity) => entity.subcategories) | |
.reduce((acc, category) => | |
{ | |
const | |
{ | |
thematic_area_id, | |
thematic_area_name | |
} = category; | |
if (!acc[thematic_area_id]) | |
{ | |
acc[thematic_area_id] = { | |
thematic_area_id, | |
thematic_area_name, | |
subcategories: [], | |
}; | |
} | |
acc[thematic_area_id].subcategories.push( | |
{ | |
subcategory_id: category.subcategory_id, | |
subcategory_name: category.subcategory_name, | |
}); | |
return acc; | |
}, | |
{}); | |
return Object.values(grouped); | |
}, | |
filteredElements() | |
{ | |
let result = this.allentities; | |
if (this.selectedElements.length > 0) | |
{ | |
result = result.filter((entity) => | |
{ | |
return entity.thematic_areas.some((thematicArea) => | |
{ | |
return thematicArea.subcategories.some((subcategory) => | |
{ | |
return this.selectedElements.some( | |
(selectedElement) => subcategory.subcategory_id === selectedElement.subcategory_id | |
); | |
}); | |
}); | |
}); | |
} | |
// Apply the subcategory filter from Select2 (for each thematic area) | |
Object.keys(this.selectedSubcategories).forEach((thematic_area_id) => | |
{ | |
const selectedSubcats = this.selectedSubcategories[thematic_area_id]; | |
if (selectedSubcats && selectedSubcats.length > 0) | |
{ | |
result = result.filter((entity) => | |
entity.thematic_areas.some( | |
(thematicArea) => | |
thematicArea.thematic_area_id == thematic_area_id && | |
thematicArea.subcategories.some((subcategory) => | |
selectedSubcats.includes(subcategory.subcategory_id) | |
) | |
) | |
); | |
} | |
}); | |
// Apply search filter | |
if (this.searchTerm.trim() !== "") | |
{ | |
const searchLower = this.searchTerm.toLowerCase().trim(); | |
result = result.filter((entity) => | |
entity.organisation.toLowerCase().includes(searchLower) | |
); | |
} | |
return result; | |
}, | |
paginateElements() | |
{ | |
const startIndex = (this.currentPage - 1) * this.itemsPerPage; | |
const endIndex = startIndex + this.itemsPerPage; | |
return this.filteredElements.slice(startIndex, endIndex); | |
}, | |
totalPages() | |
{ | |
return Math.ceil(this.filteredElements.length / this.itemsPerPage); | |
}, | |
angleStep() | |
{ | |
return 180 / this.items.length; // Adjust angle for right half-circle | |
}, | |
labelRadius() | |
{ | |
return this.radius * 0.7; // Adjust label position | |
}, | |
getOuterCirclePath() | |
{ | |
const outerRadius = this.radius * 1.1; // Decreased radius for the outer circle | |
return ` | |
M ${outerRadius} 0 | |
A ${outerRadius} ${outerRadius} 0 0 1 ${-outerRadius} 0 | |
`; | |
}, | |
getExtraCirclePath() | |
{ | |
const extraRadius = this.radius * 1.25; // Decreased radius for the extra circle | |
return ` | |
M ${extraRadius} 0 | |
A ${extraRadius} ${extraRadius} 0 0 1 ${-extraRadius} 0 | |
`; | |
}, | |
paginatedTableData() | |
{ | |
const startIndex = (this.tableCurrentPage - 1) * this.tableItemsPerPage; | |
const endIndex = startIndex + this.tableItemsPerPage; | |
return this.filteredElements.slice(startIndex, endIndex); | |
}, | |
totalTablePages() | |
{ | |
return Math.ceil(this.filteredElements.length / this.tableItemsPerPage); | |
}, | |
getResetPath() | |
{ | |
const startX = this.radius * 0.3 * Math.cos(0); | |
const startY = this.radius * 0.3 * Math.sin(0); | |
const endX = this.radius * 0.3 * Math.cos(Math.PI); | |
const endY = this.radius * 0.3 * Math.sin(Math.PI); | |
return ` | |
M ${startX} ${startY} | |
A ${this.radius * 0.3} ${this.radius * 0.3} 0 0 1 ${endX} ${endY} | |
L 0 0 | |
Z | |
`; | |
}, | |
getResetSpacePath() | |
{ | |
const outerRadius = this.radius * 0.35; // Slightly larger radius for the white space | |
const startX = outerRadius * Math.cos(0); | |
const startY = outerRadius * Math.sin(0); | |
const endX = outerRadius * Math.cos(Math.PI); | |
const endY = outerRadius * Math.sin(Math.PI); | |
return ` | |
M ${startX} ${startY} | |
A ${outerRadius} ${outerRadius} 0 0 1 ${endX} ${endY} | |
L ${this.radius * 0.3 * Math.cos(Math.PI)} ${this.radius * 0.3 * Math.sin(Math.PI)} | |
A ${this.radius * 0.3} ${this.radius * 0.3} 0 0 0 ${this.radius * 0.3 * Math.cos(0)} ${this.radius * 0.3 * Math.sin(0)} | |
Z | |
`; | |
}, | |
}, | |
methods: | |
{ | |
openEntityModal(entity) { | |
this.selectedEntity = entity; | |
this.showEntityModal = true; | |
}, | |
closeModal() { | |
this.showEntityModal = false; | |
}, | |
updateSelectedSubcategories(thematic_area_id, event) | |
{ | |
// console.log(thematic_area_id); | |
// const subcategory = this.selectedElements.find((sub) => sub.thematic_area_id === thematic_area_id); | |
// if (subcategory) { | |
// } else { | |
// this.selectedElements.push( | |
// { | |
// thematic_area_id : thematic_area_id | |
// } | |
// ) | |
// } | |
//const selectedOptions = Array.from(event.target.selectedOptions).map((option) => option.value); | |
//this.$set(this.selectedSubcategories, thematic_area_id, selectedOptions); | |
this.filterEntities(); | |
}, | |
isSelected(thematic_area_id, subcategory_id) | |
{ | |
return this.selectedSubcategories[thematic_area_id] && | |
this.selectedSubcategories[thematic_area_id].includes(subcategory_id); | |
}, | |
handleComputedValue(value) { | |
console.log(value); | |
}, | |
getContrastingTextColor(hexColor) | |
{ | |
// Convert hex color to RGB | |
const r = parseInt(hexColor.slice(1, 3), 16); | |
const g = parseInt(hexColor.slice(3, 5), 16); | |
const b = parseInt(hexColor.slice(5, 7), 16); | |
// Calculate brightness based on RGB values | |
const brightness = (r * 299 + g * 587 + b * 114) / 1000; | |
// If brightness is above the threshold (128), return black, else white | |
return brightness > 128 ? 'black' : 'white'; | |
}, | |
handleResize() | |
{ | |
this.windowWidth = window.innerWidth; | |
const isCurrentlyMobile = this.windowWidth <= 768; | |
if (isCurrentlyMobile !== this.wasMobile) | |
{ | |
this.wasMobile = isCurrentlyMobile; | |
window.location.reload(); | |
} | |
}, | |
updateCheckedSubcategories(item) | |
{ | |
this.tempSelectedSubcategories.forEach((subcategoryId) => | |
{ | |
const subcategory = this.subcategories.find((sub) => sub.subcategory_id === subcategoryId); | |
if (subcategory && !this.checkedSubcategories.find((sub) => sub.subcategory_id === sub.subcategory_id)) | |
{ | |
this.checkedSubcategories.push( | |
{ | |
subcategory_id: subcategory.subcategory_id, | |
subcategory: subcategory.subcategory, | |
thematic_area_id: subcategory.thematic_area_id, | |
thematic_area: subcategory.thematic_area, | |
}); | |
} | |
}); | |
this.checkedSubcategories = this.checkedSubcategories.filter((sub) => this.tempSelectedSubcategories.includes(sub.subcategory_id)); | |
}, | |
limitText(text, maxLength) | |
{ | |
if (text.length > maxLength) | |
{ | |
return text.slice(0, maxLength) + "..."; | |
} | |
return text; | |
}, | |
handleSearch() | |
{ | |
this.currentPage = 1; // Reset to first page when searching | |
this.searchExpanded = false; // Hide the overlay once the search is performed | |
}, | |
fetchAllSubcategories(category_id) | |
{ | |
this.fetchedsubs = this.subcategories.filter((obj) => obj.thematic_area_id === category_id); | |
}, | |
displayEntityModal() | |
{ | |
this.EntityModal = true; | |
}, | |
updateCheckedSubcategories() | |
{ | |
this.selectedSubcategories.forEach((subcategoryId) => | |
{ | |
const subcategory = this.subcategories.find((sub) => sub.subcategory_id === subcategoryId); | |
if (subcategory && !this.checkedSubcategories.find((sub) => sub.subcategory_id === sub.subcategory_id)) | |
{ | |
this.checkedSubcategories.push( | |
{ | |
subcategory_id: subcategory.subcategory_id, | |
subcategory: subcategory.subcategory, | |
thematic_area_id: subcategory.thematic_area_id, | |
thematic_area: subcategory.thematic_area, | |
}); | |
} | |
}); | |
this.checkedSubcategories = this.checkedSubcategories.filter((sub) => this.selectedSubcategories.includes(sub.subcategory_id)); | |
}, | |
showElementName(element) | |
{ | |
this.elementtitle = element.subcategory; | |
}, | |
ViewFilteredTable() | |
{ | |
this.showModal = true; | |
this.tableCurrentPage = 1; | |
}, | |
nextTablePage() | |
{ | |
if (this.tableCurrentPage < this.totalTablePages) | |
{ | |
this.tableCurrentPage++; | |
} | |
}, | |
prevTablePage() | |
{ | |
if (this.tableCurrentPage > 1) | |
{ | |
this.tableCurrentPage--; | |
} | |
}, | |
nextPage() | |
{ | |
if (this.currentPage < this.totalPages) | |
{ | |
this.currentPage++; | |
} | |
}, | |
viewEntity(id) | |
{ | |
const baseUrl = window.location.origin; | |
const newPath = `/view-entity/${id}`; | |
const url = new URL(newPath, baseUrl).href; | |
window.open(url, "_blank"); | |
}, | |
idExists(arr, id) | |
{ | |
return arr.some((obj) => obj.id === id); | |
}, | |
prevPage() | |
{ | |
if (this.currentPage > 1) | |
{ | |
this.currentPage--; | |
} | |
}, | |
getPath(index) | |
{ | |
const startAngle = index * this.angleStep; | |
const endAngle = startAngle + this.angleStep; | |
const startX = this.radius * Math.cos((startAngle * Math.PI) / 180); | |
const startY = this.radius * Math.sin((startAngle * Math.PI) / 180); | |
const endX = this.radius * Math.cos((endAngle * Math.PI) / 180); | |
const endY = this.radius * Math.sin((endAngle * Math.PI) / 180); | |
return ` | |
M 0 0 | |
L ${startX} ${startY} | |
A ${this.radius} ${this.radius} 0 0 1 ${endX} ${endY} | |
Z | |
`; | |
}, | |
removeItem(index) | |
{ | |
if (index === -1) | |
{ | |
console.log("index doesnt exist"); | |
} | |
else | |
{ | |
this.selectedElements.splice(index, 1); | |
this.removeEntities(index); | |
} | |
}, | |
toggleElementSelection(item) | |
{ | |
const index = this.selectedElements.indexOf(item); | |
if (index === -1) | |
{ | |
this.selectedElements.push(item); | |
} | |
else | |
{ | |
this.selectedElements.splice(index, 1); | |
} | |
}, | |
showSelected(selected) | |
{ | |
const index = this.selectedElements.findIndex((elem) => elem.subcategory === selected.subcategory); | |
if (index === -1) | |
{ | |
this.selectedElements.push(selected); | |
} | |
else | |
{ | |
this.selectedElements.splice(index, 1); | |
this.removeEntities(index); | |
} | |
}, | |
filterEntities() | |
{ | |
this.filteredentities = this.filteredElements; | |
}, | |
removeEntities(selectedElement) | |
{ | |
this.filteredElements.forEach((entity) => | |
{ | |
entity.subcategories.forEach((subcategory) => | |
{ | |
if (subcategory.subcategory_id === selectedElement.subcategory_id && subcategory.subcategory_name === selectedElement.subcategory) | |
{ | |
const index = this.filterEntities.findIndex((item) => item.entity_id === entity.id); | |
console.log(index); | |
if (index !== -1) | |
{ | |
this.filterEntities.splice(index, 1); // Remove the entity from the result array | |
} | |
} | |
}); | |
}); | |
}, | |
showHover(element, event) | |
{ | |
console.log(element, event); | |
}, | |
truncateText(text) | |
{ | |
const maxLength = 10; // Example max length per line | |
const lines = []; | |
let currentLine = ""; | |
text.split(" ").forEach((word) => | |
{ | |
if (currentLine.length + word.length + 1 <= maxLength) | |
{ | |
currentLine += (currentLine ? " " : "") + word; | |
} | |
else | |
{ | |
lines.push(currentLine); | |
currentLine = word; | |
} | |
}); | |
if (currentLine) | |
{ | |
lines.push(currentLine); | |
} | |
return lines; | |
}, | |
submitForm() | |
{ | |
const formData = new FormData(); | |
formData.append("organisation", this.organisation); | |
formData.append("image", this.SelectedLogoFile); | |
formData.append("acronym", this.acronym); | |
formData.append("facebook", this.facebook); | |
formData.append("instagram", this.instagram); | |
formData.append("twitter", this.twitter); | |
formData.append("linkedin", this.linkedin); | |
formData.append("youtube", this.youtube); | |
formData.append("telephone", this.telephone); | |
formData.append("email", this.email); | |
formData.append("location", this.location); | |
formData.append("description", this.description); | |
formData.append("selectedCategory", this.selectedCategory); | |
formData.append("selectedSubcategories", JSON.stringify(this.selectedSubcategories)); | |
axios | |
.post(entity_url, formData, | |
{ | |
headers: | |
{ | |
"Content-Type": "multipart/form-data", | |
}, | |
}) | |
.then((response) => | |
{ | |
if ((response.data.success = true)) | |
{ | |
this.organisation = ""; | |
this.description = ""; | |
this.selectedFile = null; | |
this.email = ""; | |
this.location = ""; | |
this.telephone = ""; | |
this.acronym = ""; | |
this.youtube = ""; | |
this.instagram = ""; | |
this.twitter = ""; | |
this.facebook = ""; | |
this.linkedin = ""; | |
this.selectedCategory = null; | |
this.selectedSubcategories = []; | |
this.EntityModal = false; | |
alert("Entity saved successfully!"); | |
} | |
else | |
{ | |
alert("An error occurred while saving the entity."); | |
} | |
}) | |
.catch((error) => | |
{ | |
alert("An error occurred while saving the entity."); | |
}); | |
}, | |
onFilelogoChange(event) | |
{ | |
const file = event.target.files[0]; | |
this.SelectedLogoFile = file; | |
this.logo_url = URL.createObjectURL(this.SelectedLogoFile); | |
}, | |
handleClick(index, color, item) | |
{ | |
this.svgElements = this.subcategories.filter((it) => it.thematic_area_id === item.thematic_area_id); | |
this.selectedSection = index; // Set selected section index | |
this.updateSVGElementsPosition(index, color); | |
}, | |
updateSVGElementsPosition(index, color) | |
{ | |
const outerRadius = this.radius * 1.2; | |
const numberOfElements = this.svgElements.length; | |
const angleIncrement = Math.PI / (numberOfElements * 2); | |
this.svgElements.forEach((element, idx) => | |
{ | |
const angle = angleIncrement * idx + Math.PI / 4; | |
element.x = outerRadius * Math.cos(angle); | |
element.y = outerRadius * Math.sin(angle); | |
element.angle = angle; | |
element.color = color; | |
}); | |
}, | |
reset() | |
{ | |
this.selectedSection = null; | |
this.selectedElements = []; | |
this.svgElements.forEach((element) => | |
{ | |
element.x = 0; | |
element.y = 0; | |
element.color = "#FFFFFF"; | |
}); | |
}, | |
exportFilteredDataAsCSV() | |
{ | |
const columns = ["organisation", "accronym", "description", "thematic_areas", "contact details"]; | |
let csvContent = "data:text/csv;charset=utf-8,"; | |
csvContent += columns.join(",") + "\n"; | |
if (this.filteredElements && this.filteredElements.length > 0) | |
{ | |
this.filteredElements.forEach((item) => | |
{ | |
let row = []; | |
columns.forEach((column) => | |
{ | |
if (column === "thematic_areas") | |
{ | |
row.push(item[column] ? item[column].map((area) => area.thematic_area_name).join("; ") : ""); | |
} | |
else if (column === "contact details") | |
{ | |
let contactDetails = [ | |
item.contactemail, | |
item.instagram, | |
item.twitter, | |
item.facebook, | |
item.youtube, | |
item.telephone, | |
item.location, | |
item.linkedin, | |
] | |
.filter(Boolean) | |
.join("; "); | |
row.push(`"${contactDetails.replace(/"/g, '""')}"`); | |
} | |
else | |
{ | |
row.push(item[column] ? `"${item[column].toString().replace(/"/g, '""')}"` : ""); | |
} | |
}); | |
csvContent += row.join(",") + "\n"; | |
}); | |
const encodedUri = encodeURI(csvContent); | |
const link = document.createElement("a"); | |
link.setAttribute("href", encodedUri); | |
link.setAttribute("download", "filtered_data.csv"); | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
console.log("CSV generation attempted"); | |
} | |
else | |
{ | |
console.error("No data available in this.filteredElements"); | |
} | |
}, | |
removeLogoImage() | |
{ | |
this.SelectedLogoFile = null; | |
this.logo_url = null; | |
}, | |
getSVGElementTransform(index) | |
{ | |
const element = this.svgElements[index]; | |
return `translate(${element.x},${element.y}) rotate(${(element.angle * 180) / Math.PI})`; | |
}, | |
zoomIn(event) | |
{ | |
event.currentTarget.style.transform = 'scale(1.05)'; | |
}, | |
zoomOut(event) | |
{ | |
event.currentTarget.style.transform = 'scale(1)'; | |
}, | |
expandSearch() | |
{ | |
this.searchExpanded = true; | |
}, | |
shrinkSearch() | |
{ | |
if (!this.searchTerm.trim()) | |
{ | |
this.searchExpanded = false; // Hide overlay if the input is empty and blurred | |
} | |
}, | |
loadHtml2Canvas() | |
{ | |
return new Promise((resolve, reject) => | |
{ | |
if (window.html2canvas) | |
{ | |
// If html2canvas is already loaded, resolve the promise | |
resolve(); | |
} | |
else | |
{ | |
// Create a script element | |
const script = document.createElement('script'); | |
script.src = "https://cdnjs.cloudflare.com/ajax/libs/html2canvas/0.4.1/html2canvas.min.js"; | |
script.onload = () => resolve(); // Resolve when script is loaded | |
script.onerror = () => reject(new Error('Failed to load html2canvas')); | |
document.head.appendChild(script); // Append the script to the head | |
} | |
}); | |
}, | |
generateCardImage(cardId) | |
{ | |
this.loadHtml2Canvas() | |
.then(() => | |
{ | |
const cardElement = document.getElementById(cardId); | |
if (cardElement) | |
{ | |
html2canvas(cardElement).then((canvas) => | |
{ | |
const link = document.createElement("a"); | |
link.href = canvas.toDataURL("image/png"); | |
link.download = "card.png"; | |
link.click(); | |
}); | |
} | |
}) | |
.catch((error) => | |
{ | |
console.error('Error loading html2canvas:', error); | |
}); | |
}, | |
mounted() | |
{ | |
window.addEventListener("resize", this.handleResize); | |
this.handleResize(); // Ensure the initial state is set based on window size | |
this.$nextTick(() => | |
{ | |
this.initializeSelect2(); // Initialize Select2 after DOM is rendered | |
}); | |
}, | |
beforeUnmount() | |
{ | |
window.removeEventListener("resize", this.handleResize); | |
}, | |
}, | |
watch: | |
{ | |
selectedSubcategories(newVal, oldVal) | |
{ | |
this.updateCheckedSubcategories(); | |
}, | |
windowWidth() | |
{ | |
this.handleResize(); | |
}, | |
}, | |
template: ` | |
<!-- Modal structure with entity details --> | |
<div v-if="showEntityModal" class="modal d-block" tabindex="-1" @click.self="showEntityModal = false" role="dialog"> | |
<div class="modal-dialog modal-lg" role="document" style="max-width: 1000px;"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title" style="font-family: Helvetica, sans-serif; font-weight: bold; font-size: 20px;"> | |
{{ selectedEntity.organisation }} Profile | |
</h5> | |
<a class="btn btn-sm btn-outline" @click="showEntityModal = false"> | |
<em class="icon ni ni-cross"></em> | |
</a> | |
</div> | |
<div class="modal-body"> | |
<div class="row" style="display: flex; flex-wrap: wrap;"> | |
<!-- Left column for the avatar and contact details --> | |
<div class="col-xl-4 col-lg-4 col-md-12 mb-4"> | |
<div class="card h-100" style="border: 1px solid #f0f0f0; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); background-color: #f8f9fa;"> | |
<div class="card-inner-group text-center p-4"> | |
<div class="card-inner"> | |
<div class="user-card user-card-s2"> | |
<div class="user-avatar lg bg-info rounded-circle d-flex justify-content-center align-items-center" style="width: 120px; height: 120px; overflow: hidden; margin: auto;"> | |
<img v-if="selectedEntity.logo_url" :src="selectedEntity.logo_url" alt="Entity Logo" style="width: 100%; object-fit: cover;"> | |
<span v-else style="color: white; font-size: 40px; font-weight: bold;"> | |
{{ selectedEntity.organisation.slice(0, 2).toUpperCase() }} | |
</span> | |
</div> | |
<div class="user-info mt-3"> | |
<div class="badge bg-light rounded-pill ucap">{{ selectedEntity.acronym }}</div> | |
<h5 class="mt-2 mb-1" style="font-size: 20px; font-weight: bold; font-family: Helvetica, sans-serif;"> | |
{{ selectedEntity.organisation }} | |
</h5> | |
<span class="sub-text">{{ selectedEntity.contactemail }}</span> | |
</div> | |
</div> | |
</div> | |
<div class="card-inner card-inner-sm mt-3"> | |
<ul class="btn-toolbar justify-center gx-2"> | |
<li v-if="selectedEntity.facebook"> | |
<a :href="selectedEntity.facebook" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-facebook-fill"></em> | |
</a> | |
</li> | |
<li v-if="selectedEntity.twitter"> | |
<a :href="selectedEntity.twitter" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-twitter-round"></em> | |
</a> | |
</li> | |
<li v-if="selectedEntity.instagram"> | |
<a :href="selectedEntity.instagram" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-instagram-round"></em> | |
</a> | |
</li> | |
<li v-if="selectedEntity.linkedin"> | |
<a :href="selectedEntity.linkedin" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-linkedin-round"></em> | |
</a> | |
</li> | |
<li v-if="selectedEntity.youtube"> | |
<a :href="selectedEntity.youtube" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-youtube-round"></em> | |
</a> | |
</li> | |
<li v-if="selectedEntity.telephone"> | |
<a :href="'tel:' + selectedEntity.telephone" class="btn btn-trigger btn-icon text-info"> | |
<em class="icon ni ni-call-alt-fill"></em> | |
</a> | |
</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Right column for the description and thematic areas --> | |
<div class="col-xl-8 col-lg-8 col-md-12"> | |
<div class="card h-100" style="border: 1px solid #f0f0f0; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); width: 100%;"> | |
<div class="card-inner p-4"> | |
<!-- Description Section --> | |
<div class="nk-block mb-4"> | |
<h6 class="text-muted" style="font-weight: 600; font-family: Helvetica, sans-serif; font-size: 18px;">Description</h6> | |
<div class="media-content mt-2"> | |
<p style="text-align:justify; color:black; line-height: 1.6;">{{ selectedEntity.description }}</p> | |
</div> | |
</div> | |
<!-- Thematic Areas and Subcategories Section --> | |
<div class="nk-block"> | |
<h6 class="text-muted" style="font-weight: 600; font-family: Helvetica, sans-serif; font-size: 18px;">Categorisation</h6> | |
<!-- 2x2 Grid for Thematic Areas --> | |
<div class="row g-3 mt-3" style="display: flex; flex-wrap: wrap;"> | |
<div v-for="thematic in selectedEntity.thematic_areas" :key="thematic.thematic_area_id" class="col-md-6 d-flex align-items-stretch" style="padding: 10px;"> | |
<div class="card mb-3 border-light" style="background-color: #f8f9fa; border-radius: 5px; flex-grow: 1; height: 100%; padding: 5px;"> | |
<div class="card-inner d-flex align-items-center p-3"> | |
<div class="icon-circle icon-circle-lg bg-light text-info"> | |
<em class="icon ni ni-layers"></em> | |
</div> | |
<div class="ms-3"> | |
<div class="lead-text" style="font-weight: 600; font-family: Helvetica, sans-serif; ">{{ thematic.thematic_area_name }}</div> | |
<span v-for="(subcategory, index) in thematic.subcategories" :key="subcategory.subcategory_id" class="sub-text text-muted"> | |
{{ subcategory.subcategory_name }}<span v-if="index < thematic.subcategories.length - 1">, </span> | |
</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="modal-footer"> | |
<button type="button" class="btn btn-outline-info" @click="showEntityModal = false">Close</button> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div v-if="EntityModal"> | |
<div class="modal" @click.self="EntityModal = false" :style="{ display: EntityModal ? 'block' : 'none' }"> | |
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg" role="document"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title" style="font-family: Helvetica, sans-serif; font-weight: bold; font-size: 20px;"> | |
Submit Entity to the Mapping | |
</h5> | |
<a href="#" class="close" @click="EntityModal = false" aria-label="Close"> | |
<em class="icon ni ni-cross"></em> | |
</a> | |
</div> | |
<div class="modal-body"> | |
<form method="post" @submit.prevent="submitForm()"> | |
<div class="row gy-4"> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Organisation Name</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-01" v-model="organisation" placeholder="Enter Organisation Name" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Acronym</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-05" v-model="acronym" placeholder="Enter Acronym" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label"> Facebook </label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-03" v-model="facebook" placeholder="Enter Facebook" required> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Instagram</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-04" v-model="instagram" placeholder="Enter Instagram" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">LinkedIn</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-03" v-model="linkedin" placeholder="Enter LinkedIn" required> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Twitter</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-04" v-model="twitter" placeholder="Enter Twitter" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Youtube</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-03" v-model="youtube" placeholder="Enter Youtube" required> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Telephone</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-04" placeholder="Enter Telephone" v-model="telephone" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Contact Email</label> | |
<div class="form-control-wrap"> | |
<input type="email" class="form-control" id="default-03" placeholder="Enter Contact Email" v-model="email" required> | |
</div> | |
</div> | |
<div class="form-group"> | |
<label class="form-label">Location Base / Office Location</label> | |
<div class="form-control-wrap"> | |
<input type="text" class="form-control" id="default-04" placeholder="Enter Location Base" v-model="location" required> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-12"> | |
<div class="form-group"> | |
<label class="form-label" for="default-textarea">Upload Company Logo</label> | |
<div class="form-control-wrap"> | |
<div v-if="!SelectedLogoFile" class="form-file"> | |
<input type="file" @change="onFilelogoChange" class="form-file-input" id="customFile"> | |
<label class="form-file-label" for="customFile">Choose file</label> | |
</div> | |
<div class="d-flex flex-column mt-3" v-if="logo_url"> | |
<img class="h-40 w-40" v-if="logo_url" :src="logo_url" /> | |
<a role="button" class="btn btn-xs btn-primary col-md-3 mt-4" @click="removeLogoImage">Remove Image</a> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Select Categories </label> | |
<div class="form-control-select"> | |
<select v-model="selectedCategory" @change="fetchAllSubcategories(selectedCategory)" class="form-select" id="default-07" aria-label="select example" required> | |
<option disabled value="">Please select a category</option> | |
<template v-for="item in thematic_areas_arr" :key="item.id"> | |
<option :value="item.thematic_area_id"> {{ item.thematic_area }}</option> | |
</template> | |
</select> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<div class="form-group"> | |
<label class="form-label">Select SubCategories </label> | |
<div class="form-control-select-multiple" style="max-height: 200px; overflow-y: auto;"> | |
<div v-for="sub in fetchedsubs" :key="sub.subcategory_id"> | |
<input | |
type="checkbox" | |
:id="'subcategory-' + sub.subcategory_id" | |
:value="sub.subcategory_id" | |
v-model="selectedSubcategories" | |
@change="updateCheckedSubcategories" | |
> | |
<label :for="'subcategory-' + sub.subcategory_id">{{ sub.subcategory }}</label> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-12"> | |
<div class="form-group"> | |
<select class="form-select" multiple="" id="default-07" aria-label="select example"> | |
<option value="option_select0"> Checked SubCategories </option> | |
<option v-for="subchecked in checkedSubcategories" :value="subchecked.subcategory_id"> {{ subchecked.subcategory }}</option> | |
</select> | |
</div> | |
</div> | |
<div class="col-sm-12"> | |
<div class="form-group"> | |
<label class="form-label" for="default-textarea">Organisation Description</label> | |
<div class="form-control-wrap"> | |
<textarea v-model="description" class="form-control no-resize" id="default-textarea" required></textarea> | |
</div> | |
</div> | |
</div> | |
<div class="col-sm-6"> | |
<button type="submit" name="submit" class="btn btn-info">Save Entities</button> | |
</div> | |
</div> | |
</form> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Modal --> | |
<div v-if="showModal" class="modal d-block" tabindex="-1" @click.self="showModal = false" role="dialog"> | |
<div class="modal-dialog modal-lg" role="document"> | |
<div class="modal-content"> | |
<div class="modal-header"> | |
<h5 class="modal-title" style="font-family: Helvetica, sans-serif; font-weight: bold; font-size: 20px;"> | |
{{ details.map_name }} Data Table | |
</h5> | |
<a @click="exportFilteredDataAsCSV" class="modal-title btn btn-sm btn-info text-white">Export Csv</a> | |
<a class="btn btn-sm btn-outline" @click="showModal = false"> | |
<em class="icon ni ni-cross"></em> | |
</a> | |
</div> | |
<div class="modal-body"> | |
<table class="table table-striped"> | |
<thead> | |
<tr> | |
<th>Organisation</th> | |
<th>Description</th> | |
<th>Category</th> | |
<th>Contact Details</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr v-for="item in paginatedTableData" :key="item.entity_id"> | |
<td>{{ item.organisation }}</td> | |
<td>{{ limitText(item.description, 100) }}</td> | |
<td> | |
<div v-for="thematic in item.thematic_areas" :key="thematic.thematic_area_id"> | |
<strong>{{ thematic.thematic_area_name }}:</strong> | |
<span> | |
<span v-for="(sub, index) in thematic.subcategories" :key="sub.subcategory_id"> | |
{{ sub.subcategory_name }}<span v-if="index < thematic.subcategories.length - 1">, </span> | |
</span> | |
</span> | |
</div> | |
</td> | |
<td> | |
<div class="d-flex flex-column bd-highlight mb-3"> | |
<div class="bd-highlight">{{ item.contactemail }}</div> | |
</div> | |
</td> | |
</tr> | |
</tbody> | |
</table> | |
<nav> | |
<ul class="pagination justify-content-center"> | |
<li class="page-item" :class="{ disabled: tableCurrentPage === 1 }"> | |
<a class="page-link" href="#" @click.prevent="prevTablePage">Previous</a> | |
</li> | |
<li class="page-item disabled"> | |
<span class="page-link">Page {{ tableCurrentPage }} of {{ totalTablePages }}</span> | |
</li> | |
<li class="page-item" :class="{ disabled: tableCurrentPage === totalTablePages }"> | |
<a class="page-link" href="#" @click.prevent="nextTablePage">Next</a> | |
</li> | |
</ul> | |
</nav> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div> | |
<!-- Mobile View --> | |
<div v-if="isMobile"> | |
<!-- Map Name Heading --> | |
<div class="d-flex flex-column align-items-center mb-4"> | |
<div class="nk-block-title page-title" | |
style="font-family: Helvetica; font-weight: bold; font-size: 30px; text-align: center;"> | |
{{ details.map_name }} | |
</div> | |
</div> | |
<div class="d-flex"> | |
<h5 style="font-family: Helvetica, sans-serif; font-size: 18px; font-weight: bold;"> Filters </h5> | |
(<span class="card-title">{{ selectedElements.length }}</span>) | |
</div> | |
<div class="row g-gs"> | |
<!-- Loop through thematic areas from the items array --> | |
<div v-for="(item, index) in items" :key="index" class="col-sm-6"> | |
<div class="form-group" style="margin-bottom: 1.5rem;"> | |
<label class="form-label" style="font-size: 1.1rem; color: #333; margin-bottom: .5rem;"> | |
{{ item.thematic_area }} | |
</label> | |
<div class="form-control-wrap"> | |
<custom-select | |
:thematic_id="item.thematic_area_id" | |
:value="selectedSubcategories[item.thematic_area_id] || []" | |
:subcategories="subcategories.filter(sub => sub.thematic_area_id === item.thematic_area_id)" | |
@change="updateSelectedSubcategories(item.thematic_area_id, $event)" | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Search Bar --> | |
<div class="search-bar-container mb-3"> | |
<input v-model="searchTerm" type="text" class="form-control search-input" placeholder="Search Organisation"> | |
</div> | |
<!-- Buttons: Download CSV and Add Entity --> | |
<div class="d-flex justify-content-between mb-3"> | |
<button @click="exportFilteredDataAsCSV()" class="btn btn-sm btn-success">Download as CSV</button> | |
<button @click="displayEntityModal()" class="btn btn-sm btn-outline-info">Add Entity</button> | |
</div> | |
<!-- Cards View --> | |
<div class="row d-flex justify-content-between" style="gap: 10px;"> | |
<!-- Loop through paginated elements to create cards --> | |
<div class="col-lg-5 col-md-6 col-sm-12 mb-2" v-for="item in paginateElements" :key="item.id" style="display: flex;"> | |
<a href="#" | |
@click.prevent="viewEntity(item.entity_id)" | |
class="card-link" | |
@mouseover="zoomIn" | |
@mouseout="zoomOut" | |
style="display: block; text-decoration: none; color: inherit; transition: transform 0.3s ease-in-out; width: 100%; height: 100%;"> | |
<!-- Card Structure with Compact Layout --> | |
<div class="card card-bordered h-100" | |
style="transition: transform 0.3s ease-in-out; cursor: pointer; width: 350px; height: auto; display: flex; flex-direction: column; padding: 8px; box-sizing: border-box; position: relative;"> | |
<!-- Card Inner Content --> | |
<div class="card-inner" style="flex: 1; padding: 5px; border-radius: 10px; overflow: hidden; padding-bottom: 40px;"> | |
<!-- Adjust padding-bottom --> | |
<div class="project"> | |
<!-- Project Head Section (Organization Name and Icon) --> | |
<div class="project-head d-flex justify-content-between align-items-center" style="margin-bottom: 5px; line-height: 1.5;"> | |
<div class="project-title d-flex align-items-center flex-grow-1"> | |
<!-- Organization Avatar --> | |
<div class="user-avatar sq lg bg-blue" | |
style="width: 60px; height: 60px; display: flex; justify-content: center; align-items: center;"> | |
<img v-if="item.logo_url" :src="item.logo_url" :alt="item.organisation + ' logo'" style="width: 100%; height: 100%; object-fit: cover;"> | |
<div v-else> | |
<span class="text-uppercase" style="color: white; font-weight: bold; font-size: 18px;">{{ item.organisation.slice(0,2).toUpperCase() }}</span> | |
</div> | |
</div> | |
<!-- Organization Name with Wrapping --> | |
<div class="project-info ml-2" style="flex-grow: 1;"> | |
<h6 class="title" | |
style="white-space: normal; word-wrap: break-word; font-size: 16px; font-weight: bold; font-family: Helvetica; max-width: 100%; line-height: 1.5;"> | |
{{ item.organisation }} | |
</h6> | |
</div> | |
</div> | |
<!-- Verified Status Button --> | |
<div class="dropdown flex-shrink-0"> | |
<a href="#" class="btn btn-sm btn-icon" style="display: flex; align-items: center; color: green;"> | |
<!-- Shield check icon --> | |
<em class="icon ni ni-shield-check" style="margin-right: 5px; font-size: 1.2rem; color: green;"></em> | |
</a> | |
</div> | |
</div> | |
<!-- Divider Line --> | |
<hr style="border: 0; border-top: 1px solid #ccc; margin: 10px 0;"> | |
<!-- Thematic Areas and Subcategories with Reduced Spacing and line-height 1.5 --> | |
<div v-for="it in item.thematic_areas" :key="it.id" class="project-progress mt-1" style="line-height: 1.2; margin-bottom: 4px;"> | |
<!-- Thematic Area Icon and Name --> | |
<span style="font-size: 12px; font-family: sans-serif; font-weight: 600; color: #333;"> | |
{{ it.thematic_area_name }}: | |
</span> | |
<!-- Subcategories (starting on the same line, wrapping below as needed, with reduced space) --> | |
<span style="font-size: 12px; font-family: sans-serif; white-space: normal; overflow-wrap: break-word; word-wrap: break-word;"> | |
<span v-for="(i, index) in it.subcategories" :key="index" style="margin-right: 4px;"> | |
{{ i.subcategory_name }}<span v-if="index < it.subcategories.length - 1">, </span> | |
</span> | |
</span> | |
</div> | |
</div> | |
</div> | |
<!-- "Read More" Button Fixed at the Bottom Right --> | |
<div class="project-meta" style="position: absolute; bottom: 10px; right: 10px;"> | |
<span class="badge badge-dim bg-blue text-white" | |
style="display: flex; align-items: center; padding: 5px 10px; font-size: 12px; border-radius: 5px; font-weight: bold; font-family: Helvetica;"> | |
<em class="icon ni ni-arrow-right" style="margin-right: 5px; font-size: 1rem;"></em> | |
<span>Read More</span> | |
</span> | |
</div> | |
</div> | |
</a> | |
</div> | |
</div> | |
<!-- Pagination Controls --> | |
<nav> | |
<ul class="pagination d-flex justify-content-center"> | |
<li class="page-item" :class="{ disabled: currentPage === 1 }"> | |
<a @click.prevent="prevPage" href="#" class="page-link">Prev</a> | |
</li> | |
<li v-for="page in totalPages" :key="page" class="page-item" :class="{ active: currentPage === page }"> | |
<a @click.prevent="goToPage(page)" href="#" class="page-link">{{ page }}</a> | |
</li> | |
<li class="page-item" :class="{ disabled: currentPage === totalPages }"> | |
<a @click.prevent="nextPage" href="#" class="page-link">Next</a> | |
</li> | |
</ul> | |
</nav> | |
</div> | |
<div v-else> | |
<div class="nk-content nk-content-fluid"> | |
<div class="container-xl wide-xl"> | |
<div class="nk-content-body"> | |
<div class="nk-block-head nk-block-head-sm"> | |
<!-- Centered Map Name --> | |
<div class="d-flex flex-column align-items-center mb-4"> | |
<div class="nk-block-title page-title" | |
style="font-family: Helvetica; font-weight: bold; font-size: 30px; text-align: center;"> | |
{{ details.map_name }} | |
</div> | |
</div> | |
<!-- Row for Search and Buttons aligned to the right --> | |
<div class="d-flex justify-content-between align-items-center"> | |
<!-- Left space (to help center the map name above) --> | |
<div></div> | |
<!-- Search and Buttons --> | |
<div class="d-flex flex-row align-items-center gap-3" style="margin-left: auto;"> | |
<!-- Modern Search Bar with Icon --> | |
<div class="search-bar-container" | |
:style="{ | |
transition: 'width 0.3s ease-in-out', | |
width: searchExpanded ? '400px' : '240px', | |
position: 'relative', | |
zIndex: searchExpanded ? 1000 : 'auto', /* Keep search bar above the overlay */ | |
}"> | |
<input | |
v-model="searchTerm" | |
@input="handleSearch" | |
@focus="expandSearch" | |
@blur="shrinkSearch" | |
type="text" | |
class="form-control search-input" | |
placeholder="Search Organisation" | |
:style="{ | |
padding: '10px 40px 10px 15px', | |
borderRadius: '25px', | |
border: '1px solid #ccc', | |
width: '100%', | |
transition: 'width 0.3s ease-in-out', | |
outline: 'none' | |
}"> | |
<!-- Search Icon (inside the search bar) --> | |
<i class="ni ni-search search-icon" | |
@click="handleSearch" | |
style=" | |
position: absolute; | |
right: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
cursor: pointer; | |
font-size: 18px; | |
color: #888; | |
"></i> | |
</div> | |
<!-- View Filtered Data Button --> | |
<button | |
style="width: 250px;" | |
@click="ViewFilteredTable()" | |
class="btn btn-sm btn-outline-info">View Filtered Data As Table</button> | |
<!-- Add Entity Button --> | |
<button @click="displayEntityModal()" href="#" class="btn btn-sm btn-outline-info d-none d-md-inline-flex"> | |
<span>Add Entity</span> | |
</button> | |
</div> | |
</div> | |
<!-- Light Transparent Layer (visible when search bar is focused) --> | |
<div | |
v-if="searchExpanded" | |
@click="shrinkSearch" | |
class="screen-overlay" | |
style=" | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100vw; | |
height: 100vh; | |
background-color: rgba(0, 0, 0, 0.2); | |
z-index: 999; | |
transition: opacity 0.3s ease-in-out; | |
"> | |
</div> | |
<!-- Inline CSS for flexbox layout --> | |
<style scoped> | |
.search-bar-container { | |
display: flex; | |
align-items: center; | |
position: relative; | |
} | |
.search-input { | |
width: 100%; | |
padding-left: 15px; | |
padding-right: 40px; /* Space for the search icon */ | |
border-radius: 25px; | |
border: 1px solid #ccc; | |
transition: width 0.3s ease-in-out; | |
} | |
.search-icon { | |
position: absolute; | |
right: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
cursor: pointer; | |
font-size: 18px; | |
color: #888; | |
} | |
.search-input:focus { | |
border-color: #007bff; | |
} | |
.screen-overlay { | |
z-index: 998; /* Below the search bar */ | |
} | |
</style> | |
</div> | |
<div class="nk-block"> | |
<div class="row g-gs"> | |
<div class="col-xxl-5 col-lg-5"> | |
<div class="d-flex"> | |
<svg style="margin-left: -105px;" :width="radius * 2.5" :height="radius * 8" | |
:viewBox="\`0 0 \${radius * 2.5} \${radius * 3.5}\`"> | |
<title class="btn btn-secondary" data-toggle="tooltip" data-placement="top" title="Tooltip on top"> {{ elementtitle }} </title> | |
<g :transform="\`translate(\${radius * 0.2}, \${radius * 0.2}) rotate(-90)\`"> | |
<!-- Outer White Circle --> | |
<path class="outer-circle" :d="getOuterCirclePath" fill="none" stroke="#FFFFFF" stroke-width="30" /> | |
<!-- Outer Blue Circle --> | |
<path class="extra-circle" :d="getExtraCirclePath" fill="none" stroke="#1A5276" stroke-width="15" /> | |
<!-- Display SVG Elements on Click --> | |
<g v-if="selectedSection !== null"> | |
<g v-for="(svgElement, index) in svgElements" :key="index"> | |
<g class="cursor-pointer" | |
@mouseover="showElementName(svgElement)" | |
@click="showSelected(svgElement)" | |
:transform="getSVGElementTransform(index)"> | |
<rect | |
:fill="!selectedElements.includes(svgElement) ? '#c3bdab' : svgElement.color" | |
width="30.27" | |
height="230.006" | |
rx="10" | |
transform="translate(266.6) rotate(90)"> | |
</rect> | |
<ellipse | |
:fill="!selectedElements.includes(svgElement) ? '#c3bdab' : svgElement.color" | |
cx="12.5" | |
cy="12" | |
rx="12.5" | |
ry="12" | |
transform="translate(0 3)"> | |
</ellipse> | |
<rect | |
:fill="!selectedElements.includes(svgElement) ? '#c3bdab' : svgElement.color" | |
width="30" | |
height="6" | |
transform="translate(12 12)"> | |
</rect> | |
<text | |
fill="white" | |
transform="translate(48 23)" | |
font-size="18px"> | |
<tspan x="0" y="0">{{ limitText(svgElement.subcategory, 20) }}</tspan> | |
</text> | |
</g> | |
</g> | |
</g> | |
<!-- Main Half Circles --> | |
<g v-for="(item, index) in items" :key="index"> | |
<!-- Define the path for each thematic area (half circle) --> | |
<path | |
:d="getPath(index)" | |
:fill="item.color" | |
@click="handleClick(index, item.color, item)" | |
style="cursor: pointer;" | |
/> | |
<!-- Define text for the thematic area, rotating 90 degrees to the right --> | |
<text | |
font-size="20px" | |
font-weight="bold" | |
font-family="Helvetica" | |
:transform="\`translate(\${labelRadius * Math.cos((index * angleStep + angleStep / 2) * (Math.PI / 180))}, \${labelRadius * Math.sin((index * angleStep + angleStep / 2) * (Math.PI / 180))}) rotate(90)\`" | |
text-anchor="middle" | |
fill="#FFFFFF" | |
dy="0.4em" | |
@click="handleClick(index, item.color, item)" | |
style="cursor: pointer;"> | |
<tspan | |
v-for="(line, idx) in truncateText(item.thematic_area)" | |
:x="0" | |
:dy="idx === 0 ? '0' : '1.2em'" | |
> | |
{{ line }} | |
</tspan> | |
</text> | |
</g> | |
<!-- Circular white space around reset button --> | |
<path class="reset-space" :d="getResetSpacePath" fill="#FFFFFF" /> | |
<!-- Reset Button --> | |
<path class="reset-button" :d="getResetPath" fill="#2980B9" @click="reset" /> | |
<text font-size="25px" class="reset-text" :x="0" :y="radius * 0.12" text-anchor="middle" fill="white" dy="0.1em" | |
@click="reset"> | |
Reset | |
</text> | |
</g> | |
</svg> | |
<div class="d-flex col-xx-4 col-lg-4 flex-column"> | |
<div class="text-center"> | |
<span @click="reset()" class="btn btn-sm btn-danger mb-4 mt-4 text-center"> | |
Clear All | |
</span> | |
</div> | |
<div class="d-flex"> | |
<h5 style="font-family: Helvetica, sans-serif; font-size: 18px; font-weight: bold;"> Filters </h5> | |
(<span class="card-title">{{ selectedElements.length }}</span>) | |
</div> | |
<ul style=" | |
max-height: calc(8 * 50px); | |
overflow-y: auto; | |
padding: 0; | |
margin: 0; | |
list-style-type: none;"> | |
<div v-for="(item, index) in selectedElements" :key="index"> | |
<li style=" | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
position: relative; | |
max-width: 90%; | |
padding: 0.5rem 0.5rem 0.5rem 14px; | |
margin: 5px; | |
font-family: Tahoma; | |
font-size: 15px; | |
line-height: 20px; | |
border-radius: 10px; | |
word-wrap: break-word; | |
overflow-wrap: break-word; | |
white-space: normal; | |
color: #fff; | |
padding-right: 40px; | |
box-sizing: border-box;" | |
class="d-flex" role="button" | |
:style="{ color: 'white', backgroundColor: item.color }"> | |
<span :title="item.subcategory" style="white-space: normal;"> | |
{{ item.subcategory }} | |
</span> | |
<svg | |
style="width: 20px; | |
height: 20px; | |
border-radius: 10px; | |
background-color: rgba(232, 232, 232, 0.8); | |
position: absolute; | |
right: 10px; | |
top: 50%; | |
transform: translateY(-50%); | |
color: #000; | |
font-weight: 700; | |
font-size: 20px; | |
cursor: pointer;" | |
@click="removeItem(index)" | |
xmlns="http://www.w3.org/2000/svg" fill="white" viewBox="0 0 24 24" | |
stroke-width="1.5" stroke="currentColor"> | |
<path stroke-linecap="round" stroke-linejoin="round" | |
d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /> | |
</svg> | |
</li> | |
</div> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<div class="col-xxl-7 col-lg-6"> | |
<div class="row"> | |
<div v-if="paginateElements.length === 0"> | |
<div style="border-radius: 15px; width: 100%; max-width: 600px; margin: auto; background-color: #fff; padding: 30px; transition: transform 0.3s ease-in-out; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center;"> | |
<!-- Icon Section with Animation --> | |
<div style="width: 120px; height: 120px; background-color: #e0f7fa; border-radius: 50%; display: flex; justify-content: center; align-items: center; margin: 0 auto 20px auto; animation: pulse 1.5s infinite;"> | |
<em class="ni ni-alert-circle" style="font-size: 56px; color: #00796b;"></em> | |
</div> | |
<!-- Heading --> | |
<h2 style="color: #333; font-weight: bold; margin-bottom: 10px; font-size: 24px;">No Results Found</h2> | |
<!-- Message --> | |
<span style="font-size: 16px; color: #6c757d;">Make sure everything is spelled correctly or try different keywords.</span> | |
<!-- Hover Effect --> | |
<div style="transition: transform 0.3s ease-in-out;"> | |
</div> | |
<!-- Inline CSS for keyframes animation --> | |
<style> | |
@keyframes pulse { | |
0% { | |
transform: scale(1); | |
} | |
50% { | |
transform: scale(1.1); | |
} | |
100% { | |
transform: scale(1); | |
} | |
} | |
</style> | |
</div> | |
</div> | |
<div id="app" class="container-fluid"> | |
<!-- Main Card Section --> | |
<div class="row d-flex justify-content-between"> | |
<!-- Loop through paginated elements to create cards --> | |
<div class="col-lg-5 col-md-6 col-sm-12 mb-3" v-for="item in paginateElements" :key="item.id"> | |
<a href="#" | |
@click.prevent="openEntityModal(item)" | |
class="card-link" | |
@mouseover="zoomIn" | |
@mouseout="zoomOut" | |
style="display: block; text-decoration: none; color: inherit; transition: transform 0.3s ease-in-out; width: 100%; height: 100%;"> | |
<!-- Card Structure with Compact Layout --> | |
<div class="card card-bordered h-100" | |
style="transition: transform 0.3s ease-in-out; cursor: pointer; display: flex; flex-direction: column; padding: 8px; box-sizing: border-box; position: relative;"> | |
<!-- Card Inner Content --> | |
<div class="card-inner" style="flex: 1; padding: 5px; border-radius: 10px; overflow: hidden; padding-bottom: 40px;"> | |
<div class="project"> | |
<!-- Project Head Section (Organization Name and Icon) --> | |
<div class="project-head d-flex justify-content-between align-items-center" style="margin-bottom: 5px;"> | |
<div class="project-title d-flex align-items-center flex-grow-1"> | |
<!-- Organization Avatar --> | |
<div class="user-avatar sq lg bg-blue" | |
style="width: 60px; height: 60px; display: flex; justify-content: center; align-items: center;"> | |
<img v-if="item.logo_url" :src="item.logo_url" :alt="item.organisation + ' logo'" style="width: 100%; height: 100%; object-fit: cover;"> | |
<div v-else> | |
<span class="text-uppercase" style="color: white; font-weight: bold; font-size: 18px;">{{ item.organisation.slice(0,2).toUpperCase() }}</span> | |
</div> | |
</div> | |
<!-- Organization Name with Wrapping --> | |
<div class="project-info ml-2" style="flex-grow: 1;"> | |
<h6 class="title" | |
style="white-space: normal; word-wrap: break-word; font-size: 16px; font-weight: bold;"> | |
{{ item.organisation }} | |
</h6> | |
</div> | |
</div> | |
<!-- Verified Status Button --> | |
<div class="dropdown flex-shrink-0"> | |
<a href="#" class="btn btn-sm btn-icon" style="display: flex; align-items: center; color: green;"> | |
<em class="icon ni ni-shield-check" style="margin-right: 5px; font-size: 1.2rem;"></em> | |
</a> | |
</div> | |
</div> | |
<hr style="border: 0; border-top: 1px solid #ccc; margin: 10px 0;"> | |
<!-- Thematic Areas and Subcategories with Reduced Spacing and line-height 1.5 --> | |
<div v-for="it in item.thematic_areas" :key="it.id" class="project-progress mt-1" style="line-height: 1.2; margin-bottom: 4px;"> | |
<span style="font-size: 12px; font-weight: 600; color: #333;">{{ it.thematic_area_name }}:</span> | |
<span style="font-size: 12px; white-space: normal; overflow-wrap: break-word;"> | |
<span v-for="(i, index) in it.subcategories" :key="index" style="margin-right: 4px;"> | |
{{ i.subcategory_name }}<span v-if="index < it.subcategories.length - 1">, </span> | |
</span> | |
</span> | |
</div> | |
</div> | |
</div> | |
<!-- "Read More" Button Fixed at the Bottom Right --> | |
<div class="project-meta" style="position: absolute; bottom: 10px; right: 10px;"> | |
<span class="badge badge-dim bg-blue text-white" | |
style="display: flex; align-items: center; padding: 5px 10px; font-size: 12px; border-radius: 5px; font-weight: bold;"> | |
<em class="icon ni ni-arrow-right" style="margin-right: 5px; font-size: 1rem;"></em> | |
<span>Read More</span> | |
</span> | |
</div> | |
</div> | |
</a> | |
</div> | |
</div> | |
<!-- Pagination Controls --> | |
<nav> | |
<ul class="pagination d-flex justify-content-center"> | |
<li class="page-item" :class="{ disabled: currentPage === 1 }"> | |
<a @click.prevent="prevPage" class="page-link" href="#" tabindex="-1" aria-disabled="currentPage === 1">Prev</a> | |
</li> | |
<li v-for="page in totalPages" :key="page" class="page-item" :class="{ active: currentPage === page }" :aria-current="currentPage === page ? 'page' : false"> | |
<a @click.prevent="goToPage(page)" class="page-link" href="#"> | |
{{ page }} <span v-if="currentPage === page" class="visually-hidden">(current)</span> | |
</a> | |
</li> | |
<li class="page-item" :class="{ disabled: currentPage === totalPages }"> | |
<a @click.prevent="nextPage" class="page-link" href="#">Next</a> | |
</li> | |
</ul> | |
</nav> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
`, | |
}).mount("#app"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment