cuga-agent / docs /sales_app.html
Sami Marreed
feat: docker-v1 with optimized frontend
6bd9812
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accounts Management</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
}
/* Custom styles for accessibility and RPA bots */
[data-automation-id] {
/* This is a hook for automation tools */
}
.status-active {
color: #16a34a; /* green-600 */
background-color: #dcfce7; /* green-100 */
}
.status-pending {
color: #d97706; /* amber-600 */
background-color: #fef3c7; /* amber-100 */
}
.status-closed {
color: #dc2626; /* red-600 */
background-color: #fee2e2; /* red-100 */
}
</style>
</head>
<body class="bg-gray-50 text-gray-800">
<div class="container mx-auto p-4 sm:p-6 lg:p-8">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-900" data-automation-id="main-header">Accounts Dashboard</h1>
<p class="text-gray-600 mt-1">Manage and track your customer accounts.</p>
</header>
<div id="summary-section" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8" data-automation-id="summary-section">
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-sm font-medium text-gray-500">Total Accounts</h2>
<p id="total-accounts" class="text-3xl font-semibold text-gray-900" data-automation-id="total-accounts-value">0</p>
</div>
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-sm font-medium text-gray-500">Total Revenue</h2>
<p id="total-revenue" class="text-3xl font-semibold text-gray-900" data-automation-id="total-revenue-value">$0.00</p>
</div>
<div class="bg-white p-6 rounded-lg shadow-md">
<h2 class="text-sm font-medium text-gray-500">Active Accounts</h2>
<p id="active-accounts" class="text-3xl font-semibold text-gray-900" data-automation-id="active-accounts-value">0</p>
</div>
</div>
<div class="bg-white p-6 rounded-lg shadow-md">
<div class="flex flex-col sm:flex-row justify-between items-center mb-6 gap-4">
<div class="relative w-full sm:w-auto">
<input type="text" id="search-input" placeholder="Search accounts..." class="pl-10 pr-4 py-2 border rounded-lg w-full focus:outline-none focus:ring-2 focus:ring-blue-500" data-automation-id="search-input">
<svg class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 transform -translate-y-1/2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
<div class="flex gap-2 w-full sm:w-auto">
<button id="add-account-btn" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition w-full sm:w-auto" data-automation-id="add-account-button">Add Account</button>
<button id="export-csv-btn" class="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition w-full sm:w-auto" data-automation-id="export-csv-button">Export to CSV</button>
</div>
</div>
<div class="overflow-x-auto">
<table id="accounts-table" class="min-w-full bg-white" data-automation-id="accounts-table">
<thead class="bg-gray-100">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Account Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Revenue</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">State</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Industry</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="accounts-table-body" class="divide-y divide-gray-200">
</tbody>
</table>
</div>
</div>
</div>
<div id="account-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden" data-automation-id="account-modal">
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-md bg-white">
<div class="mt-3 text-center">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">Add Account</h3>
<div class="mt-2 px-7 py-3">
<form id="account-form">
<input type="hidden" id="account-id">
<input id="account-name" class="mb-3 px-3 py-2 text-gray-700 border rounded-lg w-full focus:outline-none" type="text" placeholder="Account Name" required data-automation-id="modal-account-name">
<input id="account-revenue" class="mb-3 px-3 py-2 text-gray-700 border rounded-lg w-full focus:outline-none" type="number" placeholder="Revenue" required data-automation-id="modal-account-revenue">
<input id="account-state" class="mb-3 px-3 py-2 text-gray-700 border rounded-lg w-full focus:outline-none" type="text" placeholder="State" required data-automation-id="modal-account-state">
<input id="account-industry" class="mb-3 px-3 py-2 text-gray-700 border rounded-lg w-full focus:outline-none" type="text" placeholder="Industry" required data-automation-id="modal-account-industry">
<select id="account-status" class="mb-3 px-3 py-2 text-gray-700 border rounded-lg w-full focus:outline-none" required data-automation-id="modal-account-status">
<option value="Active">Active</option>
<option value="Pending">Pending</option>
<option value="Closed">Closed</option>
</select>
</form>
</div>
<div class="items-center px-4 py-3">
<button id="save-account-btn" class="px-4 py-2 bg-blue-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-300" data-automation-id="modal-save-button">Save</button>
<button id="cancel-btn" class="mt-2 px-4 py-2 bg-gray-200 text-gray-800 text-base font-medium rounded-md w-full shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300" data-automation-id="modal-cancel-button">Cancel</button>
</div>
</div>
</div>
</div>
<div id="delete-confirm-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full hidden" data-automation-id="delete-confirm-modal">
<div class="relative top-20 mx-auto p-5 border w-full max-w-md shadow-lg rounded-md bg-white">
<div class="mt-3 text-center">
<div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<svg class="h-6 w-6 text-red-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<h3 class="text-lg leading-6 font-medium text-gray-900 mt-4">Delete Account</h3>
<div class="mt-2 px-7 py-3">
<p class="text-sm text-gray-500">Are you sure you want to delete this account? This action cannot be undone.</p>
</div>
<div class="items-center px-4 py-3 flex gap-2">
<button id="confirm-delete-btn" class="px-4 py-2 bg-red-600 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-300" data-automation-id="modal-confirm-delete-button">Delete</button>
<button id="cancel-delete-btn" class="px-4 py-2 bg-gray-200 text-gray-800 text-base font-medium rounded-md w-full shadow-sm hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-300" data-automation-id="modal-cancel-delete-button">Cancel</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
// --- STATE MANAGEMENT ---
let accounts = [
{ id: 1, name: 'Innovate Corp LLC', revenue: 250000, state: 'CA', industry: 'Technology', status: 'Active' },
{ id: 2, name: 'HealthFirst Inc', revenue: 500000, state: 'NY', industry: 'Healthcare', status: 'Active' }
];
let accountIdToDelete = null; // (NEW) To track which account to delete
// --- DOM ELEMENTS ---
const tableBody = document.getElementById('accounts-table-body');
const searchInput = document.getElementById('search-input');
const exportCsvBtn = document.getElementById('export-csv-btn');
const addAccountBtn = document.getElementById('add-account-btn');
// Add/Edit Modal Elements
const modal = document.getElementById('account-modal');
const modalTitle = document.getElementById('modal-title');
const cancelButton = document.getElementById('cancel-btn');
const saveAccountBtn = document.getElementById('save-account-btn');
const accountForm = document.getElementById('account-form');
const accountIdInput = document.getElementById('account-id');
const accountNameInput = document.getElementById('account-name');
const accountRevenueInput = document.getElementById('account-revenue');
const accountStateInput = document.getElementById('account-state');
const accountIndustryInput = document.getElementById('account-industry');
const accountStatusInput = document.getElementById('account-status');
// (NEW) Delete Modal Elements
const deleteConfirmModal = document.getElementById('delete-confirm-modal');
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
const cancelDeleteBtn = document.getElementById('cancel-delete-btn');
// --- FUNCTIONS ---
/**
* Renders the accounts in the table
* @param {Array} accountsToRender - The array of accounts to display
*/
function renderTable(accountsToRender) {
tableBody.innerHTML = '';
if (accountsToRender.length === 0) {
tableBody.innerHTML = `<tr><td colspan="6" class="text-center py-4">No accounts found.</td></tr>`;
return;
}
accountsToRender.forEach(acc => {
const statusClass = getStatusClass(acc.status);
const row = document.createElement('tr');
row.setAttribute('data-account-id', acc.id); // For easy selection by bots
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap" data-label="Account Name">${acc.name}</td>
<td class="px-6 py-4 whitespace-nowrap" data-label="Revenue">${formatCurrency(acc.revenue)}</td>
<td class="px-6 py-4 whitespace-nowrap" data-label="State">${acc.state}</td>
<td class="px-6 py-4 whitespace-nowrap" data-label="Industry">${acc.industry}</td>
<td class="px-6 py-4 whitespace-nowrap" data-label="Status">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${statusClass}">
${acc.status}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium" data-label="Actions">
<button class="text-indigo-600 hover:text-indigo-900 mr-3 edit-btn" data-id="${acc.id}" data-automation-id="edit-btn-${acc.id}">Edit</button>
<button class="text-red-600 hover:text-red-900 delete-btn" data-id="${acc.id}" data-automation-id="delete-btn-${acc.id}">Delete</button>
</td>
`;
tableBody.appendChild(row);
});
updateSummary();
}
/**
* Updates the summary cards with the latest data
*/
function updateSummary() {
const totalAccounts = accounts.length;
const totalRevenue = accounts.reduce((sum, acc) => sum + acc.revenue, 0);
const activeAccounts = accounts.filter(acc => acc.status === 'Active').length;
document.getElementById('total-accounts').textContent = totalAccounts;
document.getElementById('total-revenue').textContent = formatCurrency(totalRevenue);
document.getElementById('active-accounts').textContent = activeAccounts;
}
/**
* Gets the CSS class for the status badge
* @param {string} status - The status of the account
* @returns {string} The CSS class
*/
function getStatusClass(status) {
switch (status) {
case 'Active': return 'status-active';
case 'Pending': return 'status-pending';
case 'Closed': return 'status-closed';
default: return 'bg-gray-100 text-gray-800';
}
}
/**
* Formats a number as currency
* @param {number} value - The number to format
* @returns {string} The formatted currency string
*/
function formatCurrency(value) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(value);
}
/**
* Handles the search/filter functionality
*/
function handleSearch() {
const searchTerm = searchInput.value.toLowerCase();
const filteredAccounts = accounts.filter(acc =>
Object.values(acc).some(val =>
String(val).toLowerCase().includes(searchTerm)
)
);
renderTable(filteredAccounts);
}
/**
* Exports the table data to a CSV file
*/
function exportToCsv() {
const headers = ['ID', 'Account Name', 'Revenue', 'State', 'Industry', 'Status'];
const rows = accounts.map(acc => [
acc.id,
`"${acc.name.replace(/"/g, '""')}"`,
acc.revenue,
acc.state,
acc.industry,
acc.status
].join(','));
const csvContent = "data:text/csv;charset=utf-8," + [headers.join(','), ...rows].join('\n');
const encodedUri = encodeURI(csvContent);
const link = document.createElement("a");
link.setAttribute("href", encodedUri);
link.setAttribute("download", "accounts.csv");
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* Shows the add/edit modal
* @param {Object|null} account - The account to edit, or null to add a new one
*/
function showModal(account = null) {
accountForm.reset();
if (account) {
modalTitle.textContent = 'Edit Account';
accountIdInput.value = account.id;
accountNameInput.value = account.name;
accountRevenueInput.value = account.revenue;
accountStateInput.value = account.state;
accountIndustryInput.value = account.industry;
accountStatusInput.value = account.status;
} else {
modalTitle.textContent = 'Add Account';
accountIdInput.value = '';
}
modal.classList.remove('hidden');
}
/**
* Hides the add/edit modal
*/
function hideModal() {
modal.classList.add('hidden');
}
/**
* Handles saving an account (add or edit)
* @param {Event} e - The form submit event
*/
function saveAccount(e) {
e.preventDefault();
const id = accountIdInput.value;
const accountData = {
name: accountNameInput.value,
revenue: parseFloat(accountRevenueInput.value),
state: accountStateInput.value,
industry: accountIndustryInput.value,
status: accountStatusInput.value,
};
if (id) { // Editing existing account
const index = accounts.findIndex(acc => acc.id == id);
if (index !== -1) {
accounts[index] = { ...accounts[index], ...accountData };
}
} else { // Adding new account
accountData.id = accounts.length > 0 ? Math.max(...accounts.map(a => a.id)) + 1 : 1;
accounts.push(accountData);
}
hideModal();
renderTable(accounts);
}
/**
* (NEW) Shows the delete confirmation modal
* @param {number} id - The ID of the account to be deleted
*/
function showDeleteConfirmModal(id) {
accountIdToDelete = id;
deleteConfirmModal.classList.remove('hidden');
}
/**
* (NEW) Hides the delete confirmation modal
*/
function hideDeleteConfirmModal() {
accountIdToDelete = null;
deleteConfirmModal.classList.add('hidden');
}
/**
* (NEW) Actually performs the account deletion
*/
function performDelete() {
if (accountIdToDelete !== null) {
accounts = accounts.filter(acc => acc.id !== accountIdToDelete);
renderTable(accounts);
hideDeleteConfirmModal();
}
}
// --- EVENT LISTENERS ---
searchInput.addEventListener('input', handleSearch);
exportCsvBtn.addEventListener('click', exportToCsv);
addAccountBtn.addEventListener('click', () => showModal());
cancelButton.addEventListener('click', hideModal);
accountForm.addEventListener('submit', saveAccount);
saveAccountBtn.addEventListener('click', () => accountForm.requestSubmit());
// (NEW) Listeners for the delete confirmation modal
confirmDeleteBtn.addEventListener('click', performDelete);
cancelDeleteBtn.addEventListener('click', hideDeleteConfirmModal);
tableBody.addEventListener('click', function(e) {
if (e.target.classList.contains('edit-btn')) {
const id = parseInt(e.target.dataset.id);
const account = accounts.find(acc => acc.id === id);
showModal(account);
}
if (e.target.classList.contains('delete-btn')) {
const id = parseInt(e.target.dataset.id);
// (MODIFIED) Show the custom modal instead of the browser confirm
showDeleteConfirmModal(id);
}
});
// --- INITIAL RENDER ---
renderTable(accounts);
});
</script>
</body>
</html>