Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Name game #332

Merged
merged 14 commits into from
Feb 25, 2024
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

##### Februar 2024
- Qualix enthält neu ein Namenslernspiel! Auf der TN-Liste hat es einen Link zum Spiel [#332](https://github.com/gloggi/qualix/pull/332)

##### Januar 2024
- Aus technischen und praktischen Gründen wurde die Anzahl relevante Anforderungen in einer Rückmeldung auf maximal 40 limitiert. Auslöser war, dass die Übersichtstabelle sonst technisch wie auch visuell nicht mehr sinnvoll angezeigt werden konnte. Auch fachlich gesehen ist das Konzept der Rückmeldungen in Qualix nicht darauf ausgelegt, sehr viele eingebettete Anforderungen zu enthalten, da Übersichtlichkeit, Fördergedanke, Überprüfbarkeit, zweite Chancen, Zweitausbildung etc. alle darunter leiden. Dies sehen wir in folgenden Textstellen der RQF-Broschüre bestätigt, welche klar machen dass mit jeder einzelnen Mindestanforderung der Zeitaufwand für das Kursteam wie auch für die TN markant ansteigt:
> [Es] muss beachtet werden, dass zu jeder Mindestanforderung auch ein Beobachtungsmoment gehört, bei dem die TN zeigen können, was sie gelernt haben und das Kursteam ebendies wahrnehmen kann.[^1]
Expand Down
37 changes: 37 additions & 0 deletions app/Http/Controllers/NameGameController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace App\Http\Controllers;

use App\Exceptions\Handler;
use App\Exceptions\MiDataParticipantsListsParsingException;
use App\Exceptions\UnsupportedFormatException;
use App\Http\Requests\ParticipantImportRequest;
use App\Http\Requests\ParticipantRequest;
use App\Models\Course;
use App\Models\Participant;
use App\Util\HtmlString;
use Exception;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\RouteCollectionInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\URL;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class NameGameController extends Controller
{
/**
* Play the name game.
*
* @return Response
*/
public function index()
{
return view('nameGame.index');
}
}
2 changes: 0 additions & 2 deletions app/Http/Controllers/ParticipantController.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ public function upload(Request $request, Course $course) {
$request->session()->now('alert-warning', trans('t.views.admin.participant_import.warning_existing_participants'));
}


$MiDataParticipantListLink = (new HtmlString)
->s('<a href="https://db.scout.ch/" target="_blank">')
->__('t.views.admin.participant_import.MiData.name')
Expand All @@ -81,7 +80,6 @@ public function upload(Request $request, Course $course) {
* @throws ValidationException if parsing the uploaded file fails
*/
public function import(ParticipantImportRequest $request, Course $course) {

$request->validated();
try {
$imported = $request->getImporter()->import($request->file('file')->getRealPath(), $course);
Expand Down
78 changes: 78 additions & 0 deletions cypress/e2e/name-game.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useDatabaseResets } from "../support/databaseTransactions"

describe('name game', () => {
useDatabaseResets()

beforeEach(() => {
cy.then(() => {
cy.login().then(user => {
cy.artisan('e2e:scenario', { '--user-id': user.id })
})
})
cy.courseId()
})

it('plays through the easy version of the name game', function () {
cy.visit(`/course/${this.courseId}/participants`)

cy.contains('Namen lernen').click()

cy.contains('Name Game')

cy.get('#participants').click()
cy.get('#participants .multiselect__option').first().click()
cy.get('#participants .multiselect__option').eq(2).click()
cy.get('#participants .multiselect__option').eq(3).click()

// Click outside the multiselect to close the dropdown menu
cy.get('.card-body').click('right')

cy.contains('Los geht\'s').click()

cy.contains('Zeit:')

for (let i = 0; i < 3; i++) {
// 3 participants with 2 clicks each
cy.get('.name-game button[type=submit]').first().click()
cy.get('.name-game button[type=submit]').first().click()
}

cy.contains('Nochmals').click()

cy.contains('Los geht\'s')
})

it('plays through the hard version of the name game', function () {
cy.visit(`/course/${this.courseId}/participants`)

cy.contains('Namen lernen').click()

cy.contains('Name Game')

cy.get('#participants').click()
cy.get('#participants .multiselect__option').first().click()
cy.get('#participants .multiselect__option').eq(2).click()
cy.get('#participants .multiselect__option').eq(3).click()

// Click outside the multiselect to close the dropdown menu
cy.get('.card-body').click('right')

cy.get('#gameMode').click()
cy.contains('Schwierig (Namen eintippen)').click()

cy.contains('Los geht\'s').click()

cy.contains('Zeit:')

for (let i = 0; i < 3; i++) {
// 3 participants with 2 clicks each
cy.get('.name-game button[type=submit]').first().click()
cy.get('.name-game button[type=submit]').first().click()
}

cy.contains('Nochmals').click()

cy.contains('Los geht\'s')
cy.contains('Einfach (multiple choice)').should('not.be.visible')
})
})
30 changes: 30 additions & 0 deletions lang/de/t.php
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,35 @@
),
"via_midata" => "Via PBS MiData einloggen",
),
"name_game" => array(
"abort" => "Zurück zu den TN",
"correct" => "Richtig!",
"game_mode" => "Schwierigkeit",
"manual_name_input" => "Schwierig (Namen eintippen)",
"multiple_choice" => "Einfach (multiple choice)",
"name_game" => "Name Game",
"next" => "Weiter",
"no_image" => "kein Bild",
"num_correct_and_incorrect" => ":correct richtig, :incorrect falsch von :total",
"page_title" => "Name Game",
"participants" => "Namen",
"play_again" => "Nochmals",
"practice_wrong_names" => "Falsch geratene Namen üben",
"scout_name" => "Name",
"select_all" => "Alle auswählen",
"select_all_equipe_members" => "Equipe auswählen",
"select_all_participants" => "Alle TN auswählen",
"select_all_participants_with_image" => "Alle TN mit Bild auswählen",
"skip" => "Weiss nicht",
"start" => "Los geht's",
"submit" => "Abschicken",
"this_is" => "Das ist:",
"too_few_participants" => "Wähle mindestens 3 Personen aus",
"well_done" => "Super gemacht!",
"who_is_this" => "Wer ist das?",
"wrong_guesses" => "Folgende Namen hast du falsch geraten:",
"you_guessed" => "Deine Antwort:",
),
"observations" => array(
"add_success" => "Beobachtung erfasst. Mässi!",
"go_to_participant" => "Zu :name",
Expand All @@ -573,6 +602,7 @@
"participants" => array(
"here" => "hier",
"menu_name" => "TN",
"name_game" => "Namen lernen",
"no_participants" => "Bisher sind keine Teilnehmende erfasst. Bitte erfasse sie :here.",
"page_title" => "TN-Überblick",
"title" => "Beobachtung für TN erfassen",
Expand Down
48 changes: 48 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"yjs": "^13.5.12"
},
"dependencies": {
"@formatjs/intl-durationformat": "^0.2.2",
"jszip": "^3.10.1"
}
}
3 changes: 2 additions & 1 deletion resources/js/components/form/ButtonSubmit.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div class="form-group row mb-0">
<div :class="inputColumnClass">
<button type="submit" class="btn btn-primary mr-3 mb-1" :disabled="disabled" v-on="$listeners">
<button type="submit" class="btn btn-primary mr-3 mb-1" :disabled="disabled" :value="value" v-on="$listeners">
{{ label }}
</button>

Expand All @@ -15,6 +15,7 @@ export default {
name: 'ButtonSubmit',
props: {
label: { type: String, default: function() { return this.$t('t.global.save') } },
value: { default: null },
disabled: { type: Boolean, default: false },
narrowForm: { type: Boolean, default: false },
},
Expand Down
8 changes: 7 additions & 1 deletion resources/js/components/form/InputText.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
v-model="currentValue"
:required="required"
:autofocus="autofocus"
:v-focus="autofocus">
:v-focus="autofocus"
ref="input">

<span v-if="errorMessage" class="invalid-feedback" role="alert">
<strong>{{ errorMessage }}</strong>
Expand All @@ -50,6 +51,11 @@ export default {
label: { type: String, required: true },
type: { type: String, default: 'text' },
autofocus: { type: Boolean, default: false },
},
methods: {
focus() {
this.$refs.input.focus()
}
}
}
</script>
Expand Down
77 changes: 77 additions & 0 deletions resources/js/components/nameGame/AnswerInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div v-if="gameMode === 'multipleChoice'">
<button
v-for="option in multipleChoiceOptions"
:key="option.id"
type="submit"
class="btn btn-primary mr-3 mb-1 w-100 h-25"
:value="option.id">
{{ option.scout_name }}
</button>
</div>
<div v-else>
<input-text v-model="manualInput" :label="$t('t.views.name_game.scout_name')" name="scout_name" ref="scoutName"></input-text>
<button
type="submit"
class="btn btn-primary mr-3 mb-1 w-100 h-25">
{{ buttonLabel }}
</button>
</div>
</template>

<script>

import InputText from '../form/InputText.vue';

export default {
name: 'AnswerInput',
components: { InputText },
props: {
participant: { type: Object, required: true },
participants: { type: Array, required: true },
gameMode: { type: String, required: true },
},
data: () => ({
manualInput: '',
}),
computed: {
wrongGuess1() {
let result = this.participant
if (this.participants.length < 2) return result
while (result.id === this.participant.id) {
result = this.participants[Math.floor(Math.random() * this.participants.length)]
}
return result
},
wrongGuess2() {
let result = this.participant
if (this.participants.length < 3) return result
while (result.id === this.participant.id || result.id === this.wrongGuess1.id) {
result = this.participants[Math.floor(Math.random() * this.participants.length)]
}
return result
},
multipleChoiceOptions() {
const options = [this.participant, this.wrongGuess1, this.wrongGuess2]
options.sort((a, b) => a.scout_name.localeCompare(b.scout_name))
return options
},
buttonLabel() {
if (this.manualInput.length) {
return this.$t('t.views.name_game.submit')
}
return this.$t('t.views.name_game.skip')
},
},
mounted() {
if (this.gameMode === 'manualNameInput' && this.$refs.scoutName) {
this.manualInput = ''
this.$refs.scoutName.focus()
}
},
}
</script>

<style scoped>

</style>
28 changes: 28 additions & 0 deletions resources/js/components/nameGame/GuessPrompt.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<row-text v-if="image" :narrow-form="true">
<div class="square-container name-game-image">
<img class="card-img-top img img-responsive full-width" :src="participant.image_path" :alt="$t('t.views.name_game.who_is_this')">
</div>
</row-text>
<div v-else>{{ participant.scout_name }}</div>
</template>

<script>

export default {
name: 'GuessPrompt',
props: {
participant: { type: Object, required: true },
gameMode: { type: String, required: true },
},
computed: {
image() {
return ['multipleChoice', 'manualNameInput'].includes(this.gameMode)
},
}
}
</script>

<style scoped>

</style>
Loading
Loading