186 lines
6.3 KiB
HTML
186 lines
6.3 KiB
HTML
<ion-header>
|
||
<ion-toolbar color="primary">
|
||
<ion-title>Survey</ion-title>
|
||
</ion-toolbar>
|
||
</ion-header>
|
||
|
||
<ion-content class="ion-padding">
|
||
|
||
<!-- ── Connecting ── -->
|
||
<div *ngIf="state === 'connecting'" class="state-card">
|
||
<ion-spinner name="crescent"></ion-spinner>
|
||
<h3>Connecting to survey host…</h3>
|
||
<p>Please wait while we establish a peer-to-peer connection.</p>
|
||
</div>
|
||
|
||
<!-- ── Connected (waiting for survey data) ── -->
|
||
<div *ngIf="state === 'connected'" class="state-card">
|
||
<ion-spinner name="dots"></ion-spinner>
|
||
<h3>Loading survey…</h3>
|
||
</div>
|
||
|
||
<!-- ── Host offline ── -->
|
||
<ion-card *ngIf="state === 'host-offline'" color="warning">
|
||
<ion-card-header>
|
||
<ion-card-title>
|
||
<ion-icon name="wifi-outline"></ion-icon>
|
||
Host is Offline
|
||
</ion-card-title>
|
||
</ion-card-header>
|
||
<ion-card-content>
|
||
<p>
|
||
The survey host's browser is not currently accepting connections.
|
||
The survey creator needs to open the survey and click <strong>Start Hosting</strong>.
|
||
</p>
|
||
<p>Please try again later or ask the survey creator to come online.</p>
|
||
</ion-card-content>
|
||
</ion-card>
|
||
|
||
<!-- ── Error ── -->
|
||
<ion-card *ngIf="state === 'error'" color="danger">
|
||
<ion-card-header>
|
||
<ion-card-title>
|
||
<ion-icon name="alert-circle-outline"></ion-icon>
|
||
Error
|
||
</ion-card-title>
|
||
</ion-card-header>
|
||
<ion-card-content>
|
||
<p>{{ errorMessage }}</p>
|
||
</ion-card-content>
|
||
</ion-card>
|
||
|
||
<!-- ── Survey form ── -->
|
||
<ng-container *ngIf="state === 'survey-loaded' && survey">
|
||
<div class="survey-header">
|
||
<h2>{{ survey.title }}</h2>
|
||
<p *ngIf="survey.description" class="survey-description">{{ survey.description }}</p>
|
||
</div>
|
||
|
||
<!-- Question cards -->
|
||
<ion-card *ngFor="let question of survey.questions; let i = index">
|
||
<ion-card-header>
|
||
<ion-card-subtitle>
|
||
Question {{ i + 1 }}
|
||
<span *ngIf="question.required" class="required-mark">*</span>
|
||
</ion-card-subtitle>
|
||
<ion-card-title class="question-title">{{ question.text }}</ion-card-title>
|
||
</ion-card-header>
|
||
<ion-card-content>
|
||
|
||
<!-- Free text -->
|
||
<ion-item *ngIf="question.type === 'text'" lines="none">
|
||
<ion-textarea
|
||
[(ngModel)]="answers[question.id]"
|
||
placeholder="Your answer…"
|
||
rows="4"
|
||
maxlength="2000"
|
||
(ionBlur)="saveDraft()">
|
||
</ion-textarea>
|
||
</ion-item>
|
||
|
||
<!-- Multiple choice -->
|
||
<ion-radio-group
|
||
*ngIf="question.type === 'multiple_choice'"
|
||
[(ngModel)]="answers[question.id]"
|
||
(ngModelChange)="saveDraft()">
|
||
<ion-item *ngFor="let option of question.options" lines="none">
|
||
<ion-radio [value]="option" slot="start"></ion-radio>
|
||
<ion-label>{{ option }}</ion-label>
|
||
</ion-item>
|
||
</ion-radio-group>
|
||
|
||
<!-- Yes / No -->
|
||
<ion-radio-group
|
||
*ngIf="question.type === 'yes_no'"
|
||
[(ngModel)]="answers[question.id]"
|
||
(ngModelChange)="saveDraft()">
|
||
<ion-item lines="none">
|
||
<ion-radio value="Yes" slot="start"></ion-radio>
|
||
<ion-label>Yes</ion-label>
|
||
</ion-item>
|
||
<ion-item lines="none">
|
||
<ion-radio value="No" slot="start"></ion-radio>
|
||
<ion-label>No</ion-label>
|
||
</ion-item>
|
||
</ion-radio-group>
|
||
|
||
<!-- Rating 1–5 -->
|
||
<div *ngIf="question.type === 'rating'" class="rating-row">
|
||
<ion-button
|
||
*ngFor="let val of ratingValues()"
|
||
[fill]="answers[question.id] === val ? 'solid' : 'outline'"
|
||
[color]="answers[question.id] === val ? 'primary' : 'medium'"
|
||
(click)="answers[question.id] = val; saveDraft()"
|
||
size="small">
|
||
{{ val }}
|
||
</ion-button>
|
||
</div>
|
||
|
||
</ion-card-content>
|
||
</ion-card>
|
||
|
||
<p class="required-note">* Required question</p>
|
||
|
||
<div class="submit-area">
|
||
<ion-button expand="block" (click)="submit()" [disabled]="!isFormValid">
|
||
<ion-icon slot="start" name="checkmark-outline"></ion-icon>
|
||
Submit
|
||
</ion-button>
|
||
</div>
|
||
</ng-container>
|
||
|
||
<!-- ── Submitted ── -->
|
||
<div *ngIf="state === 'submitted'" class="state-card success-state">
|
||
<ion-icon name="checkmark-circle-outline" color="success" size="large"></ion-icon>
|
||
<h2>Thank you!</h2>
|
||
<p>Your response has been submitted successfully.</p>
|
||
|
||
<!-- Show results if host sent them -->
|
||
<ng-container *ngIf="results && survey">
|
||
<div class="results-divider">
|
||
<ion-label>Survey Results</ion-label>
|
||
</div>
|
||
|
||
<ion-card *ngFor="let q of survey.questions">
|
||
<ion-card-header>
|
||
<ion-card-title class="question-title">{{ q.text }}</ion-card-title>
|
||
</ion-card-header>
|
||
<ion-card-content>
|
||
<ng-container *ngIf="getQuestionResult(q.id) as qr">
|
||
|
||
<!-- Tally (multiple_choice / yes_no) -->
|
||
<div *ngIf="qr.tally">
|
||
<div *ngFor="let entry of tallyEntries(qr.tally)" class="bar-row">
|
||
<span class="bar-label">{{ entry.key }}</span>
|
||
<div class="bar-track">
|
||
<div class="bar-fill" [style.width.%]="tallyPercent(entry.count)"></div>
|
||
</div>
|
||
<span class="bar-count">{{ entry.count }} ({{ tallyPercent(entry.count) }}%)</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Text answers -->
|
||
<div *ngIf="qr.texts">
|
||
<p *ngFor="let t of qr.texts; let i = index">{{ i + 1 }}. {{ t }}</p>
|
||
</div>
|
||
|
||
<!-- Rating -->
|
||
<div *ngIf="qr.ratingDistribution">
|
||
<p>Average: <strong>{{ formatAvg(qr.ratingAvg) }}</strong> / 5</p>
|
||
<div *ngFor="let count of qr.ratingDistribution; let i = index" class="bar-row">
|
||
<span class="bar-label">{{ ratingLabel(i) }} ★</span>
|
||
<div class="bar-track">
|
||
<div class="bar-fill" [style.width.%]="tallyPercent(count)"></div>
|
||
</div>
|
||
<span class="bar-count">{{ count }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
</ng-container>
|
||
</ion-card-content>
|
||
</ion-card>
|
||
</ng-container>
|
||
</div>
|
||
|
||
</ion-content>
|