1import { useEffect, useState } from 'react';
2import { useParams, useNavigate } from 'react-router-dom';
3import MainLayout from '../components/Layout/MainLayout';
4import { apiFetch } from '../lib/api';
5import { notifySuccess, notifyError } from '../utils/notifications';
6import './Customers.css';
7import './Settings.css';
10function TenantDetail() {
11 const { id } = useParams();
12 const navigate = useNavigate();
13 const [data, setData] = useState(null);
14 const [loading, setLoading] = useState(false);
15 const [error, setError] = useState('');
17 // Create user modal state
18 const [showCreateUserModal, setShowCreateUserModal] = useState(false);
19 const [newUser, setNewUser] = useState({
25 const [creatingUser, setCreatingUser] = useState(false);
27 // Users list and editing state
28 const [users, setUsers] = useState([]);
29 const [usersLoading, setUsersLoading] = useState(false);
30 const [editingUser, setEditingUser] = useState(null);
31 const [editUserData, setEditUserData] = useState({
36 const [updatingUser, setUpdatingUser] = useState(false);
43 async function fetchDetail() {
47 const token = localStorage.getItem('token');
48 const res = await apiFetch(`/tenants/${id}`, {
50 Authorization: `Bearer ${token}`,
51 'X-Tenant-Subdomain': 'admin'
55 if (!res.ok) throw new Error('Failed to load tenant');
56 const json = await res.json();
60 setError(err.message || 'Load error');
66 async function fetchUsers() {
67 setUsersLoading(true);
69 const token = localStorage.getItem('token');
70 const res = await apiFetch(`/users?tenant_id=${id}`, {
72 Authorization: `Bearer ${token}`
76 if (!res.ok) throw new Error('Failed to load users');
77 const usersData = await res.json();
78 // Filter to only show users from this tenant
79 setUsers(usersData.filter(u => u.tenant_id === id));
81 console.error('Error fetching users:', err);
84 setUsersLoading(false);
88 const handleCreateUser = async () => {
89 if (!newUser.name || !newUser.email || !newUser.password) {
90 notifyError('Please fill in all required fields');
94 setCreatingUser(true);
96 const token = localStorage.getItem('token');
97 const res = await apiFetch('/users', {
100 'Content-Type': 'application/json',
101 Authorization: `Bearer ${token}`
103 body: JSON.stringify({
105 tenant_id: id // Use the current tenant ID
110 const errData = await res.json();
111 throw new Error(errData.error || 'Failed to create user');
114 notifySuccess('User created successfully');
115 setShowCreateUserModal(false);
116 setNewUser({ name: '', email: '', role: 'staff', password: '' });
118 // Refresh users and tenant stats
123 notifyError(err.message || 'Failed to create user');
125 setCreatingUser(false);
129 const startEditUser = (user) => {
130 setEditingUser(user);
138 const cancelEditUser = () => {
139 setEditingUser(null);
140 setEditUserData({ name: '', email: '', role: '' });
143 const updateUser = async () => {
144 if (!editingUser || !editUserData.name || !editUserData.email) {
145 notifyError('Please fill in all required fields');
149 setUpdatingUser(true);
151 const token = localStorage.getItem('token');
152 const res = await apiFetch(`/users/${editingUser.user_id}`, {
155 'Content-Type': 'application/json',
156 Authorization: `Bearer ${token}`
158 body: JSON.stringify({
159 name: editUserData.name,
160 email: editUserData.email,
161 role: editUserData.role
166 const errorData = await res.json();
167 throw new Error(errorData.error || 'Update failed');
170 notifySuccess('User updated successfully');
171 setEditingUser(null);
172 setEditUserData({ name: '', email: '', role: '' });
174 // Refresh users list
178 notifyError(err.message || 'Failed to update user');
180 setUpdatingUser(false);
184 const deleteUser = async (user) => {
185 if (!window.confirm(`Are you sure you want to delete user "${user.name}"?`)) return;
188 const token = localStorage.getItem('token');
189 const res = await apiFetch(`/users/${user.user_id}`, {
192 Authorization: `Bearer ${token}`
196 if (!res.ok) throw new Error('Delete failed');
198 notifySuccess('User deleted successfully');
200 // Refresh users and stats
205 notifyError(err.message || 'Failed to delete user');
209 if (loading) return (
211 <div className="page-content"><div className="loading">Loading tenant...</div></div>
217 <div className="page-content"><div className="error-message">{error}</div></div>
221 if (!data) return null;
223 const { tenant, stats } = data;
227 <div className="page-content">
228 <div className="page-header">
229 <h2>{tenant.name}</h2>
230 <div style={{ display: 'flex', gap: '8px' }}>
231 <button className="btn" onClick={() => navigate(`/tenants/${id}/edit`)}>
232 <span className="material-symbols-outlined">edit</span> Edit
237 <section className="dashboard-grid">
238 {/* Tenant Information Card */}
239 <div className="dashboard-card">
240 <h3>Tenant Information</h3>
241 <div style={{ display: 'grid', gap: '12px' }}>
243 <strong>Status:</strong>{' '}
244 <span className={`status-badge status-${tenant.status?.toLowerCase() || 'active'}`}>
245 {tenant.status || 'Active'}
249 <strong>Type:</strong>{' '}
254 backgroundColor: 'var(--primary)',
265 backgroundColor: 'var(--bg-secondary)',
266 color: 'var(--text-muted)',
274 <strong>Subdomain:</strong> <code>{tenant.subdomain}</code>
277 <strong>Tenant ID:</strong> <code style={{ fontSize: '0.85em' }}>{tenant.tenant_id}</code>
280 <strong>Created:</strong> {new Date(tenant.created_at).toLocaleString()}
283 <strong>Last Updated:</strong> {new Date(tenant.updated_at).toLocaleString()}
288 {/* Statistics Card */}
289 <div className="dashboard-card">
291 <div style={{ display: 'grid', gap: '16px' }}>
292 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
293 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
294 <span className="material-symbols-outlined" style={{ color: 'var(--primary)' }}>group</span>
297 <strong style={{ fontSize: '1.2em' }}>{stats.user_count || 0}</strong>
299 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
300 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
301 <span className="material-symbols-outlined" style={{ color: 'var(--success)' }}>business</span>
302 <span>Customers</span>
304 <strong style={{ fontSize: '1.2em' }}>{stats.customer_count || 0}</strong>
306 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
307 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
308 <span className="material-symbols-outlined" style={{ color: 'var(--warning)' }}>confirmation_number</span>
311 <strong style={{ fontSize: '1.2em' }}>{stats.ticket_count || 0}</strong>
313 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
314 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
315 <span className="material-symbols-outlined" style={{ color: 'var(--info)' }}>receipt</span>
316 <span>Invoices</span>
318 <strong style={{ fontSize: '1.2em' }}>{stats.invoice_count || 0}</strong>
320 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
321 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
322 <span className="material-symbols-outlined" style={{ color: 'var(--secondary)' }}>description</span>
323 <span>Contracts</span>
325 <strong style={{ fontSize: '1.2em' }}>{stats.contract_count || 0}</strong>
327 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
328 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
329 <span className="material-symbols-outlined" style={{ color: 'var(--text-muted)' }}>computer</span>
332 <strong style={{ fontSize: '1.2em' }}>{stats.agent_count || 0}</strong>
337 {/* Access Information Card */}
338 <div className="dashboard-card">
339 <h3>Access Information</h3>
340 <div style={{ display: 'grid', gap: '12px' }}>
342 <strong>Portal URL:</strong>
346 backgroundColor: 'var(--bg-secondary)',
348 fontFamily: 'monospace'
350 https://{tenant.subdomain}.yourdomain.com
356 backgroundColor: 'var(--bg-secondary)',
357 border: '1px solid var(--border)'
359 <div style={{ fontSize: '0.9em', color: 'var(--text-muted)' }}>
360 <strong>Development Access:</strong>
361 <div style={{ marginTop: '8px' }}>
362 Add header to requests:
367 backgroundColor: 'var(--bg-primary)',
370 X-Tenant-Subdomain: {tenant.subdomain}
378 {/* Quick Actions Card */}
379 <div className="dashboard-card">
380 <h3>Quick Actions</h3>
381 <div style={{ display: 'grid', gap: '8px' }}>
384 onClick={() => setShowCreateUserModal(true)}
385 style={{ justifyContent: 'flex-start' }}
387 <span className="material-symbols-outlined">person_add</span>
388 Create User for This Tenant
392 onClick={() => navigate(`/tenants/${id}/audit`)}
393 style={{ justifyContent: 'flex-start' }}
395 <span className="material-symbols-outlined">history</span>
400 onClick={() => navigate('/tenants')}
401 style={{ justifyContent: 'flex-start' }}
403 <span className="material-symbols-outlined">arrow_back</span>
410 {/* Users Section */}
411 <section style={{ marginTop: '2rem' }}>
412 <div className="dashboard-card">
413 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
414 <h3 style={{ margin: 0 }}>Users ({users.length})</h3>
416 className="btn primary"
417 onClick={() => setShowCreateUserModal(true)}
418 style={{ display: 'flex', alignItems: 'center', gap: '6px' }}
420 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>add</span>
426 <div className="loading">Loading users...</div>
427 ) : users.length === 0 ? (
428 <div style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>
429 <span className="material-symbols-outlined" style={{ fontSize: '48px', opacity: 0.3 }}>group_off</span>
430 <p>No users found for this tenant.</p>
431 <button className="btn primary" onClick={() => setShowCreateUserModal(true)}>
436 <div style={{ overflowX: 'auto' }}>
437 <table className="data-table">
452 <span className={`status-badge status-${u.role}`}>
457 <div className="action-buttons">
459 className="btn primary"
460 onClick={() => startEditUser(u)}
461 style={{ fontSize: '0.9rem', padding: '0.4rem 0.8rem' }}
467 onClick={() => deleteUser(u)}
468 style={{ fontSize: '0.9rem', padding: '0.4rem 0.8rem' }}
483 {/* Edit User Modal */}
485 <div className="edit-user-modal">
486 <div className="edit-user-modal-content">
487 <h3>Edit User: {editingUser.name}</h3>
489 <div className="form-group">
490 <label>Name *</label>
493 value={editUserData.name}
494 onChange={(e) => setEditUserData({ ...editUserData, name: e.target.value })}
495 placeholder="Full name"
499 <div className="form-group">
500 <label>Email *</label>
503 value={editUserData.email}
504 onChange={(e) => setEditUserData({ ...editUserData, email: e.target.value })}
505 placeholder="user@example.com"
509 <div className="form-group">
512 value={editUserData.role}
513 onChange={(e) => setEditUserData({ ...editUserData, role: e.target.value })}
515 <option value="staff">Staff</option>
516 <option value="admin">Admin</option>
517 <option value="msp">MSP</option>
521 <div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
523 className="btn primary"
525 disabled={updatingUser}
527 {updatingUser ? 'Updating...' : 'Save Changes'}
531 onClick={cancelEditUser}
532 disabled={updatingUser}
541 {/* Create User Modal */}
542 {showCreateUserModal && (
543 <div className="edit-user-modal">
544 <div className="edit-user-modal-content">
545 <h3>Create User for {tenant.name}</h3>
547 <div className="form-group">
548 <label>Name *</label>
552 onChange={(e) => setNewUser({ ...newUser, name: e.target.value })}
553 placeholder="Full name"
557 <div className="form-group">
558 <label>Email *</label>
561 value={newUser.email}
562 onChange={(e) => setNewUser({ ...newUser, email: e.target.value })}
563 placeholder="user@example.com"
567 <div className="form-group">
568 <label>Password *</label>
571 value={newUser.password}
572 onChange={(e) => setNewUser({ ...newUser, password: e.target.value })}
573 placeholder="Enter password"
577 <div className="form-group">
581 onChange={(e) => setNewUser({ ...newUser, role: e.target.value })}
583 <option value="staff">Staff</option>
584 <option value="admin">Admin</option>
588 <div style={{ display: 'flex', gap: '8px', marginTop: '16px' }}>
590 className="btn primary"
591 onClick={handleCreateUser}
592 disabled={creatingUser}
594 {creatingUser ? 'Creating...' : 'Create User'}
599 setShowCreateUserModal(false);
600 setNewUser({ name: '', email: '', role: 'staff', password: '' });
602 disabled={creatingUser}
615export default TenantDetail;