337 lines
11 KiB
Svelte
337 lines
11 KiB
Svelte
<script>
|
|
import { goto, invalidate } from '$app/navigation';
|
|
|
|
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';
|
|
import { auth } from '$lib/stores/auth';
|
|
import { ToastsStore } from '$lib/stores/toasts';
|
|
import { viewIdx } from '$lib/stores/todo';
|
|
|
|
export let theme = null;
|
|
export let exercice = null;
|
|
export let query;
|
|
|
|
let query_commentsP;
|
|
let thumbs = [];
|
|
let thumb_me = [];
|
|
let has_comments = false;
|
|
$: updateComments(query)
|
|
|
|
function updateComments(query) {
|
|
if (query) {
|
|
query_commentsP = getQAComments(query.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;
|
|
function addComment() {
|
|
submissionInProgress = true;
|
|
newComment.save(query.id).then(() => {
|
|
query_commentsP = getQAComments(query.id);
|
|
newComment = new QAComment();
|
|
submissionInProgress = false;
|
|
}).catch((err) => {
|
|
submissionInProgress = false;
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
function updateQA() {
|
|
query.save().then(() => {
|
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
function solveQA() {
|
|
query.solved = new Date();
|
|
query.save().then(() => {
|
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
function reopenQA() {
|
|
query.solved = null;
|
|
query.save().then(() => {
|
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
function closeQA() {
|
|
query.closed = new Date();
|
|
query.save().then(() => {
|
|
invalidate(`api/exercices/${exercice.id}/qa`);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
async function deleteQA() {
|
|
query.delete().then(async () => {
|
|
await invalidate(`api/exercices/${exercice.id}/qa`);
|
|
goto(theme?`themes/${theme.id}/${exercice.id}`:`exercices/${exercice.id}`);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
function deleteMyThumbs() {
|
|
if (thumb_me.length) {
|
|
for (const c of thumb_me) {
|
|
c.delete(query.id);
|
|
}
|
|
updateComments(query);
|
|
}
|
|
}
|
|
|
|
function deleteComment(comment) {
|
|
comment.delete(query.id).then(() => {
|
|
updateComments(query);
|
|
}).catch((err) => {
|
|
ToastsStore.addErrorToast({
|
|
msg: err,
|
|
})
|
|
})
|
|
}
|
|
|
|
async function rungitlab() {
|
|
const res = await fetch('api/gitlab/token');
|
|
if (res.status === 200) {
|
|
return res.json();
|
|
} else {
|
|
throw new Error((await res.json()).errmsg);
|
|
}
|
|
}
|
|
|
|
const gitlab = rungitlab();
|
|
</script>
|
|
|
|
{#if query}
|
|
<Card>
|
|
<CardHeader>
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<h4 class="card-title fw-bold mb-0">{query.subject}</h4>
|
|
<div>
|
|
{#if $auth && !query.solved}
|
|
{#await gitlab then gl}
|
|
<Button
|
|
on:click={() => query.export2Gitlab()}
|
|
>
|
|
<Icon name="gitlab" />
|
|
Exporter vers GitLab
|
|
</Button>
|
|
{/await}
|
|
{/if}
|
|
{#if $auth && !query.solved && $viewIdx[query.id_exercice]}
|
|
<Button on:click={solveQA} color="success">
|
|
<Icon name="check" />
|
|
Marquer comme résolu
|
|
</Button>
|
|
{/if}
|
|
{#if $auth && $auth.id_team == query.id_team}
|
|
{#if query.solved && !query.closed && (query.subject != "RAS" || query.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.solved && !has_comments) || (query.subject == "RAS" && query.state == "ok" && !has_comments)}
|
|
<Button on:click={deleteQA} color="danger">
|
|
<Icon name="trash-fill" />
|
|
Supprimer
|
|
</Button>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</CardHeader>
|
|
<CardBody>
|
|
<Row class="level" cols={5}>
|
|
<Col>
|
|
<div class="level-item">
|
|
<div class="heading">
|
|
Qui ?
|
|
</div>
|
|
<div class="value">
|
|
{query.user.split("@")[0]}
|
|
</div>
|
|
<div class="text-muted">
|
|
(team #{query.id_team})
|
|
</div>
|
|
</div>
|
|
</Col>
|
|
|
|
<Col>
|
|
<div class="level-item">
|
|
<div class="heading">
|
|
État
|
|
</div>
|
|
<BadgeState class="value" state={query.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.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.solved}
|
|
<DateFormat date={query.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.closed}
|
|
<DateFormat date={query.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}
|