feat: complete vendor frontend parity with admin
Phase 1 - Sidebar Refactor: - Refactor sidebar to use collapsible sections with Alpine.js - Add localStorage persistence for section states - Reorganize navigation into logical groups Phase 2 - Core JS Files: - Add products.js: product CRUD, search, filtering, toggle active/featured - Add orders.js: order list, status management, filtering - Add inventory.js: stock tracking, adjust/set quantity modals - Add customers.js: customer list, order history, messaging - Add team.js: member invite, role management, remove members - Add profile.js: profile editing with form validation - Add settings.js: tabbed settings (general, marketplace, notifications) Templates updated from placeholders to full functional UIs. Vendor frontend now at ~90% parity with admin. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
268
static/vendor/js/team.js
vendored
Normal file
268
static/vendor/js/team.js
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
// 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;
|
||||
|
||||
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/${this.vendorCode}/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/${this.vendorCode}/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/${this.vendorCode}/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/${this.vendorCode}/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'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user