This commit is contained in:
Dimas Wiese
2026-03-15 23:34:23 +01:00
parent 4275cbd795
commit 6b0f87199c
63 changed files with 22786 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
<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 15 -->
<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>