Loading permit details...
- Project Address
- Project Type
- Construction Area (sq. ft.)
- Estimated Project Value
- Application Status
- Latest Term Start Date
Contractors
| Name | Phone | Contractor Type | License Number |
|---|
Select a permit above to view its details
Here's the JavaScript and HTML used to build this example.
JavaScript
const API_BASE = 'https://api.civicreview.com/public/v1';
const API_KEY = 'YOUR_API_KEY';
// Field IDs for the data we want to display
const FIELD_IDS = {
projectAddress: '5bae59e59543de000e982f50',
projectType: '5cfc2ec9cfad40001b623c20',
constructionArea: '5bae46d4b9a91d000e637def',
estimatedValue: '5cfc2f98cfad40001b623c22',
contractors: '5924affcaa6ed9000cf63162'
};
// Contractor sub-field IDs
const CONTRACTOR_FIELDS = {
name: '5924affcaa6ed9000cf63164',
phone: '5924affcaa6ed9000cf63163',
type: '5bae5b879543de000e982f62',
license: '68c9d1a6d370fe7652c3d6ce'
};
async function fetchPermit(permitId) {
const response = await fetch(`${API_BASE}/permits/${permitId}`, {
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
return response.json();
}
function getFieldValue(permit, fieldId) {
const field = permit.formData?.fields?.find(f => f.formField === fieldId);
return field?.value || '—';
}
function getContractors(permit) {
const field = permit.formData?.fields?.find(
f => f.formField === FIELD_IDS.contractors
);
if (!field?.collectionItems) return [];
return field.collectionItems.map(item => ({
name: item.fields?.find(f => f.formField === CONTRACTOR_FIELDS.name)?.value || '—',
phone: item.fields?.find(f => f.formField === CONTRACTOR_FIELDS.phone)?.value || '—',
type: item.fields?.find(f => f.formField === CONTRACTOR_FIELDS.type)?.value || '—',
license: item.fields?.find(f => f.formField === CONTRACTOR_FIELDS.license)?.value || '—'
}));
}
function formatCurrency(value) {
if (!value) return '—';
return new Intl.NumberFormat('en-US', {
style: 'currency', currency: 'USD', maximumFractionDigits: 0
}).format(Number(value));
}
function formatNumber(value) {
if (!value) return '—';
return new Intl.NumberFormat('en-US').format(Number(value));
}
function formatDate(dateString) {
if (!dateString) return '—';
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric', month: 'short', day: 'numeric'
});
}
function formatPhone(phone) {
if (!phone || phone === '—') return '—';
const digits = phone.replace(/\D/g, '');
if (digits.length === 11 && digits.startsWith('1')) {
return `(${digits.slice(1,4)}) ${digits.slice(4,7)}-${digits.slice(7)}`;
}
if (digits.length === 10) {
return `(${digits.slice(0,3)}) ${digits.slice(3,6)}-${digits.slice(6)}`;
}
return phone;
}
function getLatestTermBegDate(permit) {
if (!permit.terms || permit.terms.length === 0) return '—';
const latest = permit.terms[permit.terms.length - 1];
return formatDate(latest.begDate);
}
function renderPermit(permit) {
document.getElementById('permit-title').textContent =
permit.entityName || 'Building Permit';
document.getElementById('detail-address').textContent =
getFieldValue(permit, FIELD_IDS.projectAddress);
document.getElementById('detail-type').textContent =
getFieldValue(permit, FIELD_IDS.projectType);
document.getElementById('detail-area').textContent =
formatNumber(getFieldValue(permit, FIELD_IDS.constructionArea));
document.getElementById('detail-value').textContent =
formatCurrency(getFieldValue(permit, FIELD_IDS.estimatedValue));
document.getElementById('detail-status').textContent =
permit.applicationStatus;
document.getElementById('detail-term-date').textContent =
getLatestTermBegDate(permit);
// Render contractors
const contractors = getContractors(permit);
const tbody = document.getElementById('contractors-body');
tbody.innerHTML = contractors.map(c => `
<tr>
<td class="px-4 py-3 text-sm">${c.name}</td>
<td class="px-4 py-3 text-sm">${formatPhone(c.phone)}</td>
<td class="px-4 py-3 text-sm">${c.type}</td>
<td class="px-4 py-3 text-sm">${c.license}</td>
</tr>
`).join('');
document.getElementById('permit-details').classList.remove('hidden');
document.getElementById('empty-state').classList.add('hidden');
}
// Event listener for permit selection
document.getElementById('permit-select').addEventListener('change', async (e) => {
const permitId = e.target.value;
if (!permitId) {
document.getElementById('permit-details').classList.add('hidden');
document.getElementById('empty-state').classList.remove('hidden');
return;
}
document.getElementById('loading').classList.remove('hidden');
document.getElementById('permit-details').classList.add('hidden');
document.getElementById('empty-state').classList.add('hidden');
document.getElementById('error').classList.add('hidden');
try {
const permit = await fetchPermit(permitId);
document.getElementById('loading').classList.add('hidden');
renderPermit(permit);
} catch (err) {
document.getElementById('loading').classList.add('hidden');
document.getElementById('error').classList.remove('hidden');
document.getElementById('error-message').textContent = err.message;
}
});
HTML
<!-- Permit selector -->
<label for="permit-select">Select a Building Permit</label>
<select id="permit-select">
<option value="">Choose a permit...</option>
<option value="69a84897f444add96ee3bf17">175 South DK Way</option>
<option value="68c43a4c183d1b3d5148ead8">123 Boulevard</option>
<option value="684208e8a666fd1d35069be9">405 Cass Road</option>
</select>
<!-- Permit summary -->
<div id="permit-details" class="hidden">
<h3 id="permit-title"></h3>
<dl>
<dt>Project Address</dt>
<dd id="detail-address"></dd>
<dt>Project Type</dt>
<dd id="detail-type"></dd>
<dt>Construction Area (sq. ft.)</dt>
<dd id="detail-area"></dd>
<dt>Estimated Project Value</dt>
<dd id="detail-value"></dd>
<dt>Application Status</dt>
<dd id="detail-status"></dd>
<dt>Latest Term Start Date</dt>
<dd id="detail-term-date"></dd>
</dl>
<!-- Contractors table -->
<h3>Contractors</h3>
<table>
<thead>
<tr>
<th>Name</th>
<th>Phone</th>
<th>Contractor Type</th>
<th>License Number</th>
</tr>
</thead>
<tbody id="contractors-body"></tbody>
</table>
</div>