Vendor API endpoints use JWT authentication, not URL path parameters. The vendorCode should only be used for page URLs (navigation), not API calls. Fixed API paths in 10 vendor JS files: - analytics.js, customers.js, inventory.js, notifications.js - order-detail.js, orders.js, products.js, profile.js - settings.js, team.js Added architecture rule JS-014 to prevent this pattern from recurring. Added validation check _check_vendor_api_paths to validate_architecture.py. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
275 lines
8.3 KiB
JavaScript
275 lines
8.3 KiB
JavaScript
// static/vendor/js/team.js
|
|
/**
|
|
* Vendor team management page logic
|
|
* Manage team members, invitations, and roles
|
|
*/
|
|
|
|
const vendorTeamLog = window.LogConfig.loggers.vendorTeam ||
|
|
window.LogConfig.createLogger('vendorTeam', false);
|
|
|
|
vendorTeamLog.info('Loading...');
|
|
|
|
function vendorTeam() {
|
|
vendorTeamLog.info('vendorTeam() called');
|
|
|
|
return {
|
|
// Inherit base layout state
|
|
...data(),
|
|
|
|
// Set page identifier
|
|
currentPage: 'team',
|
|
|
|
// Loading states
|
|
loading: true,
|
|
error: '',
|
|
saving: false,
|
|
|
|
// Team data
|
|
members: [],
|
|
roles: [],
|
|
stats: {
|
|
total: 0,
|
|
active_count: 0,
|
|
pending_invitations: 0
|
|
},
|
|
|
|
// Modal states
|
|
showInviteModal: false,
|
|
showEditModal: false,
|
|
showRemoveModal: false,
|
|
selectedMember: null,
|
|
|
|
// Invite form
|
|
inviteForm: {
|
|
email: '',
|
|
first_name: '',
|
|
last_name: '',
|
|
role_name: 'staff'
|
|
},
|
|
|
|
// Edit form
|
|
editForm: {
|
|
role_id: null,
|
|
is_active: true
|
|
},
|
|
|
|
// Available role names for invite
|
|
roleOptions: [
|
|
{ value: 'owner', label: 'Owner', description: 'Full access to all features' },
|
|
{ value: 'manager', label: 'Manager', description: 'Manage orders, products, and team' },
|
|
{ value: 'staff', label: 'Staff', description: 'Handle orders and products' },
|
|
{ value: 'support', label: 'Support', description: 'Customer support access' },
|
|
{ value: 'viewer', label: 'Viewer', description: 'Read-only access' },
|
|
{ value: 'marketing', label: 'Marketing', description: 'Content and promotions' }
|
|
],
|
|
|
|
async init() {
|
|
vendorTeamLog.info('Team init() called');
|
|
|
|
// Guard against multiple initialization
|
|
if (window._vendorTeamInitialized) {
|
|
vendorTeamLog.warn('Already initialized, skipping');
|
|
return;
|
|
}
|
|
window._vendorTeamInitialized = true;
|
|
|
|
// IMPORTANT: Call parent init first to set vendorCode from URL
|
|
const parentInit = data().init;
|
|
if (parentInit) {
|
|
await parentInit.call(this);
|
|
}
|
|
|
|
try {
|
|
await Promise.all([
|
|
this.loadMembers(),
|
|
this.loadRoles()
|
|
]);
|
|
} catch (error) {
|
|
vendorTeamLog.error('Init failed:', error);
|
|
this.error = 'Failed to initialize team page';
|
|
}
|
|
|
|
vendorTeamLog.info('Team initialization complete');
|
|
},
|
|
|
|
/**
|
|
* Load team members
|
|
*/
|
|
async loadMembers() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const response = await apiClient.get(`/vendor/team/members?include_inactive=true`);
|
|
|
|
this.members = response.members || [];
|
|
this.stats = {
|
|
total: response.total || 0,
|
|
active_count: response.active_count || 0,
|
|
pending_invitations: response.pending_invitations || 0
|
|
};
|
|
|
|
vendorTeamLog.info('Loaded team members:', this.members.length);
|
|
} catch (error) {
|
|
vendorTeamLog.error('Failed to load team members:', error);
|
|
this.error = error.message || 'Failed to load team members';
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load available roles
|
|
*/
|
|
async loadRoles() {
|
|
try {
|
|
const response = await apiClient.get(`/vendor/team/roles`);
|
|
this.roles = response.roles || [];
|
|
vendorTeamLog.info('Loaded roles:', this.roles.length);
|
|
} catch (error) {
|
|
vendorTeamLog.error('Failed to load roles:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Open invite modal
|
|
*/
|
|
openInviteModal() {
|
|
this.inviteForm = {
|
|
email: '',
|
|
first_name: '',
|
|
last_name: '',
|
|
role_name: 'staff'
|
|
};
|
|
this.showInviteModal = true;
|
|
},
|
|
|
|
/**
|
|
* Send invitation
|
|
*/
|
|
async sendInvitation() {
|
|
if (!this.inviteForm.email) {
|
|
Utils.showToast('Email is required', 'error');
|
|
return;
|
|
}
|
|
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.post(`/vendor/team/invite`, this.inviteForm);
|
|
|
|
Utils.showToast('Invitation sent successfully', 'success');
|
|
vendorTeamLog.info('Invitation sent to:', this.inviteForm.email);
|
|
|
|
this.showInviteModal = false;
|
|
await this.loadMembers();
|
|
} catch (error) {
|
|
vendorTeamLog.error('Failed to send invitation:', error);
|
|
Utils.showToast(error.message || 'Failed to send invitation', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Open edit member modal
|
|
*/
|
|
openEditModal(member) {
|
|
this.selectedMember = member;
|
|
this.editForm = {
|
|
role_id: member.role_id,
|
|
is_active: member.is_active
|
|
};
|
|
this.showEditModal = true;
|
|
},
|
|
|
|
/**
|
|
* Update team member
|
|
*/
|
|
async updateMember() {
|
|
if (!this.selectedMember) return;
|
|
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.put(
|
|
`/vendor/${this.vendorCode}/team/members/${this.selectedMember.user_id}`,
|
|
this.editForm
|
|
);
|
|
|
|
Utils.showToast('Team member updated', 'success');
|
|
vendorTeamLog.info('Updated team member:', this.selectedMember.user_id);
|
|
|
|
this.showEditModal = false;
|
|
this.selectedMember = null;
|
|
await this.loadMembers();
|
|
} catch (error) {
|
|
vendorTeamLog.error('Failed to update team member:', error);
|
|
Utils.showToast(error.message || 'Failed to update team member', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Confirm remove member
|
|
*/
|
|
confirmRemove(member) {
|
|
this.selectedMember = member;
|
|
this.showRemoveModal = true;
|
|
},
|
|
|
|
/**
|
|
* Remove team member
|
|
*/
|
|
async removeMember() {
|
|
if (!this.selectedMember) return;
|
|
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.delete(`/vendor/team/members/${this.selectedMember.user_id}`);
|
|
|
|
Utils.showToast('Team member removed', 'success');
|
|
vendorTeamLog.info('Removed team member:', this.selectedMember.user_id);
|
|
|
|
this.showRemoveModal = false;
|
|
this.selectedMember = null;
|
|
await this.loadMembers();
|
|
} catch (error) {
|
|
vendorTeamLog.error('Failed to remove team member:', error);
|
|
Utils.showToast(error.message || 'Failed to remove team member', 'error');
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get role display name
|
|
*/
|
|
getRoleName(member) {
|
|
if (member.role_name) return member.role_name;
|
|
const role = this.roles.find(r => r.id === member.role_id);
|
|
return role ? role.name : 'Unknown';
|
|
},
|
|
|
|
/**
|
|
* Get member initials for avatar
|
|
*/
|
|
getInitials(member) {
|
|
const first = member.first_name || member.email?.charAt(0) || '';
|
|
const last = member.last_name || '';
|
|
return (first.charAt(0) + last.charAt(0)).toUpperCase() || '?';
|
|
},
|
|
|
|
/**
|
|
* Format date for display
|
|
*/
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '-';
|
|
return new Date(dateStr).toLocaleDateString('de-DE', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
}
|
|
};
|
|
}
|