qa: Improve design
This commit is contained in:
parent
13588fc634
commit
0e19b59452
11
qa/ui/src/app.css
Normal file
11
qa/ui/src/app.css
Normal file
@ -0,0 +1,11 @@
|
||||
.level .level-item {
|
||||
text-align: center;
|
||||
}
|
||||
.level .level-item .heading {
|
||||
font-variant: small-caps;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.level .level-item .value {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bolder;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<html lang="fr" class="d-flex flex-column mh-100 h-100">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@ -10,7 +10,7 @@
|
||||
<meta name="robots" content="none">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body>
|
||||
<body class="flex-fill d-flex flex-column">
|
||||
%sveltekit.body%
|
||||
</body>
|
||||
</html>
|
||||
|
41
qa/ui/src/lib/components/BadgeState.svelte
Normal file
41
qa/ui/src/lib/components/BadgeState.svelte
Normal file
@ -0,0 +1,41 @@
|
||||
<script>
|
||||
import {
|
||||
Badge,
|
||||
} from 'sveltestrap';
|
||||
|
||||
export { className as class };
|
||||
let className = '';
|
||||
|
||||
export let state = "ok";
|
||||
let color = "";
|
||||
$: {
|
||||
switch (state) {
|
||||
case "ok":
|
||||
color = "success";
|
||||
break;
|
||||
case "issue-flag":
|
||||
case "issue-statement":
|
||||
case "issue-mcq":
|
||||
case "issue-hint":
|
||||
case "issue-file":
|
||||
case "issue":
|
||||
color = "danger";
|
||||
break;
|
||||
case "orthograph":
|
||||
case "suggest":
|
||||
color = "info";
|
||||
break;
|
||||
case "too-hard":
|
||||
case "too-easy":
|
||||
color = "warning";
|
||||
break;
|
||||
default:
|
||||
color = "secondary";
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Badge {color} class={className}>
|
||||
{state}
|
||||
</Badge>
|
@ -3,12 +3,15 @@
|
||||
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Container,
|
||||
Icon,
|
||||
Row,
|
||||
Table,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import QAItems from '$lib/components/QAItems.svelte';
|
||||
import QAItem from '$lib/components/QAItem.svelte';
|
||||
import QANewItem from '$lib/components/QANewItem.svelte';
|
||||
import { themes, themesIdx } from '$lib/stores/themes';
|
||||
|
||||
@ -16,49 +19,87 @@
|
||||
themes.refresh();
|
||||
}
|
||||
|
||||
export let theme_id = null;
|
||||
export let exercice = {};
|
||||
let query_selected = null;
|
||||
let countCreation = 0;
|
||||
</script>
|
||||
|
||||
<h2>
|
||||
{#if query_selected}
|
||||
<Button
|
||||
class="float-start"
|
||||
color="link"
|
||||
on:click={() => query_selected = null}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
{:else if theme_id}
|
||||
<Button
|
||||
class="float-start"
|
||||
color="link"
|
||||
on:click={() => goto('themes/' + theme_id)}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
{:else}
|
||||
<Button
|
||||
class="float-start"
|
||||
color="link"
|
||||
on:click={() => goto('exercices/')}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
{/if}
|
||||
{exercice.title}
|
||||
{#if exercice.wip}
|
||||
<Icon name="cone-striped" />
|
||||
{/if}
|
||||
{#if $themes.length && $themesIdx[exercice.id_theme]}
|
||||
<small>
|
||||
<a href="themes/{exercice.id_theme}" title={$themesIdx[exercice.id_theme].authors}>{$themesIdx[exercice.id_theme].name}</a>
|
||||
</small>
|
||||
{#if $themesIdx[exercice.id_theme].exercices && $themesIdx[exercice.id_theme].exercices[exercice.id]}
|
||||
<div class="btn-group" role="group">
|
||||
<a href="exercices/{$themesIdx[exercice.id_theme].exercices[exercice.id].previous}" title="Exercice précédent" class:disabled={!$themesIdx[exercice.id_theme].exercices[exercice.id].previous} class="btn btn-sm btn-light"><Icon name="chevron-left" /></a>
|
||||
<a href="exercices/{$themesIdx[exercice.id_theme].exercices[exercice.id].next}" title="Exercice suivant" class:disabled={!$themesIdx[exercice.id_theme].exercices[exercice.id].next} class="btn btn-sm btn-light"><Icon name="chevron-right" /></a>
|
||||
</div>
|
||||
{/if}
|
||||
<a href="../{$themesIdx[exercice.id_theme].urlid}/{exercice.urlid}" target="_self" class="float-right ml-2 btn btn-sm btn-info"><Icon name="play-fill" /> Site du challenge</a>
|
||||
{/if}
|
||||
</h2>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 50vh;"
|
||||
>
|
||||
{@html exercice.statement}
|
||||
</div>
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 50vh;"
|
||||
>
|
||||
{@html exercice.overview}
|
||||
</div>
|
||||
</div>
|
||||
<Container fluid class="flex-fill d-flex">
|
||||
<Row class="flex-fill">
|
||||
<Col md={3} class="px-0 py-2" style="background: #e7e8e9">
|
||||
{#key countCreation}
|
||||
<QAItems
|
||||
bind:query_selected={query_selected}
|
||||
{exercice}
|
||||
/>
|
||||
{/key}
|
||||
</Col>
|
||||
<Col md={9} class="d-flex flex-column py-2">
|
||||
{#if query_selected}
|
||||
<QAItem
|
||||
bind:query_selected={query_selected}
|
||||
on:update-queries={() => countCreation++}
|
||||
/>
|
||||
{:else}
|
||||
<Row class="mb-3">
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 40vh;"
|
||||
>
|
||||
{@html exercice.statement}
|
||||
</div>
|
||||
<div
|
||||
class="col-md-6"
|
||||
style="overflow-y: auto; max-height: 40vh;"
|
||||
>
|
||||
{@html exercice.overview}
|
||||
</div>
|
||||
</Row>
|
||||
|
||||
<div class="mb-5">
|
||||
<QANewItem
|
||||
{exercice}
|
||||
on:new-query={() => countCreation++}
|
||||
/>
|
||||
{#key countCreation}
|
||||
<QAItems
|
||||
{exercice}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
<QANewItem
|
||||
{exercice}
|
||||
on:new-query={() => countCreation++}
|
||||
/>
|
||||
{/if}
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
@ -33,9 +33,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Navbar color="dark" dark expand="md">
|
||||
<Navbar color="dark" dark expand="xs">
|
||||
<NavbarBrand href=".">
|
||||
<img src="../img/fic.png" alt="FIC">
|
||||
<img src="../img/fic.png" alt="FIC" height="26">
|
||||
QA
|
||||
</NavbarBrand>
|
||||
<Nav navbar>
|
||||
@ -45,7 +45,7 @@
|
||||
active={activemenu === ''}
|
||||
>
|
||||
<Icon name="house-door" />
|
||||
Accueil
|
||||
<span class="d-none d-md-inline">Accueil</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
@ -54,7 +54,7 @@
|
||||
active={activemenu === 'themes'}
|
||||
>
|
||||
<Icon name="box-seam" />
|
||||
Scénarios
|
||||
<span class="d-none d-md-inline">Scénarios</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
@ -63,7 +63,7 @@
|
||||
active={activemenu === 'exercices'}
|
||||
>
|
||||
<Icon name="bar-chart-steps" />
|
||||
Étapes
|
||||
<span class="d-none d-md-inline">Étapes</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
@ -72,7 +72,7 @@
|
||||
active={activemenu === 'teams'}
|
||||
>
|
||||
<Icon name="people" />
|
||||
Équipes
|
||||
<span class="d-none d-md-inline">Équipes</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
@ -81,12 +81,12 @@
|
||||
active={activemenu === 'repositories'}
|
||||
>
|
||||
<Icon name="archive" />
|
||||
Dépôts
|
||||
<span class="d-none d-md-inline">Dépôts</span>
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
<Nav class="ms-auto text-light" navbar>
|
||||
<NavItem class="ms-2">
|
||||
<NavItem class="ms-2 text-truncate">
|
||||
v{$version.version}
|
||||
{#if $auth}– Logged as {$auth.name} (team #{$auth.id_team}){/if}
|
||||
</NavItem>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Spinner,
|
||||
} from 'sveltestrap';
|
||||
|
||||
@ -45,6 +46,9 @@
|
||||
<div class={className}>
|
||||
<h3>Vos étapes</h3>
|
||||
{#await my_exercicesP}
|
||||
<div class="text-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then}
|
||||
<table class="table table-stripped table-hover">
|
||||
<thead>
|
||||
|
@ -2,6 +2,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Icon,
|
||||
Spinner,
|
||||
} from 'sveltestrap';
|
||||
|
||||
@ -30,8 +31,14 @@
|
||||
<div class={className}>
|
||||
<h3>Étapes à tester et valider</h3>
|
||||
{#await todos.refresh()}
|
||||
<div class="text-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then}
|
||||
{#await exo_doneP}
|
||||
<div class="text-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then exo_done}
|
||||
<table class="table table-stripped table-hover">
|
||||
<thead>
|
||||
|
@ -2,24 +2,63 @@
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Col,
|
||||
Icon,
|
||||
Row,
|
||||
Spinner,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import BadgeState from '$lib/components/BadgeState.svelte';
|
||||
import DateFormat from '$lib/components/DateFormat.svelte';
|
||||
import { getQAComments, QAComment } from '$lib/qa.js';
|
||||
import { getQAComments, QAComment } from '$lib/qa';
|
||||
import { auth } from '$lib/stores/auth';
|
||||
import { viewIdx } from '$lib/stores/todo';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let query_selected;
|
||||
|
||||
let query_commentsP;
|
||||
$: query_commentsP = getQAComments(query_selected.id);
|
||||
let thumbs = [];
|
||||
let thumb_me = [];
|
||||
let has_comments = false;
|
||||
$: updateComments(query_selected)
|
||||
|
||||
function updateComments(query_selected) {
|
||||
if (query_selected) {
|
||||
query_commentsP = getQAComments(query_selected.id);
|
||||
query_commentsP.then((comments) => {
|
||||
thumbs = [];
|
||||
thumb_me = [];
|
||||
has_comments = false;
|
||||
|
||||
for (const c of comments) {
|
||||
has_comments = true;
|
||||
if (c.content == "+1") {
|
||||
let found = false;
|
||||
for (const t of thumbs) {
|
||||
if (t == c.user) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
thumbs.push(c.user);
|
||||
}
|
||||
if (c.user == $auth.name) {
|
||||
thumb_me.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let newComment = new QAComment();
|
||||
let submissionInProgress = false;
|
||||
@ -65,119 +104,182 @@
|
||||
dispatch("update-queries");
|
||||
})
|
||||
}
|
||||
|
||||
function deleteMyThumbs() {
|
||||
if (thumb_me.length) {
|
||||
for (const c of thumb_me) {
|
||||
c.delete(query_selected.id);
|
||||
}
|
||||
dispatch("update-queries");
|
||||
}
|
||||
}
|
||||
|
||||
function deleteComment(comment) {
|
||||
comment.delete(query_selected.id).then(() => {
|
||||
dispatch("update-queries");
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="d-flex justify-content-between">
|
||||
<h4 class="card-title mb-0">{query_selected.subject}</h4>
|
||||
<div>
|
||||
{#if $auth && $auth.id_team == query_selected.id_team}
|
||||
{#if query_selected.solved && !query_selected.closed}
|
||||
<Button on:click={closeQA} color="success">
|
||||
<Icon name="check" />
|
||||
Valider la résolution
|
||||
</Button>
|
||||
<Button on:click={reopenQA} color="danger">
|
||||
<Icon name="x" />
|
||||
Réouvrir
|
||||
</Button>
|
||||
{/if}
|
||||
{#if !query_selected.solved}
|
||||
<Button on:click={deleteQA} color="danger">
|
||||
<Icon name="trash-fill" />
|
||||
Supprimer
|
||||
</Button>
|
||||
{/if}
|
||||
{:else if $auth && !query_selected.solved}
|
||||
<Button on:click={solveQA} color="success">
|
||||
<Icon name="check" />
|
||||
Marquer comme résolu
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<div class="row">
|
||||
<dl class="col row">
|
||||
<dt class="col-3">Qui ?</dt>
|
||||
<dd class="col-9">{query_selected.user} (team #{query_selected.id_team})</dd>
|
||||
|
||||
<dt class="col-3">État</dt>
|
||||
<dd class="col-9">{query_selected.state}</dd>
|
||||
|
||||
<dt class="col-3">Date de création</dt>
|
||||
<dd class="col-9">
|
||||
<DateFormat date={query_selected.creation} dateStyle="long" timeStyle="medium" />
|
||||
</dd>
|
||||
|
||||
<dt class="col-3">Date de résolution</dt>
|
||||
<dd class="col-9">
|
||||
{#if query_selected.solved}
|
||||
<DateFormat date={query_selected.solved} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
</dd>
|
||||
|
||||
<dt class="col-3">Date de clôture</dt>
|
||||
<dd class="col-9">
|
||||
{#if query_selected.closed}
|
||||
<DateFormat date={query_selected.closed} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="col-auto">
|
||||
{#if $auth && $auth.id_team == query_selected.id_team}
|
||||
<Button on:click={updateQA} color="primary">
|
||||
<Icon name="upload" />
|
||||
Mettre à jour
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#await query_commentsP}
|
||||
<div class="d-flex">
|
||||
<Spinner />
|
||||
{#if query_selected}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h4 class="card-title fw-bold mb-0">{query_selected.subject}</h4>
|
||||
<div>
|
||||
Chargement des commentaires en cours…
|
||||
{#if $auth && !query_selected.solved && $viewIdx[query_selected.id_exercice]}
|
||||
<Button on:click={solveQA} color="success">
|
||||
<Icon name="check" />
|
||||
Marquer comme résolu
|
||||
</Button>
|
||||
{/if}
|
||||
{#if $auth && $auth.id_team == query_selected.id_team}
|
||||
{#if query_selected.solved && !query_selected.closed && (query_selected.subject != "RAS" || query_selected.state != "ok")}
|
||||
<Button on:click={closeQA} color="success">
|
||||
<Icon name="check" />
|
||||
Valider la résolution
|
||||
</Button>
|
||||
<Button on:click={reopenQA} color="danger">
|
||||
<Icon name="x" />
|
||||
Réouvrir
|
||||
</Button>
|
||||
{/if}
|
||||
{#if (!query_selected.solved && !has_comments) || (query_selected.subject == "RAS" && query_selected.state == "ok" && !has_comments)}
|
||||
<Button on:click={deleteQA} color="danger">
|
||||
<Icon name="trash-fill" />
|
||||
Supprimer
|
||||
</Button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:then query_comments}
|
||||
<table class="table table-striped">
|
||||
{#each query_comments as comment (comment.id)}
|
||||
<tr>
|
||||
<td style="white-space: pre-line">
|
||||
Le <DateFormat date={comment.date} dateStyle="medium" timeStyle="short" />, <strong>{comment.user}</strong> a écrit : {comment.content}
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
{/await}
|
||||
<form on:submit|preventDefault={addComment}>
|
||||
<label for="newComment">Répondre :</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
placeholder="Ajouter un commentaire"
|
||||
rows="2"
|
||||
id="newComment"
|
||||
bind:value={newComment.content}
|
||||
></textarea>
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="mt-1 float-right"
|
||||
disabled={!newComment.content || newComment.content.length == 0 || submissionInProgress}
|
||||
>
|
||||
{#if submissionInProgress}
|
||||
<Spinner size="sm" />
|
||||
{/if}
|
||||
Ajouter le commentaire
|
||||
</Button>
|
||||
</form>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Row class="level" cols={5}>
|
||||
<Col>
|
||||
<div class="level-item">
|
||||
<div class="heading">
|
||||
Qui ?
|
||||
</div>
|
||||
<div class="value">
|
||||
{query_selected.user.split("@")[0]}
|
||||
</div>
|
||||
<div class="text-muted">
|
||||
(team #{query_selected.id_team})
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
<Col>
|
||||
<div class="level-item">
|
||||
<div class="heading">
|
||||
État
|
||||
</div>
|
||||
<BadgeState class="value" state={query_selected.state} />
|
||||
<div
|
||||
class="text-muted"
|
||||
title={thumbs.join(', ')}
|
||||
on:click={deleteMyThumbs}
|
||||
>
|
||||
<Icon
|
||||
name="hand-thumbs-up-fill"
|
||||
class={thumb_me.length?"text-info":""}
|
||||
style={thumb_me.length?"cursor: pointer;":""}
|
||||
/>
|
||||
{thumbs.length}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<div class="level-item">
|
||||
<div class="heading">
|
||||
Date de création
|
||||
</div>
|
||||
<div class="value">
|
||||
<DateFormat date={query_selected.creation} dateStyle="long" timeStyle="medium" />
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<div class="level-item">
|
||||
<div class="heading">
|
||||
Date de résolution
|
||||
</div>
|
||||
<div class="value">
|
||||
{#if query_selected.solved}
|
||||
<DateFormat date={query_selected.solved} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<div class="level-item">
|
||||
<div class="heading">
|
||||
Date de clôture
|
||||
</div>
|
||||
<div class="value">
|
||||
{#if query_selected.closed}
|
||||
<DateFormat date={query_selected.closed} dateStyle="long" timeStyle="medium" />
|
||||
{:else}
|
||||
-
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<hr>
|
||||
{#await query_commentsP then query_comments}
|
||||
{#each query_comments as comment (comment.id)}
|
||||
{#if comment.content != "+1"}
|
||||
<Alert fade={false}>
|
||||
<div style="white-space: pre-line">{comment.content}</div>
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
{#if comment.user == $auth.name}
|
||||
<Button
|
||||
size="sm"
|
||||
color="danger"
|
||||
class="me-2"
|
||||
on:click={() => deleteComment(comment)}
|
||||
>
|
||||
<Icon name="trash-fill" />
|
||||
</Button>
|
||||
{/if}
|
||||
<em>
|
||||
Par <strong>{comment.user}</strong>, le <DateFormat date={comment.date} dateStyle="medium" timeStyle="short" />
|
||||
</em>
|
||||
</div>
|
||||
</Alert>
|
||||
{/if}
|
||||
{/each}
|
||||
{/await}
|
||||
<form on:submit|preventDefault={addComment}>
|
||||
<textarea
|
||||
class="form-control"
|
||||
placeholder="Ajouter votre commentaire"
|
||||
rows="2"
|
||||
id="newComment"
|
||||
bind:value={newComment.content}
|
||||
></textarea>
|
||||
{#if newComment.content && newComment.content.length > 0}
|
||||
<Button
|
||||
type="submit"
|
||||
color="primary"
|
||||
class="mt-1 float-right"
|
||||
disabled={submissionInProgress}
|
||||
>
|
||||
{#if submissionInProgress}
|
||||
<Spinner size="sm" />
|
||||
{/if}
|
||||
Ajouter le commentaire
|
||||
</Button>
|
||||
{/if}
|
||||
</form>
|
||||
|
||||
</CardBody>
|
||||
</Card>
|
||||
{/if}
|
||||
|
@ -6,13 +6,14 @@
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { getExerciceQA, QAComment } from '$lib/qa.js';
|
||||
import QAItem from '$lib/components/QAItem.svelte';
|
||||
import BadgeState from '$lib/components/BadgeState.svelte';
|
||||
|
||||
export { className as class };
|
||||
let className = '';
|
||||
|
||||
export let exercice = { };
|
||||
export let query_selected = null;
|
||||
|
||||
const fields = [ "state", "subject" ];
|
||||
|
||||
let queriesP;
|
||||
$: queriesP = getExerciceQA(exercice.id);
|
||||
|
||||
@ -34,62 +35,46 @@
|
||||
|
||||
{#await queriesP}
|
||||
{:then queries}
|
||||
<table
|
||||
class="table table-bordered table-striped"
|
||||
class:table-hover={queries.length}
|
||||
class:table-sm={queries.length}
|
||||
>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
{#each fields as field}
|
||||
<th>
|
||||
{ field }
|
||||
</th>
|
||||
{/each}
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<div class="list-group {className}" class:list-group-flush={true}>
|
||||
{#if queries.length}
|
||||
<tbody>
|
||||
{#each queries as q (q.id)}
|
||||
<tr on:click={() => query_selected = q} class:bg-warning={query_selected && q.id == query_selected.id}>
|
||||
{#each fields as field}
|
||||
<td>
|
||||
{@html q[field]}
|
||||
</td>
|
||||
{/each}
|
||||
<td>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-light"
|
||||
disabled={thumbInProgress !== null}
|
||||
on:click|preventDefault={() => thumbUp(q.id)}
|
||||
>
|
||||
{#if thumbInProgress == q.id}
|
||||
<Spinner size="sm" />
|
||||
{:else}
|
||||
<Icon name="hand-thumbs-up" />
|
||||
{/if}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
{:else}
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan={fields.length+1} class="font-weight-bold text-info text-center">
|
||||
Aucune requête enregistrée
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
{#each queries as q (q.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"
|
||||
class:active={query_selected && q.id == query_selected.id}
|
||||
aria-current="true"
|
||||
on:click={() => query_selected = q}
|
||||
>
|
||||
<div class="text-truncate">
|
||||
<BadgeState state={q.state} />
|
||||
{#if !q.solved}
|
||||
<strong>{q.subject}</strong>
|
||||
{:else if !q.closed}
|
||||
{q.subject}
|
||||
{:else}
|
||||
<s>{q.subject}</s>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !q.closed}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-info"
|
||||
disabled={thumbInProgress !== null}
|
||||
on:click|preventDefault={() => thumbUp(q.id)}
|
||||
>
|
||||
{#if thumbInProgress == q.id}
|
||||
<Spinner size="sm" />
|
||||
{:else}
|
||||
<Icon name="hand-thumbs-up-fill" />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="fw-bold text-center">
|
||||
Aucune requête enregistrée
|
||||
</div>
|
||||
{/if}
|
||||
</table>
|
||||
|
||||
{#if query_selected}
|
||||
<QAItem
|
||||
bind:query_selected={query_selected}
|
||||
on:update-queries={updateQueries}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/await}
|
||||
|
@ -91,7 +91,7 @@ export class QAComment {
|
||||
method: 'DELETE',
|
||||
headers: {'Accept': 'application/json'}
|
||||
});
|
||||
if (res.status == 200) {
|
||||
if (res.status < 300) {
|
||||
return true;
|
||||
} else {
|
||||
throw new Error((await res.json()).errmsg);
|
||||
|
@ -37,3 +37,19 @@ export const exercicesIdx = derived(
|
||||
return exercices_idx;
|
||||
},
|
||||
);
|
||||
|
||||
export const exercicesByTheme = derived(
|
||||
exercices,
|
||||
$exercices => {
|
||||
const exercices_idx = { };
|
||||
|
||||
for (const e of $exercices) {
|
||||
if (!exercices_idx[e.id_theme]) {
|
||||
exercices_idx[e.id_theme] = []
|
||||
}
|
||||
exercices_idx[e.id_theme].push(e);
|
||||
}
|
||||
|
||||
return exercices_idx;
|
||||
},
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { writable } from 'svelte/store';
|
||||
import { writable, derived } from 'svelte/store';
|
||||
|
||||
import { getQAWork } from '$lib/todo'
|
||||
import { getQAView, getQAWork } from '$lib/todo';
|
||||
|
||||
function createTodosStore() {
|
||||
const { subscribe, set, update } = writable([]);
|
||||
@ -24,3 +24,39 @@ function createTodosStore() {
|
||||
}
|
||||
|
||||
export const todos = createTodosStore();
|
||||
|
||||
function createViewStore() {
|
||||
const { subscribe, set, update } = writable([]);
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
|
||||
set: (v) => {
|
||||
update((m) => Object.assign(m, v));
|
||||
},
|
||||
|
||||
update,
|
||||
|
||||
refresh: async () => {
|
||||
const list = await getQAView();
|
||||
update((m) => list);
|
||||
return list;
|
||||
},
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export const view = createViewStore();
|
||||
|
||||
export const viewIdx = derived(
|
||||
view,
|
||||
$view => {
|
||||
const idx = { };
|
||||
|
||||
for (const v of $view) {
|
||||
idx[v.id_exercice] = v;
|
||||
}
|
||||
|
||||
return idx;
|
||||
}
|
||||
);
|
||||
|
@ -1,4 +1,6 @@
|
||||
<script>
|
||||
import '../app.css';
|
||||
|
||||
import {
|
||||
Container,
|
||||
Styles,
|
||||
@ -6,9 +8,13 @@
|
||||
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import { version } from '$lib/stores/auth';
|
||||
import { view } from '$lib/stores/todo';
|
||||
|
||||
version.refresh();
|
||||
setInterval(version.refresh, 30000);
|
||||
|
||||
view.refresh();
|
||||
setInterval(view.refresh, 60000);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import {
|
||||
Alert,
|
||||
Col,
|
||||
Container,
|
||||
Row,
|
||||
} from 'sveltestrap';
|
||||
@ -32,7 +33,11 @@
|
||||
</p>
|
||||
</Alert>
|
||||
<Row>
|
||||
<MyTodo class="col-6" />
|
||||
<MyExercices class="col-6" />
|
||||
<Col>
|
||||
<MyTodo />
|
||||
</Col>
|
||||
<Col>
|
||||
<MyExercices />
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
|
@ -47,6 +47,9 @@
|
||||
{#each fieldsExercices as field}
|
||||
<td>
|
||||
{@html exercice[field]}
|
||||
{#if field == "title" && exercice.wip}
|
||||
<Icon name="cone-striped" />
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
|
@ -15,19 +15,12 @@
|
||||
let exerciceP = getExercice($page.params.eid);
|
||||
</script>
|
||||
|
||||
<Container class="mt-2 mb-5">
|
||||
{#await exerciceP}
|
||||
{#await exerciceP}
|
||||
<Container class="mt-2 mb-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then exercice}
|
||||
<Button
|
||||
class="float-start"
|
||||
color="link"
|
||||
on:click={() => goto('exercices')}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
<ExerciceQA {exercice} />
|
||||
{/await}
|
||||
</Container>
|
||||
</Container>
|
||||
{:then exercice}
|
||||
<ExerciceQA {exercice} />
|
||||
{/await}
|
||||
|
@ -41,9 +41,9 @@
|
||||
{#if theme.name.indexOf(query) >= 0 || theme.authors.indexOf(query) >= 0 || theme.intro.indexOf(query) >= 0}
|
||||
<tr on:click={() => show(theme.id)}>
|
||||
{#each fields as field}
|
||||
<td>
|
||||
<td class:text-end={field == "image"}>
|
||||
{#if field == "image"}
|
||||
<img src={"../files" + theme[field]} alt="Image du scénario">
|
||||
<img src={"../files" + theme[field]} alt="Image du scénario" height="120">
|
||||
{:else}
|
||||
{@html theme[field]}
|
||||
{/if}
|
||||
|
@ -40,13 +40,13 @@
|
||||
<small class="m-2 mb-3 text-muted text-truncate">{@html theme.authors}</small>
|
||||
</div>
|
||||
|
||||
<Container class="text-muted">
|
||||
<Container class="text-muted" style="overflow-y: auto; max-height: 34vh">
|
||||
{@html theme.intro}
|
||||
</Container>
|
||||
|
||||
{#await getThemedExercices($page.params.tid)}
|
||||
{:then exercices}
|
||||
<h3>
|
||||
<h3 class="mt-2">
|
||||
Défis ({exercices.length})
|
||||
</h3>
|
||||
|
||||
@ -70,6 +70,9 @@
|
||||
{#each fieldsExercices as field}
|
||||
<td>
|
||||
{@html exercice[field]}
|
||||
{#if field == "title" && exercice.wip}
|
||||
<Icon name="cone-striped" />
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
|
@ -15,19 +15,12 @@
|
||||
let exerciceP = getThemedExercice($page.params.tid, $page.params.eid);
|
||||
</script>
|
||||
|
||||
<Container class="mt-2 mb-5">
|
||||
{#await exerciceP}
|
||||
{#await exerciceP}
|
||||
<Container class="mt-2 mb-5">
|
||||
<div class="d-flex justify-content-center">
|
||||
<Spinner size="lg" />
|
||||
</div>
|
||||
{:then exercice}
|
||||
<Button
|
||||
class="float-start"
|
||||
color="link"
|
||||
on:click={() => goto('themes/' + $page.params.tid)}
|
||||
>
|
||||
<Icon name="chevron-left" />
|
||||
</Button>
|
||||
<ExerciceQA {exercice} />
|
||||
{/await}
|
||||
</Container>
|
||||
</Container>
|
||||
{:then exercice}
|
||||
<ExerciceQA theme_id={$page.params.tid} {exercice} />
|
||||
{/await}
|
||||
|
Loading…
Reference in New Issue
Block a user