Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 139 additions & 0 deletions website/static/js/organization_switcher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/**
* Organization Switcher JavaScript
* Handles switching between organizations in the dashboard
*/

(function() {
'use strict';

// Map of URL names to their URL patterns
const urlPatterns = {
'organization_analytics': '/organization/{id}/dashboard/analytics/',
'organization_team_overview': '/organization/{id}/dashboard/team-overview/',
'organization_manage_bugs': '/organization/{id}/dashboard/bugs/',
'organization_manage_domains': '/organization/{id}/dashboard/domains/',
'organization_manage_bughunts': '/organization/{id}/dashboard/bughunts/',
'organization_manage_roles': '/organization/{id}/dashboard/roles/',
'organization_manage_integrations': '/organization/{id}/dashboard/integrations/',
'organization_manage_jobs': '/organization/{id}/dashboard/jobs/',
'add_domain': '/organization/{id}/dashboard/add_domain/',
'edit_domain': '/organization/{id}/dashboard/edit_domain/',
'add_bughunt': '/organization/{id}/dashboard/add_bughunt/',
'add_slack_integration': '/organization/{id}/dashboard/add_slack_integration/',
'create_job': '/organization/{id}/jobs/create/',
'edit_job': '/organization/{id}/jobs/edit/',
};

/**
* Get the URL for a given organization ID and current URL name
* @param {number} orgId - The organization ID
* @param {string} urlName - The current URL name
* @returns {string} The constructed URL
*/
function getOrganizationUrl(orgId, urlName) {
// Check if we have a pattern for this URL name
if (urlPatterns[urlName]) {
return urlPatterns[urlName].replace('{id}', orgId);
}

// Fallback: try to construct from current path
const currentPath = window.location.pathname;
const pathParts = currentPath.split('/');

// Find the organization ID in the path (should be after 'organization')
const orgIndex = pathParts.indexOf('organization');
if (orgIndex !== -1 && pathParts[orgIndex + 1]) {
// Replace the organization ID
pathParts[orgIndex + 1] = orgId;

// Check if this is an edit_job page and preserve the job_id
const editJobIndex = pathParts.indexOf('edit_job');
if (editJobIndex !== -1 && pathParts[editJobIndex + 1]) {
// The job_id is already in the path, just return the reconstructed path
return pathParts.join('/');
}

return pathParts.join('/');
}

// Ultimate fallback: redirect to analytics page
return `/organization/${orgId}/dashboard/analytics/`;
}

/**
* Initialize the organization switcher
*/
function initOrganizationSwitcher() {
const switcherButton = document.getElementById('org-switcher-button');
const switcherDropdown = document.getElementById('org-switcher-dropdown');
const switcherChevron = document.getElementById('org-switcher-chevron');
const switcherItems = document.querySelectorAll('.org-switcher-item');

if (!switcherButton || !switcherDropdown || !switcherChevron) {
return;
}

// Toggle dropdown on button click
switcherButton.addEventListener('click', function(e) {
e.stopPropagation();
const isHidden = switcherDropdown.classList.contains('hidden');

if (isHidden) {
switcherDropdown.classList.remove('hidden');
switcherChevron.classList.add('rotate-180');
} else {
switcherDropdown.classList.add('hidden');
switcherChevron.classList.remove('rotate-180');
}
});

// Close dropdown when clicking outside
document.addEventListener('click', function(e) {
const container = document.getElementById('org-switcher-container');
if (container && !container.contains(e.target)) {
switcherDropdown.classList.add('hidden');
switcherChevron.classList.remove('rotate-180');
}
});

// Handle organization selection
switcherItems.forEach(function(item) {
item.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();

const orgId = parseInt(this.getAttribute('data-org-id'));
const orgName = this.getAttribute('data-org-name');
const currentOrgId = window.currentOrgId;
const currentUrlName = window.currentUrlName;

// Don't do anything if clicking on the current organization
if (orgId === currentOrgId) {
switcherDropdown.classList.add('hidden');
switcherChevron.classList.remove('rotate-180');
return;
}

// Get the new URL
const newUrl = getOrganizationUrl(orgId, currentUrlName);

// Update the current org name in the button (optimistic update)
const currentOrgNameElement = document.getElementById('current-org-name');
if (currentOrgNameElement) {
currentOrgNameElement.textContent = orgName;
}

// Redirect to the new organization's dashboard
window.location.href = newUrl;
});
});
}

// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initOrganizationSwitcher);
} else {
initOrganizationSwitcher();
}
})();

Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,48 @@
<div class="w-full h-[80px] bg-gray-50 dark:bg-gray-900"></div>
<!-- Organization Dashboard Top Navigation -->
{% if request.resolver_match.kwargs.id %}
<!-- Organization Switcher -->
{% if organizations|length > 1 %}
<div class="w-full bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
<div class="w-full px-4 sm:px-6 lg:px-8 py-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i class="fas fa-building text-[#e74c3c]"></i>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Switch Organization:</span>
</div>
<div class="relative" id="org-switcher-container">
<button id="org-switcher-button"
class="flex items-center space-x-2 px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-[#e74c3c] focus:ring-offset-2">
<span class="font-medium" id="current-org-name">
{% for org in organizations %}
{% if org.id == request.resolver_match.kwargs.id %}{{ org.name }}{% endif %}
{% endfor %}
</span>
<i class="fas fa-chevron-down text-xs transition-transform duration-200"
id="org-switcher-chevron"></i>
</button>
<div id="org-switcher-dropdown"
class="absolute right-0 mt-2 w-64 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 z-50 max-h-96 overflow-y-auto hidden">
<div class="py-2">
{% for org in organizations %}
<a href="#"
data-org-id="{{ org.id }}"
data-org-name="{{ org.name }}"
class="org-switcher-item block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-[#e74c3c] hover:text-white transition-colors duration-150 {% if org.id == request.resolver_match.kwargs.id %}bg-[#e74c3c] text-white font-medium{% endif %}">
<div class="flex items-center space-x-2">
<i class="fas fa-building text-xs"></i>
<span>{{ org.name }}</span>
{% if org.id == request.resolver_match.kwargs.id %}<i class="fas fa-check ml-auto text-xs"></i>{% endif %}
</div>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="w-full bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 shadow-sm">
<div class="w-full px-4 sm:px-6 lg:px-8">
<nav class="flex overflow-x-auto scrollbar-none -mb-px"
Expand Down Expand Up @@ -130,6 +172,15 @@
</div>
<!-- Include messages JavaScript -->
<script src="{% static 'js/messages.js' %}"></script>
<!-- Organization Switcher JavaScript -->
{% if organizations|length > 1 and request.resolver_match.kwargs.id %}
<script>
// Pass current URL name and organization ID to JavaScript
window.currentOrgId = parseInt('{{ request.resolver_match.kwargs.id }}', 10);
window.currentUrlName = '{{ request.resolver_match.url_name }}';
</script>
<script src="{% static 'js/organization_switcher.js' %}"></script>
{% endif %}
{% block scripts %}
{% endblock scripts %}
</body>
Expand Down
20 changes: 20 additions & 0 deletions website/views/company.py
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,15 @@ def get_channel_id(self, app, channel_name):
def get(self, request, id, *args, **kwargs):
slack_integration = self._get_slack_integration(id)

# Get organizations for navigation (used by dashboard base template)
organizations = []
if request.user.is_authenticated:
organizations = (
Organization.objects.values("name", "id")
.filter(Q(managers__in=[request.user]) | Q(admin=request.user))
.distinct()
)

if slack_integration:
try:
app = App(token=slack_integration.bot_access_token)
Expand All @@ -1388,6 +1397,7 @@ def get(self, request, id, *args, **kwargs):
"organization/dashboard/add_slack_integration.html",
context={
"organization": id,
"organizations": organizations,
"slack_integration": slack_integration,
"channels": channels_list,
"hours": range(24),
Expand Down Expand Up @@ -2057,8 +2067,18 @@ def get(self, request, id, *args, **kwargs):
else:
moderator_count += 1

# Get organizations for navigation (if authenticated)
organizations = []
if request.user.is_authenticated:
organizations = (
Organization.objects.values("name", "id")
.filter(Q(managers__in=[request.user]) | Q(admin=request.user))
.distinct()
)

context = {
"organization": id,
"organizations": organizations,
"organization_obj": organization_obj,
"roles": roles_data,
"domains": list(domains.values("id", "name")),
Expand Down
Loading