| const { useState, useMemo, useEffect, createElement: h } = React;
|
|
|
| function getOrdinalSuffix(day) {
|
| const mod100 = day % 100;
|
| if (mod100 >= 11 && mod100 <= 13) return 'th';
|
| switch (day % 10) {
|
| case 1: return 'st';
|
| case 2: return 'nd';
|
| case 3: return 'rd';
|
| default: return 'th';
|
| }
|
| }
|
|
|
| function formatAssessmentDate(date) {
|
| const day = date.getDate();
|
| const month = date.toLocaleString(undefined, { month: 'long' });
|
| const year = date.getFullYear();
|
| return `${day}${getOrdinalSuffix(day)} ${month} ${year}`;
|
| }
|
|
|
| function getDefaultAssessmentName(now = new Date()) {
|
| return `LVMWD - ${formatAssessmentDate(now)}`;
|
| }
|
|
|
|
|
| function getMaturityLevel(score) {
|
| if (score >= 80) return { name: 'Leading', color: '#3b82f6' };
|
| if (score >= 60) return { name: 'Scaling', color: '#22c55e' };
|
| if (score >= 40) return { name: 'Adopting', color: '#eab308' };
|
| if (score >= 20) return { name: 'Experimenting', color: '#f97316' };
|
| return { name: 'Curious/Aware', color: '#ef4444' };
|
| }
|
|
|
| function AssessmentTool() {
|
| const [responses, setResponses] = useState({});
|
| const [notes, setNotes] = useState({});
|
| const [expandedSections, setExpandedSections] = useState(
|
| Object.fromEntries(assessmentData.dimensions.map((_, i) => [i, false]))
|
| );
|
| const [showSaveDialog, setShowSaveDialog] = useState(false);
|
| const [showViewSaved, setShowViewSaved] = useState(false);
|
| const [organizationName, setOrganizationName] = useState('');
|
| const [assessmentName, setAssessmentName] = useState(getDefaultAssessmentName());
|
| const [activeQuestionId, setActiveQuestionId] = useState(null);
|
| const [showValidationErrors, setShowValidationErrors] = useState(false);
|
|
|
| const [autosaveState, setAutosaveState] = useState({
|
| status: 'Saved',
|
| lastUpdated: null
|
| });
|
|
|
| const draftStorageKey = 'aiMaturityAssessmentDraftV1';
|
|
|
| const questionIndex = useMemo(() => {
|
| const order = [];
|
| const indexById = {};
|
| assessmentData.dimensions.forEach((dimension, dimIndex) => {
|
| dimension.questions.forEach((question, qIndex) => {
|
| const globalIndex = order.length;
|
| order.push(question.id);
|
| indexById[question.id] = { dimIndex, qIndex, globalIndex };
|
| });
|
| });
|
| return { order, indexById };
|
| }, []);
|
|
|
| const unansweredQuestionIds = useMemo(() => {
|
| return questionIndex.order.filter(id => responses[id] === undefined);
|
| }, [questionIndex.order, responses]);
|
|
|
| const scrollToQuestion = (questionId) => {
|
| const meta = questionIndex.indexById[questionId];
|
| if (!meta) return;
|
|
|
| setExpandedSections(prev => ({ ...prev, [meta.dimIndex]: true }));
|
| setActiveQuestionId(questionId);
|
|
|
| setTimeout(() => {
|
| const el = document.getElementById(questionId);
|
| if (el) {
|
| el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
| const input = el.querySelector('input');
|
| if (input) input.focus({ preventScroll: true });
|
| }
|
| }, 50);
|
| };
|
|
|
| const goToUnanswered = (direction) => {
|
| if (unansweredQuestionIds.length === 0) {
|
| alert('All questions are answered.');
|
| return;
|
| }
|
|
|
| const order = questionIndex.order;
|
| const startIndex = activeQuestionId && questionIndex.indexById[activeQuestionId]
|
| ? questionIndex.indexById[activeQuestionId].globalIndex
|
| : (direction > 0 ? -1 : order.length);
|
|
|
| const step = direction > 0 ? 1 : -1;
|
| const maxIters = order.length;
|
| let i = startIndex;
|
|
|
| for (let iter = 0; iter < maxIters; iter++) {
|
| i = (i + step + order.length) % order.length;
|
| const id = order[i];
|
| if (responses[id] === undefined) {
|
| scrollToQuestion(id);
|
| return;
|
| }
|
| }
|
|
|
| scrollToQuestion(unansweredQuestionIds[0]);
|
| };
|
|
|
| const validateAllAnswered = () => {
|
| const missing = unansweredQuestionIds;
|
| if (missing.length === 0) return { ok: true, missing: [] };
|
| return { ok: false, missing };
|
| };
|
|
|
|
|
| useEffect(() => {
|
| try {
|
| const raw = localStorage.getItem(draftStorageKey);
|
| if (!raw) return;
|
| const draft = JSON.parse(raw);
|
|
|
| if (draft && typeof draft === 'object') {
|
| if (draft.responses && typeof draft.responses === 'object') setResponses(draft.responses);
|
| if (draft.notes && typeof draft.notes === 'object') setNotes(draft.notes);
|
| if (typeof draft.organizationName === 'string') setOrganizationName(draft.organizationName);
|
| if (typeof draft.assessmentName === 'string' && draft.assessmentName.trim()) {
|
| setAssessmentName(draft.assessmentName);
|
| }
|
| if (draft.updatedAt) {
|
| const d = new Date(draft.updatedAt);
|
| if (!isNaN(d.getTime())) {
|
| setAutosaveState({ status: 'Saved', lastUpdated: d });
|
| }
|
| }
|
| }
|
| } catch (_) {
|
|
|
| }
|
|
|
| }, []);
|
|
|
|
|
| const calculations = useMemo(() => {
|
| const dimensionScores = assessmentData.dimensions.map(dimension => {
|
| let earnedPoints = 0;
|
| let totalPossiblePoints = 0;
|
| let answeredQuestions = 0;
|
|
|
| dimension.questions.forEach(question => {
|
| const maxPoints = Math.max(...question.options.map((_, i) => question.options[i]?.points || i * 3 + 3));
|
| totalPossiblePoints += maxPoints;
|
|
|
| if (responses[question.id] !== undefined) {
|
| earnedPoints += question.options[responses[question.id]]?.points || responses[question.id] * 3 + 3;
|
| answeredQuestions++;
|
| }
|
| });
|
|
|
| const percentage = totalPossiblePoints > 0 ? (earnedPoints / totalPossiblePoints) * 100 : 0;
|
| const completionRate = (answeredQuestions / dimension.questions.length) * 100;
|
|
|
| return {
|
| name: dimension.name,
|
| score: percentage,
|
| earnedPoints,
|
| totalPossiblePoints,
|
| weight: dimension.weight,
|
| completionRate,
|
| answeredQuestions,
|
| totalQuestions: dimension.questions.length
|
| };
|
| });
|
|
|
| let totalWeightedScore = 0;
|
| let totalWeight = 0;
|
|
|
| dimensionScores.forEach(dim => {
|
| totalWeightedScore += dim.score * dim.weight;
|
| totalWeight += dim.weight;
|
| });
|
|
|
| const overallScore = totalWeight > 0 ? totalWeightedScore / totalWeight : 0;
|
|
|
| let maturityLevel = 'Curious/Aware';
|
| let maturityColor = '#ef4444';
|
| if (overallScore >= 80) { maturityLevel = 'Leading'; maturityColor = '#3b82f6'; }
|
| else if (overallScore >= 60) { maturityLevel = 'Scaling'; maturityColor = '#22c55e'; }
|
| else if (overallScore >= 40) { maturityLevel = 'Adopting'; maturityColor = '#eab308'; }
|
| else if (overallScore >= 20) { maturityLevel = 'Experimenting'; maturityColor = '#f97316'; }
|
|
|
| const totalAnswered = dimensionScores.reduce((s, d) => s + d.answeredQuestions, 0);
|
| const totalQuestions = dimensionScores.reduce((s, d) => s + d.totalQuestions, 0);
|
|
|
| return {
|
| dimensionScores,
|
| overallScore,
|
| maturityLevel,
|
| maturityColor,
|
| totalAnswered,
|
| totalQuestions
|
| };
|
| }, [responses]);
|
|
|
|
|
| useEffect(() => {
|
| const ctx = document.getElementById('radarChart');
|
| if (!ctx) return;
|
|
|
| if (window.radarChartInstance) {
|
| window.radarChartInstance.destroy();
|
| }
|
|
|
| window.radarChartInstance = new Chart(ctx.getContext('2d'), {
|
| type: 'radar',
|
| data: {
|
| labels: calculations.dimensionScores.map(d => d.name),
|
| datasets: [{
|
| label: 'Maturity Score',
|
| data: calculations.dimensionScores.map(d => d.score),
|
| fill: true,
|
| backgroundColor: 'rgba(59, 130, 246, 0.2)',
|
| borderColor: 'rgba(59, 130, 246, 1)',
|
| pointBackgroundColor: 'rgba(59, 130, 246, 1)',
|
| pointBorderColor: '#fff',
|
| pointHoverBackgroundColor: '#fff',
|
| pointHoverBorderColor: 'rgba(59, 130, 246, 1)',
|
| pointRadius: 5,
|
| pointHoverRadius: 7
|
| }]
|
| },
|
| options: {
|
| responsive: true,
|
| maintainAspectRatio: true,
|
| scales: {
|
| r: {
|
| beginAtZero: true,
|
| max: 100,
|
| ticks: {
|
| stepSize: 20,
|
| callback: value => value + '%'
|
| },
|
| pointLabels: {
|
| font: { size: 11 }
|
| }
|
| }
|
| },
|
| plugins: {
|
| legend: { display: false },
|
| tooltip: {
|
| callbacks: {
|
| label: context => context.parsed.r.toFixed(1) + '%'
|
| }
|
| }
|
| }
|
| }
|
| });
|
|
|
| return () => {
|
| if (window.radarChartInstance) {
|
| window.radarChartInstance.destroy();
|
| }
|
| };
|
| }, [calculations]);
|
|
|
| const handleResponse = (questionId, value) => {
|
| setResponses(prev => ({ ...prev, [questionId]: value }));
|
| setActiveQuestionId(questionId);
|
| };
|
|
|
| const toggleSection = (index) => {
|
| setExpandedSections(prev => ({ ...prev, [index]: !prev[index] }));
|
| };
|
|
|
| const exportAssessment = () => {
|
| const validation = validateAllAnswered();
|
| if (!validation.ok) {
|
| setShowValidationErrors(true);
|
| scrollToQuestion(validation.missing[0]);
|
| alert(`Please answer all questions before finalizing. Remaining: ${validation.missing.length}`);
|
| return;
|
| }
|
|
|
| const data = {
|
| assessmentName: assessmentName || "AI Maturity Assessment",
|
| organizationName: organizationName || "",
|
| timestamp: new Date().toISOString(),
|
| responses: responses,
|
| notes: notes,
|
| scores: calculations
|
| };
|
|
|
| const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
| const url = URL.createObjectURL(blob);
|
| const a = document.createElement('a');
|
| a.href = url;
|
| a.download = `assessment-${new Date().toISOString().split('T')[0]}.json`;
|
| document.body.appendChild(a);
|
| a.click();
|
| document.body.removeChild(a);
|
| URL.revokeObjectURL(url);
|
| };
|
|
|
| const loadAssessment = () => {
|
| const input = document.createElement('input');
|
| input.type = 'file';
|
| input.accept = '.json';
|
| input.onchange = (e) => {
|
| const file = e.target.files[0];
|
| if (!file) return;
|
|
|
| const reader = new FileReader();
|
| reader.onload = (event) => {
|
| try {
|
| const data = JSON.parse(event.target.result);
|
| if (data.responses) {
|
| setResponses(data.responses);
|
| }
|
| if (data.notes && typeof data.notes === 'object') setNotes(data.notes);
|
| if (typeof data.organizationName === 'string') setOrganizationName(data.organizationName);
|
| if (typeof data.assessmentName === 'string' && data.assessmentName.trim()) setAssessmentName(data.assessmentName);
|
| } catch (error) {
|
| alert('Failed to load file');
|
| }
|
| };
|
| reader.readAsText(file);
|
| };
|
| input.click();
|
| };
|
|
|
| const copyToClipboard = () => {
|
| const text = `AI Maturity Assessment Results
|
| Overall Score: ${calculations.overallScore.toFixed(1)}%
|
| Maturity Level: ${calculations.maturityLevel}
|
| Progress: ${calculations.totalAnswered}/${calculations.totalQuestions} questions
|
|
|
| Dimension Scores:
|
| ${calculations.dimensionScores.map(d =>
|
| `${d.name}: ${d.score.toFixed(1)}% (${d.answeredQuestions}/${d.totalQuestions} answered)`
|
| ).join('\n')}`;
|
|
|
| navigator.clipboard.writeText(text).then(() => alert('Copied to clipboard!'));
|
| };
|
|
|
| const saveAssessmentToCloud = () => {
|
| if (!organizationName.trim()) {
|
| alert('Please enter an organization name');
|
| return;
|
| }
|
|
|
| const data = {
|
| assessmentName: assessmentName || getDefaultAssessmentName(),
|
| organizationName: organizationName.trim(),
|
| timestamp: new Date().toISOString(),
|
| responses: responses,
|
| notes: notes,
|
| scores: calculations
|
| };
|
|
|
|
|
| const saved = JSON.parse(localStorage.getItem('savedAssessments') || '[]');
|
| saved.push(data);
|
| localStorage.setItem('savedAssessments', JSON.stringify(saved));
|
|
|
| alert('Assessment saved successfully!');
|
| setShowSaveDialog(false);
|
|
|
|
|
| setResponses({});
|
| setNotes({});
|
| setOrganizationName('');
|
| setAssessmentName(getDefaultAssessmentName());
|
| setActiveQuestionId(null);
|
| setShowValidationErrors(false);
|
| setExpandedSections(Object.fromEntries(assessmentData.dimensions.map((_, i) => [i, false])));
|
| localStorage.removeItem(draftStorageKey);
|
| window.scrollTo({ top: 0, behavior: 'smooth' });
|
| };
|
|
|
| const getSavedAssessments = () => {
|
| return JSON.parse(localStorage.getItem('savedAssessments') || '[]');
|
| };
|
|
|
| const loadSavedAssessment = (index) => {
|
| const saved = getSavedAssessments();
|
| if (saved[index]) {
|
| setResponses(saved[index].responses);
|
| if (saved[index].notes && typeof saved[index].notes === 'object') setNotes(saved[index].notes);
|
| setOrganizationName(saved[index].organizationName);
|
| if (typeof saved[index].assessmentName === 'string' && saved[index].assessmentName.trim()) {
|
| setAssessmentName(saved[index].assessmentName);
|
| }
|
| setShowViewSaved(false);
|
| alert('Assessment loaded!');
|
| }
|
| };
|
|
|
|
|
| useEffect(() => {
|
| setAutosaveState(prev => ({ ...prev, status: 'Saving…' }));
|
| const timer = setTimeout(() => {
|
| try {
|
| const updatedAt = new Date();
|
| const draft = {
|
| assessmentName,
|
| organizationName,
|
| responses,
|
| notes,
|
| updatedAt: updatedAt.toISOString()
|
| };
|
| localStorage.setItem(draftStorageKey, JSON.stringify(draft));
|
| setAutosaveState({ status: 'Saved', lastUpdated: updatedAt });
|
| } catch (_) {
|
| setAutosaveState(prev => ({ ...prev, status: 'Saved' }));
|
| }
|
| }, 500);
|
|
|
| return () => clearTimeout(timer);
|
| }, [assessmentName, organizationName, responses, notes]);
|
|
|
| const deleteSavedAssessment = (index) => {
|
| if (confirm('Are you sure you want to delete this assessment?')) {
|
| const saved = getSavedAssessments();
|
| saved.splice(index, 1);
|
| localStorage.setItem('savedAssessments', JSON.stringify(saved));
|
| setShowViewSaved(false);
|
| setTimeout(() => setShowViewSaved(true), 0);
|
| }
|
| };
|
|
|
| return h('div', { className: 'min-h-screen bg-gray-50' },
|
| h('div', { className: 'max-w-7xl mx-auto p-6' },
|
|
|
| h('div', { className: 'bg-white rounded-lg shadow-lg p-4 md:p-6 mb-4' },
|
| h('div', { className: 'flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-4' },
|
| h('div', { className: 'flex items-center gap-4' },
|
| h('img', {
|
| src: 'LOGO1.png',
|
| alt: 'Sedna Consulting Group',
|
| className: 'h-8 md:h-10'
|
| }),
|
| h('div', {},
|
| h('h1', { className: 'text-xl md:text-2xl font-bold text-gray-900' }, 'AI Maturity Assessment'),
|
| h('p', { className: 'text-xs md:text-sm text-gray-600' },
|
| `Progress: ${calculations.totalAnswered} of ${calculations.totalQuestions} questions`),
|
| h('div', { className: 'mt-2 flex flex-col sm:flex-row sm:items-center gap-2' },
|
| h('input', {
|
| type: 'text',
|
| value: assessmentName,
|
| onChange: (e) => setAssessmentName(e.target.value),
|
| placeholder: 'Assessment name',
|
| className: 'w-full sm:w-[360px] px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500'
|
| }),
|
| h('div', { className: 'text-xs text-gray-500' },
|
| autosaveState.status === 'Saving…'
|
| ? 'Saving…'
|
| : `Saved${autosaveState.lastUpdated ? ` • Last updated ${autosaveState.lastUpdated.toLocaleString()}` : ''}`
|
| )
|
| )
|
| )
|
| ),
|
| h('div', { className: 'flex flex-wrap gap-2 no-print' },
|
| h('button', {
|
| onClick: () => setShowSaveDialog(true),
|
| className: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium'
|
| }, 'Save'),
|
| h('button', {
|
| onClick: () => setShowViewSaved(true),
|
| className: 'px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 font-medium'
|
| }, 'Load'),
|
| h('button', {
|
| onClick: exportAssessment,
|
| className: 'px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 font-medium'
|
| }, unansweredQuestionIds.length > 0 ? `Finalize (${unansweredQuestionIds.length} left)` : 'Finalize'),
|
| h('button', {
|
| onClick: () => window.print(),
|
| className: 'px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 font-medium'
|
| }, 'Print')
|
| )
|
| ),
|
| h('div', { className: 'text-center py-4' },
|
| h('div', {
|
| className: 'text-4xl md:text-5xl font-bold mb-1',
|
| style: { color: calculations.maturityColor }
|
| }, `${calculations.overallScore.toFixed(1)}%`),
|
| h('div', {
|
| className: 'text-lg md:text-xl font-semibold',
|
| style: { color: calculations.maturityColor }
|
| }, calculations.maturityLevel)
|
| )
|
| ),
|
|
|
|
|
| h('div', { className: 'bg-white rounded-lg shadow-md p-3 mb-4 no-print flex flex-wrap gap-2 justify-end items-center' },
|
| h('div', { className: 'text-sm text-gray-600 mr-auto' },
|
| unansweredQuestionIds.length > 0
|
| ? `${unansweredQuestionIds.length} unanswered remaining`
|
| : 'All questions answered'
|
| ),
|
| h('button', {
|
| onClick: () => goToUnanswered(-1),
|
| className: 'px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 font-medium'
|
| }, 'Previous Unanswered'),
|
| h('button', {
|
| onClick: () => goToUnanswered(1),
|
| className: 'px-4 py-2 border border-gray-300 text-gray-700 rounded-lg hover:bg-gray-50 font-medium'
|
| }, 'Next Unanswered')
|
| ),
|
|
|
|
|
| h('div', { className: 'bg-white rounded-lg shadow-lg p-4 md:p-6 mb-4' },
|
| h('h2', { className: 'text-lg font-bold text-gray-900 mb-3 text-center' }, 'Maturity Dimensions'),
|
| h('div', { className: 'max-w-md mx-auto', style: { height: '280px' } },
|
| h('canvas', { id: 'radarChart' })
|
| )
|
| ),
|
|
|
|
|
| h('div', { className: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mb-4' },
|
| ...assessmentData.dimensions.map((dimension, dimIndex) => {
|
| const dimScore = calculations.dimensionScores[dimIndex];
|
| const pct = dimScore.score;
|
| const levelColor = getMaturityLevel(pct).color;
|
| return h('div', {
|
| key: `summary-${dimIndex}`,
|
| className: 'bg-white rounded-lg shadow-md p-4 cursor-pointer hover:shadow-lg transition-shadow',
|
| onClick: () => {
|
| setExpandedSections(prev => ({ ...prev, [dimIndex]: true }));
|
| setTimeout(() => {
|
| const el = document.getElementById(dimension.questions[0].id);
|
| if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
| }, 100);
|
| }
|
| },
|
| h('div', { className: 'flex items-center justify-between mb-2' },
|
| h('h3', { className: 'font-semibold text-gray-900 text-sm' }, dimension.name),
|
| h('span', {
|
| className: 'text-2xl font-bold',
|
| style: { color: levelColor }
|
| }, `${pct.toFixed(0)}%`)
|
| ),
|
| h('p', { className: 'text-xs text-gray-500 mb-2' },
|
| `${dimScore.answeredQuestions}/${dimScore.totalQuestions} questions`),
|
| h('div', { className: 'w-full bg-gray-200 rounded-full h-2' },
|
| h('div', {
|
| className: 'h-2 rounded-full transition-all',
|
| style: { width: `${pct}%`, backgroundColor: levelColor }
|
| })
|
| )
|
| );
|
| })
|
| ),
|
|
|
|
|
| showSaveDialog && h('div', {
|
| className: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50',
|
| onClick: () => setShowSaveDialog(false)
|
| },
|
| h('div', {
|
| className: 'bg-white rounded-lg p-6 max-w-md w-full mx-4',
|
| onClick: (e) => e.stopPropagation()
|
| },
|
| h('h3', { className: 'text-xl font-bold mb-4' }, 'Save Assessment'),
|
| h('input', {
|
| type: 'text',
|
| placeholder: 'Assessment name',
|
| value: assessmentName,
|
| onChange: (e) => setAssessmentName(e.target.value),
|
| className: 'w-full px-4 py-2 border border-gray-300 rounded-lg mb-3 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
| }),
|
| h('input', {
|
| type: 'text',
|
| placeholder: 'Enter organization name',
|
| value: organizationName,
|
| onChange: (e) => setOrganizationName(e.target.value),
|
| className: 'w-full px-4 py-2 border border-gray-300 rounded-lg mb-4 focus:outline-none focus:ring-2 focus:ring-blue-500'
|
| }),
|
| h('div', { className: 'flex gap-2 justify-end' },
|
| h('button', {
|
| onClick: () => setShowSaveDialog(false),
|
| className: 'px-4 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600'
|
| }, 'Cancel'),
|
| h('button', {
|
| onClick: saveAssessmentToCloud,
|
| className: 'px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700'
|
| }, 'Save')
|
| )
|
| )
|
| ),
|
|
|
|
|
| showViewSaved && h('div', {
|
| className: 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4',
|
| onClick: () => setShowViewSaved(false)
|
| },
|
| h('div', {
|
| className: 'bg-white rounded-lg p-6 max-w-4xl w-full max-h-[80vh] overflow-y-auto',
|
| onClick: (e) => e.stopPropagation()
|
| },
|
| h('div', { className: 'flex justify-between items-center mb-4' },
|
| h('h3', { className: 'text-xl font-bold' }, 'Saved Assessments'),
|
| h('button', {
|
| onClick: () => setShowViewSaved(false),
|
| className: 'text-gray-500 hover:text-gray-700 text-2xl'
|
| }, '×')
|
| ),
|
| getSavedAssessments().length === 0
|
| ? h('p', { className: 'text-gray-500 text-center py-8' }, 'No saved assessments yet')
|
| : h('div', { className: 'space-y-4' },
|
| ...getSavedAssessments().map((assessment, index) =>
|
| h('div', {
|
| key: index,
|
| className: 'border border-gray-200 rounded-lg p-4 hover:bg-gray-50'
|
| },
|
| h('div', { className: 'flex justify-between items-start mb-2' },
|
| h('div', {},
|
| h('h4', { className: 'font-bold text-lg' }, assessment.assessmentName || 'Untitled Assessment'),
|
| h('p', { className: 'text-sm text-gray-700' }, assessment.organizationName),
|
| h('p', { className: 'text-sm text-gray-500' },
|
| new Date(assessment.timestamp).toLocaleString()
|
| )
|
| ),
|
| h('div', { className: 'flex gap-2' },
|
| h('button', {
|
| onClick: () => loadSavedAssessment(index),
|
| className: 'px-3 py-1 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm'
|
| }, 'Load'),
|
| h('button', {
|
| onClick: () => deleteSavedAssessment(index),
|
| className: 'px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700 text-sm'
|
| }, 'Delete')
|
| )
|
| ),
|
| h('div', { className: 'text-sm' },
|
| h('p', {},
|
| h('span', { className: 'font-semibold' }, 'Score: '),
|
| h('span', { style: { color: assessment.scores.maturityColor } },
|
| `${assessment.scores.overallScore.toFixed(1)}% - ${assessment.scores.maturityLevel}`)
|
| ),
|
| h('p', {},
|
| h('span', { className: 'font-semibold' }, 'Progress: '),
|
| `${assessment.scores.totalAnswered}/${assessment.scores.totalQuestions} questions`
|
| )
|
| )
|
| )
|
| )
|
| )
|
| )
|
| ),
|
|
|
|
|
| ...assessmentData.dimensions.map((dimension, dimIndex) =>
|
| h('div', {
|
| key: dimIndex,
|
| className: 'bg-white rounded-lg shadow-md mb-4 overflow-hidden'
|
| },
|
|
|
| h('div', {
|
| className: 'bg-gray-50 border-b border-gray-200 p-4 md:p-5 cursor-pointer hover:bg-gray-100',
|
| onClick: () => toggleSection(dimIndex)
|
| },
|
| h('div', { className: 'flex items-center justify-between' },
|
| h('div', {},
|
| h('h3', { className: 'text-lg md:text-xl font-bold text-gray-900' }, dimension.name)
|
| ),
|
| h('div', { className: 'flex items-center gap-4 text-sm text-gray-600' },
|
| h('span', {},
|
| `${calculations.dimensionScores[dimIndex].answeredQuestions}/${dimension.questions.length} answered`),
|
| h('span', {},
|
| `Score: ${calculations.dimensionScores[dimIndex].score.toFixed(1)}%`),
|
| h('span', {},
|
| `Weight: ${dimension.weight} pts`),
|
| h('svg', {
|
| className: `w-6 h-6 transform transition-transform ${expandedSections[dimIndex] ? 'rotate-180' : ''}`,
|
| fill: 'none',
|
| stroke: 'currentColor',
|
| viewBox: '0 0 24 24'
|
| },
|
| h('path', {
|
| strokeLinecap: 'round',
|
| strokeLinejoin: 'round',
|
| strokeWidth: 2,
|
| d: 'M19 9l-7 7-7-7'
|
| })
|
| )
|
| )
|
| )
|
| ),
|
|
|
|
|
| expandedSections[dimIndex] && h('div', { className: 'p-4 md:p-6' },
|
| ...dimension.questions.map((question, qIndex) => {
|
| const isUnanswered = responses[question.id] === undefined;
|
| const showRequired = showValidationErrors && isUnanswered;
|
|
|
| return h('div', {
|
| id: question.id,
|
| key: question.id,
|
| className: `mb-6 pb-6 border-b border-gray-200 last:border-b-0 last:mb-0 last:pb-0 ${showRequired ? 'ring-2 ring-red-400 rounded-lg p-3 -m-3' : ''}`
|
| },
|
| h('div', { className: 'mb-3' },
|
| h('div', { className: 'flex items-start gap-2' },
|
| h('span', { className: 'text-blue-600 font-semibold flex-shrink-0' }, `${qIndex + 1}.`),
|
| h('div', { className: 'flex-1' },
|
| h('p', { className: `font-medium ${showRequired ? 'text-red-700' : 'text-gray-900'}` }, question.text),
|
| showRequired && h('p', { className: 'text-xs text-red-600 mt-1' }, 'Required')
|
| )
|
| )
|
| ),
|
| h('div', { className: 'space-y-2 ml-6' },
|
| ...question.options.map((option, optIndex) => {
|
| const isSelected = responses[question.id] === optIndex;
|
| const points = option.points !== undefined ? option.points : optIndex * 3 + 3;
|
|
|
| return h('label', {
|
| key: optIndex,
|
| className: `flex items-start gap-3 p-3 border-2 rounded-lg cursor-pointer transition-all ${isSelected
|
| ? 'border-blue-500 bg-blue-50'
|
| : 'border-gray-200 hover:border-blue-300 hover:bg-gray-50'
|
| }`
|
| },
|
| h('input', {
|
| type: 'radio',
|
| name: question.id,
|
| value: optIndex,
|
| checked: isSelected,
|
| onChange: () => handleResponse(question.id, optIndex),
|
| className: 'mt-1 w-5 h-5 text-blue-600 flex-shrink-0'
|
| }),
|
| h('div', { className: 'flex-1' },
|
| h('div', { className: 'font-semibold text-gray-900 mb-1' },
|
| option.level || ['Curious/Aware', 'Experimenting', 'Adopting', 'Scaling', 'Leading'][optIndex]
|
| ),
|
| h('div', { className: 'text-sm text-gray-600' },
|
| option.description || option
|
| )
|
| ),
|
| h('div', { className: 'text-sm font-medium text-gray-500 flex-shrink-0' },
|
| `${points} pts`
|
| )
|
| );
|
| })
|
| ),
|
| h('div', { className: 'ml-6 mt-3' },
|
| h('label', { className: 'block text-sm font-medium text-gray-700 mb-1' }, 'Notes'),
|
| h('textarea', {
|
| value: notes[question.id] || '',
|
| onChange: (e) => setNotes(prev => ({ ...prev, [question.id]: e.target.value })),
|
| placeholder: 'Add your notes here...',
|
| rows: 2,
|
| className: 'w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 resize-y'
|
| })
|
| )
|
| );
|
| })
|
| )
|
| )
|
| )
|
| )
|
| );
|
| }
|
|
|
|
|
| ReactDOM.createRoot(document.getElementById('root')).render(h(AssessmentTool));
|
|
|
|
|