export function projectMilestoneSignoff(form: FormTs) {
// Project Milestone Sign-off Form - Construction/Development Approval
// Demonstrates: Multi-page wizard, StarRating, CheckboxList, conditional approval workflow
// ============================================
// STATE
// ============================================
const hasIncompleteItems = form.state(false);
const approvalStatus = form.state<'pending' | 'approved' | 'conditional' | 'rejected'>('pending');
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Project Milestone Sign-off',
computedValue: () => 'Formal approval for project phase completion',
customStyles: {
backgroundColor: '#1e40af',
color: 'white',
padding: '24px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// MULTI-PAGE WIZARD
// ============================================
const pages = form.addPages('milestonePages');
// ============================================
// PAGE 1: Project & Milestone Information
// ============================================
const page1 = pages.addPage('projectInfo');
page1.addRow(row => {
row.addTextPanel('page1Title', {
label: 'Step 1 of 4: Project Information',
customStyles: {
backgroundColor: '#dbeafe',
color: '#1e40af',
padding: '12px 16px',
borderRadius: '8px',
fontWeight: 'bold'
}
});
});
const projectSection = page1.addSubform('projectDetails', {
title: 'Project Details'
});
projectSection.addRow(row => {
row.addTextbox('projectName', {
label: 'Project Name',
placeholder: 'Enter project name',
isRequired: true
}, '1fr');
row.addTextbox('projectNumber', {
label: 'Project Number/ID',
placeholder: 'e.g., PRJ-2024-001'
}, '1fr');
});
projectSection.addRow(row => {
row.addTextbox('clientName', {
label: 'Client/Owner Name',
placeholder: 'Enter client name',
isRequired: true
}, '1fr');
row.addTextbox('contractorName', {
label: 'Contractor/Vendor',
placeholder: 'Enter contractor name'
}, '1fr');
});
const milestoneSection = page1.addSubform('milestoneDetails', {
title: 'Milestone Details'
});
milestoneSection.addRow(row => {
row.addDropdown('milestoneType', {
label: 'Milestone Type',
options: [
{ id: 'foundation', name: 'Foundation Complete' },
{ id: 'framing', name: 'Framing Complete' },
{ id: 'roofing', name: 'Roofing Complete' },
{ id: 'mep-rough', name: 'MEP Rough-in Complete' },
{ id: 'drywall', name: 'Drywall Complete' },
{ id: 'finishes', name: 'Finishes Complete' },
{ id: 'substantial', name: 'Substantial Completion' },
{ id: 'final', name: 'Final Completion' },
{ id: 'custom', name: 'Custom Milestone' }
],
isRequired: true
}, '1fr');
row.addTextbox('customMilestone', {
label: 'Custom Milestone Name',
placeholder: 'Describe the milestone',
isVisible: () => milestoneSection.dropdown('milestoneType')?.value() === 'custom',
isRequired: () => milestoneSection.dropdown('milestoneType')?.value() === 'custom'
}, '1fr');
});
milestoneSection.addRow(row => {
row.addDatepicker('scheduledDate', {
label: 'Scheduled Completion Date'
}, '1fr');
row.addDatepicker('actualDate', {
label: 'Actual Completion Date',
isRequired: true,
defaultValue: new Date().toISOString().slice(0, 10)
}, '1fr');
});
page1.addSpacer();
page1.addRow(row => {
row.addButton('nextToPage2', {
label: 'Next: Deliverables Review',
onClick: () => pages.goToPage('deliverables')
});
});
// ============================================
// PAGE 2: Deliverables Verification
// ============================================
const page2 = pages.addPage('deliverables');
page2.addRow(row => {
row.addTextPanel('page2Title', {
label: 'Step 2 of 4: Deliverables Review',
customStyles: {
backgroundColor: '#dbeafe',
color: '#1e40af',
padding: '12px 16px',
borderRadius: '8px',
fontWeight: 'bold'
}
});
});
const deliverablesSection = page2.addSubform('deliverables', {
title: 'Verify Milestone Deliverables'
});
deliverablesSection.addRow(row => {
row.addMatrixQuestion('deliverableStatus', {
label: 'Review each deliverable for this milestone:',
rows: [
{ id: 'scope', label: 'All scope items completed per plans', isRequired: true },
{ id: 'specs', label: 'Work meets specifications', isRequired: true },
{ id: 'codes', label: 'Complies with applicable codes', isRequired: true },
{ id: 'docs', label: 'Required documentation submitted', isRequired: true },
{ id: 'safety', label: 'Safety requirements met', isRequired: true },
{ id: 'cleanup', label: 'Site cleanup completed', isRequired: true }
],
columns: [
{ id: 'complete', label: 'Complete' },
{ id: 'partial', label: 'Partial' },
{ id: 'incomplete', label: 'Incomplete' },
{ id: 'na', label: 'N/A' }
],
fullWidth: true,
onValueChange: (value) => {
if (!value) return;
const hasIssues = Object.values(value).some(v => v === 'partial' || v === 'incomplete');
hasIncompleteItems.set(hasIssues);
}
});
});
const punchListSection = page2.addSubform('punchList', {
title: 'Punch List Items',
isVisible: () => hasIncompleteItems(),
customStyles: { backgroundColor: '#fef3c7', padding: '16px', borderRadius: '8px' }
});
punchListSection.addRow(row => {
row.addTextPanel('punchNote', {
computedValue: () => 'Document all incomplete or partial items that require attention before final approval.',
customStyles: { color: '#92400e', fontStyle: 'italic' }
});
});
punchListSection.addRow(row => {
row.addTextarea('punchItems', {
label: 'List all punch list items:',
placeholder: '1. Item description and location\n2. Item description and location\n3. ...',
rows: 5,
isRequired: () => hasIncompleteItems()
});
});
punchListSection.addRow(row => {
row.addDatepicker('punchDeadline', {
label: 'Punch List Completion Deadline',
isRequired: () => hasIncompleteItems()
});
});
page2.addSpacer();
page2.addRow(row => {
row.addButton('backToPage1', {
label: 'Back',
onClick: () => pages.goToPage('projectInfo')
}, '100px');
row.addEmpty('1fr');
row.addButton('nextToPage3', {
label: 'Next: Quality Assessment',
onClick: () => pages.goToPage('quality')
}, 'auto');
});
// ============================================
// PAGE 3: Quality Assessment
// ============================================
const page3 = pages.addPage('quality');
page3.addRow(row => {
row.addTextPanel('page3Title', {
label: 'Step 3 of 4: Quality Assessment',
customStyles: {
backgroundColor: '#dbeafe',
color: '#1e40af',
padding: '12px 16px',
borderRadius: '8px',
fontWeight: 'bold'
}
});
});
const qualitySection = page3.addSubform('qualityAssessment', {
title: 'Rate Work Quality'
});
qualitySection.addRow(row => {
row.addStarRating('workmanship', {
label: 'Workmanship Quality',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
row.addStarRating('materials', {
label: 'Materials Quality',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
qualitySection.addRow(row => {
row.addStarRating('timeliness', {
label: 'Schedule Adherence',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
row.addStarRating('communication', {
label: 'Communication & Coordination',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
qualitySection.addRow(row => {
row.addEmojiRating('overallSatisfaction', {
label: 'Overall Satisfaction with Milestone Completion',
preset: 'satisfaction',
size: 'lg',
showLabels: true,
alignment: 'center'
});
});
const issuesSection = page3.addSubform('issues', {
title: 'Quality Issues'
});
issuesSection.addRow(row => {
row.addCheckboxList('qualityIssues', {
label: 'Any quality concerns? (select all that apply)',
options: [
{ id: 'none', name: 'No quality concerns' },
{ id: 'finish', name: 'Finish quality below standard' },
{ id: 'alignment', name: 'Alignment/levelness issues' },
{ id: 'damage', name: 'Damage requiring repair' },
{ id: 'incomplete', name: 'Missing or incomplete elements' },
{ id: 'deviation', name: 'Deviation from approved plans' }
],
orientation: 'vertical'
});
});
issuesSection.addSpacer();
issuesSection.addRow(row => {
row.addTextarea('qualityNotes', {
label: 'Quality Assessment Notes',
placeholder: 'Document any specific quality observations, concerns, or commendations...',
rows: 4
});
});
page3.addSpacer();
page3.addRow(row => {
row.addButton('backToPage2', {
label: 'Back',
onClick: () => pages.goToPage('deliverables')
}, '100px');
row.addEmpty('1fr');
row.addButton('nextToPage4', {
label: 'Next: Approval',
onClick: () => pages.goToPage('approval')
}, 'auto');
});
// ============================================
// PAGE 4: Approval & Sign-off
// ============================================
const page4 = pages.addPage('approval');
page4.addRow(row => {
row.addTextPanel('page4Title', {
label: 'Step 4 of 4: Approval & Sign-off',
customStyles: {
backgroundColor: '#dbeafe',
color: '#1e40af',
padding: '12px 16px',
borderRadius: '8px',
fontWeight: 'bold'
}
});
});
const approvalSection = page4.addSubform('approvalDecision', {
title: 'Approval Decision'
});
approvalSection.addRow(row => {
row.addRadioButton('approvalType', {
label: 'Milestone Approval Status:',
options: [
{ id: 'approved', name: 'Approved - Milestone accepted as complete' },
{ id: 'conditional', name: 'Conditionally Approved - Approved with punch list items' },
{ id: 'rejected', name: 'Rejected - Significant issues require rework' }
],
orientation: 'vertical',
isRequired: true,
onValueChange: (value) => {
if (value === 'approved') approvalStatus.set('approved');
else if (value === 'conditional') approvalStatus.set('conditional');
else if (value === 'rejected') approvalStatus.set('rejected');
else approvalStatus.set('pending');
}
});
});
const conditionsSection = page4.addSubform('conditions', {
title: 'Approval Conditions',
isVisible: () => approvalStatus() === 'conditional',
customStyles: { backgroundColor: '#fef3c7', padding: '16px', borderRadius: '8px' }
});
conditionsSection.addRow(row => {
row.addTextarea('conditionDetails', {
label: 'Conditions for Approval',
placeholder: 'List conditions that must be met for full approval...',
rows: 4,
isRequired: () => approvalStatus() === 'conditional'
});
});
const rejectionSection = page4.addSubform('rejection', {
title: 'Rejection Details',
isVisible: () => approvalStatus() === 'rejected',
customStyles: { backgroundColor: '#fee2e2', padding: '16px', borderRadius: '8px' }
});
rejectionSection.addRow(row => {
row.addTextarea('rejectionReason', {
label: 'Reason for Rejection',
placeholder: 'Explain why the milestone cannot be approved...',
rows: 4,
isRequired: () => approvalStatus() === 'rejected'
});
});
const signoffSection = page4.addSubform('signoff', {
title: 'Sign-off Information'
});
signoffSection.addRow(row => {
row.addTextbox('reviewerName', {
label: 'Reviewer Name',
placeholder: 'Enter your full name',
isRequired: true
}, '1fr');
row.addTextbox('reviewerTitle', {
label: 'Title/Role',
placeholder: 'e.g., Project Manager'
}, '1fr');
});
signoffSection.addRow(row => {
row.addDatepicker('signoffDate', {
label: 'Sign-off Date',
defaultValue: new Date().toISOString().slice(0, 10),
isRequired: true
}, '1fr');
row.addEmail('reviewerEmail', {
label: 'Email',
placeholder: 'your@email.com'
}, '1fr');
});
signoffSection.addRow(row => {
row.addCheckbox('confirmAccuracy', {
label: 'I confirm that this assessment is accurate and complete to the best of my knowledge',
isRequired: true
});
});
// ============================================
// SUMMARY SECTION
// ============================================
const summarySection = page4.addSubform('summary', {
title: 'Sign-off Summary',
isVisible: () => approvalStatus() !== 'pending'
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const status = approvalStatus();
const statusText = status === 'approved' ? 'APPROVED' :
status === 'conditional' ? 'CONDITIONALLY APPROVED' : 'REJECTED';
const emoji = status === 'approved' ? '' :
status === 'conditional' ? '' : '';
return `${emoji} MILESTONE STATUS: ${statusText}\n\nThis milestone sign-off will be recorded and all relevant parties will be notified.`;
},
customStyles: () => {
const status = approvalStatus();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
textAlign: 'center',
fontWeight: 'bold'
};
if (status === 'approved') {
return { ...baseStyles, backgroundColor: '#d1fae5', color: '#065f46', borderLeft: '4px solid #059669' };
} else if (status === 'conditional') {
return { ...baseStyles, backgroundColor: '#fef3c7', color: '#92400e', borderLeft: '4px solid #d97706' };
}
return { ...baseStyles, backgroundColor: '#fee2e2', color: '#991b1b', borderLeft: '4px solid #dc2626' };
}
});
});
page4.addSpacer();
page4.addRow(row => {
row.addButton('backToPage3', {
label: 'Back',
onClick: () => pages.goToPage('quality')
}, '100px');
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: () => {
const status = approvalStatus();
if (status === 'approved') return 'Submit Approval';
if (status === 'conditional') return 'Submit Conditional Approval';
if (status === 'rejected') return 'Submit Rejection';
return 'Submit Sign-off';
},
isVisible: () => approvalStatus() !== 'pending'
});
form.configureCompletionScreen({
type: 'text',
title: () => {
const status = approvalStatus();
if (status === 'approved') return 'Milestone Approved!';
if (status === 'conditional') return 'Conditional Approval Recorded';
return 'Sign-off Submitted';
},
message: 'Your milestone sign-off has been recorded. All stakeholders will receive notification of this decision. Please retain a copy for your records.'
});
}