qa: Managers can view team and manage theirs todo list
This commit is contained in:
parent
b94beb363b
commit
cd64fc90bf
11
libfic/qa.go
11
libfic/qa.go
@ -242,6 +242,17 @@ func (t *Team) NewQATodo(idExercice int64) (*QATodo, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete the comment in the database.
|
||||||
|
func (t *QATodo) Delete() (int64, error) {
|
||||||
|
if res, err := DBExec("DELETE FROM teams_qa_todo WHERE id_todo = ?", t.Id); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
} else {
|
||||||
|
return nb, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// QAView
|
// QAView
|
||||||
|
|
||||||
func (t *Team) GetQAView() (res []*QATodo, err error) {
|
func (t *Team) GetQAView() (res []*QATodo, err error) {
|
||||||
|
@ -29,4 +29,5 @@ func DeclareRoutes(router *gin.RouterGroup) {
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
declareTodoManagerRoutes(apiManagerRoutes)
|
declareTodoManagerRoutes(apiManagerRoutes)
|
||||||
|
declareTeamsRoutes(apiManagerRoutes)
|
||||||
}
|
}
|
||||||
|
53
qa/api/team.go
Normal file
53
qa/api/team.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func declareTeamsRoutes(router *gin.RouterGroup) {
|
||||||
|
router.GET("/teams", listTeams)
|
||||||
|
|
||||||
|
teamsRoutes := router.Group("/teams/:tid")
|
||||||
|
teamsRoutes.Use(teamHandler)
|
||||||
|
teamsRoutes.GET("", showTeam)
|
||||||
|
|
||||||
|
declareTodoRoutes(teamsRoutes)
|
||||||
|
declareTodoManagerRoutes(teamsRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func teamHandler(c *gin.Context) {
|
||||||
|
var team *fic.Team
|
||||||
|
if tid, err := strconv.ParseInt(string(c.Param("tid")), 10, 64); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad team identifier."})
|
||||||
|
return
|
||||||
|
} else if team, err = fic.GetTeam(tid); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Team not found."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set("team", team)
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func listTeams(c *gin.Context) {
|
||||||
|
teams, err := fic.GetTeams()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Unable to GetTeams: ", err.Error())
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list teams: %s", err.Error())})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, teams)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showTeam(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("team"))
|
||||||
|
}
|
@ -2,6 +2,7 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"srs.epita.fr/fic-server/libfic"
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
@ -18,12 +19,22 @@ func declareTodoRoutes(router *gin.RouterGroup) {
|
|||||||
func declareTodoManagerRoutes(router *gin.RouterGroup) {
|
func declareTodoManagerRoutes(router *gin.RouterGroup) {
|
||||||
router.POST("/qa_my_exercices.json", addQAView)
|
router.POST("/qa_my_exercices.json", addQAView)
|
||||||
router.POST("/qa_work.json", createQATodo)
|
router.POST("/qa_work.json", createQATodo)
|
||||||
|
|
||||||
|
todosRoutes := router.Group("/todo/:wid")
|
||||||
|
todosRoutes.Use(todoHandler)
|
||||||
|
todosRoutes.GET("", showTodo)
|
||||||
|
todosRoutes.DELETE("", deleteQATodo)
|
||||||
}
|
}
|
||||||
|
|
||||||
type exerciceTested map[int64]string
|
type exerciceTested map[int64]string
|
||||||
|
|
||||||
func getExerciceTested(c *gin.Context) {
|
func getExerciceTested(c *gin.Context) {
|
||||||
teamid := c.MustGet("LoggedTeam").(int64)
|
var teamid int64
|
||||||
|
if team, ok := c.Get("team"); ok {
|
||||||
|
teamid = team.(*fic.Team).Id
|
||||||
|
} else {
|
||||||
|
teamid = c.MustGet("LoggedTeam").(int64)
|
||||||
|
}
|
||||||
|
|
||||||
team, err := fic.GetTeam(teamid)
|
team, err := fic.GetTeam(teamid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -55,7 +66,12 @@ func getExerciceTested(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getQAView(c *gin.Context) {
|
func getQAView(c *gin.Context) {
|
||||||
teamid := c.MustGet("LoggedTeam").(int64)
|
var teamid int64
|
||||||
|
if team, ok := c.Get("team"); ok {
|
||||||
|
teamid = team.(*fic.Team).Id
|
||||||
|
} else {
|
||||||
|
teamid = c.MustGet("LoggedTeam").(int64)
|
||||||
|
}
|
||||||
|
|
||||||
team, err := fic.GetTeam(teamid)
|
team, err := fic.GetTeam(teamid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -73,7 +89,12 @@ func getQAView(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getQAWork(c *gin.Context) {
|
func getQAWork(c *gin.Context) {
|
||||||
teamid := c.MustGet("LoggedTeam").(int64)
|
var teamid int64
|
||||||
|
if team, ok := c.Get("team"); ok {
|
||||||
|
teamid = team.(*fic.Team).Id
|
||||||
|
} else {
|
||||||
|
teamid = c.MustGet("LoggedTeam").(int64)
|
||||||
|
}
|
||||||
|
|
||||||
team, err := fic.GetTeam(teamid)
|
team, err := fic.GetTeam(teamid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -91,7 +112,12 @@ func getQAWork(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getQATodo(c *gin.Context) {
|
func getQATodo(c *gin.Context) {
|
||||||
teamid := c.MustGet("LoggedTeam").(int64)
|
var teamid int64
|
||||||
|
if team, ok := c.Get("team"); ok {
|
||||||
|
teamid = team.(*fic.Team).Id
|
||||||
|
} else {
|
||||||
|
teamid = c.MustGet("LoggedTeam").(int64)
|
||||||
|
}
|
||||||
|
|
||||||
team, err := fic.GetTeam(teamid)
|
team, err := fic.GetTeam(teamid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -167,3 +193,45 @@ func addQAView(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, view)
|
c.JSON(http.StatusOK, view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func todoHandler(c *gin.Context) {
|
||||||
|
team := c.MustGet("team").(*fic.Team)
|
||||||
|
|
||||||
|
var wid int64
|
||||||
|
var todos []*fic.QATodo
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if wid, err = strconv.ParseInt(string(c.Param("wid")), 10, 64); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad todo identifier."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if todos, err = team.GetQATodo(); err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Todo not found."})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range todos {
|
||||||
|
if t.Id == wid {
|
||||||
|
c.Set("todo", t)
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Unable to find the requested QA Todo"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func showTodo(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, c.MustGet("todo"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteQATodo(c *gin.Context) {
|
||||||
|
todo := c.MustGet("todo").(*fic.QATodo)
|
||||||
|
|
||||||
|
if _, err := todo.Delete(); err != nil {
|
||||||
|
c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
} else {
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -121,6 +121,8 @@ func declareStaticRoutes(router *gin.RouterGroup, baseURL string) {
|
|||||||
router.GET("/exercices", serveOrReverse("/", baseURL))
|
router.GET("/exercices", serveOrReverse("/", baseURL))
|
||||||
router.GET("/exercices/*_", serveOrReverse("/", baseURL))
|
router.GET("/exercices/*_", serveOrReverse("/", baseURL))
|
||||||
router.GET("/export", serveOrReverse("/", baseURL))
|
router.GET("/export", serveOrReverse("/", baseURL))
|
||||||
|
router.GET("/teams", serveOrReverse("/", baseURL))
|
||||||
|
router.GET("/teams/*_", serveOrReverse("/", baseURL))
|
||||||
router.GET("/themes", serveOrReverse("/", baseURL))
|
router.GET("/themes", serveOrReverse("/", baseURL))
|
||||||
router.GET("/themes/*_", serveOrReverse("/", baseURL))
|
router.GET("/themes/*_", serveOrReverse("/", baseURL))
|
||||||
router.GET("/_app/*_", serveOrReverse("", baseURL))
|
router.GET("/_app/*_", serveOrReverse("", baseURL))
|
||||||
|
@ -9,11 +9,12 @@
|
|||||||
import { getExerciceQA } from '$lib/qa';
|
import { getExerciceQA } from '$lib/qa';
|
||||||
import { exercicesIdx } from '$lib/stores/exercices';
|
import { exercicesIdx } from '$lib/stores/exercices';
|
||||||
import { themesIdx } from '$lib/stores/themes';
|
import { themesIdx } from '$lib/stores/themes';
|
||||||
import { todos } from '$lib/stores/todo';
|
|
||||||
|
|
||||||
export { className as class };
|
export { className as class };
|
||||||
let className = '';
|
let className = '';
|
||||||
|
|
||||||
|
export let team = null;
|
||||||
|
|
||||||
function show(id) {
|
function show(id) {
|
||||||
goto("exercices/" + id)
|
goto("exercices/" + id)
|
||||||
}
|
}
|
||||||
@ -23,7 +24,7 @@
|
|||||||
setInterval(() => my_exercicesP = update_exercices(), 62000);
|
setInterval(() => my_exercicesP = update_exercices(), 62000);
|
||||||
|
|
||||||
async function update_exercices() {
|
async function update_exercices() {
|
||||||
const view = await getQAView();
|
const view = await getQAView(team);
|
||||||
my_exercices = [];
|
my_exercices = [];
|
||||||
|
|
||||||
for (const v of view) {
|
for (const v of view) {
|
||||||
@ -50,7 +51,13 @@
|
|||||||
>
|
>
|
||||||
↻
|
↻
|
||||||
</button>
|
</button>
|
||||||
<h3>Vos étapes</h3>
|
<h3>
|
||||||
|
{#if team}
|
||||||
|
Étapes de l'équipe
|
||||||
|
{:else}
|
||||||
|
Vos étapes
|
||||||
|
{/if}
|
||||||
|
</h3>
|
||||||
{#await my_exercicesP}
|
{#await my_exercicesP}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
|
@ -13,10 +13,15 @@
|
|||||||
export { className as class };
|
export { className as class };
|
||||||
let className = '';
|
let className = '';
|
||||||
|
|
||||||
let exo_doneP = getExerciceTested();
|
export let team = null;
|
||||||
|
|
||||||
|
let teamtodos = todos;
|
||||||
|
$: teamtodos = team ? team.todos : todos;
|
||||||
|
|
||||||
|
let exo_doneP = getExerciceTested(team);
|
||||||
let tododone = { };
|
let tododone = { };
|
||||||
|
|
||||||
getQAWork().then((queries) => {
|
getQAWork(team).then((queries) => {
|
||||||
for (const q of queries) {
|
for (const q of queries) {
|
||||||
tododone[q.id_exercice] = q;
|
tododone[q.id_exercice] = q;
|
||||||
}
|
}
|
||||||
@ -30,12 +35,12 @@
|
|||||||
<div class={className}>
|
<div class={className}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-dark float-end"
|
class="btn btn-dark float-end"
|
||||||
on:click|preventDefault={() => { todos.refresh(); exo_doneP = getExerciceTested(); }}
|
on:click|preventDefault={() => { todos.refresh(); exo_doneP = getExerciceTested(team); }}
|
||||||
>
|
>
|
||||||
↻
|
↻
|
||||||
</button>
|
</button>
|
||||||
<h3>Étapes à tester et valider</h3>
|
<h3>Étapes à tester et valider</h3>
|
||||||
{#await todos.refresh()}
|
{#await teamtodos.refresh()}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<Spinner size="lg" />
|
<Spinner size="lg" />
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +59,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each $todos as todo (todo.id)}
|
{#each $teamtodos as todo (todo.id)}
|
||||||
<tr
|
<tr
|
||||||
style:cursor="pointer"
|
style:cursor="pointer"
|
||||||
class:text-light={!tododone[todo.id_exercice] && !exo_done[todo.id_exercice]}
|
class:text-light={!tododone[todo.id_exercice] && !exo_done[todo.id_exercice]}
|
||||||
|
39
qa/ui/src/lib/stores/teams.js
Normal file
39
qa/ui/src/lib/stores/teams.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { writable, derived } from 'svelte/store';
|
||||||
|
|
||||||
|
import { getTeams } from '$lib/teams'
|
||||||
|
|
||||||
|
function createTeamsStore() {
|
||||||
|
const { subscribe, set, update } = writable([]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
|
||||||
|
set: (v) => {
|
||||||
|
update((m) => Object.assign(m, v));
|
||||||
|
},
|
||||||
|
|
||||||
|
update,
|
||||||
|
|
||||||
|
refresh: async () => {
|
||||||
|
const list = await getTeams();
|
||||||
|
update((m) => list);
|
||||||
|
return list;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const teams = createTeamsStore();
|
||||||
|
|
||||||
|
export const teamsIdx = derived(
|
||||||
|
teams,
|
||||||
|
$teams => {
|
||||||
|
const teams_idx = { };
|
||||||
|
|
||||||
|
for (const e of $teams) {
|
||||||
|
teams_idx[e.id] = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return teams_idx;
|
||||||
|
},
|
||||||
|
);
|
@ -2,7 +2,7 @@ import { writable, derived } from 'svelte/store';
|
|||||||
|
|
||||||
import { getQAView, getQATodo, getQAWork } from '$lib/todo';
|
import { getQAView, getQATodo, getQAWork } from '$lib/todo';
|
||||||
|
|
||||||
function createTodosStore() {
|
export function createTodosStore(team) {
|
||||||
const { subscribe, set, update } = writable([]);
|
const { subscribe, set, update } = writable([]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -15,8 +15,8 @@ function createTodosStore() {
|
|||||||
update,
|
update,
|
||||||
|
|
||||||
refresh: async () => {
|
refresh: async () => {
|
||||||
const list = await getQATodo();
|
const list = await getQATodo(team);
|
||||||
list.push(...await getQAWork());
|
list.push(...await getQAWork(team));
|
||||||
update((m) => list);
|
update((m) => list);
|
||||||
return list;
|
return list;
|
||||||
},
|
},
|
||||||
|
49
qa/ui/src/lib/teams.js
Normal file
49
qa/ui/src/lib/teams.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { createTodosStore } from '$lib/stores/todo.js'
|
||||||
|
|
||||||
|
export const fieldsTeams = ["name", "color", "active", "external_id"];
|
||||||
|
|
||||||
|
export class Team {
|
||||||
|
constructor(res) {
|
||||||
|
if (res) {
|
||||||
|
this.update(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.todos = createTodosStore(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
update({ id, name, color, active, external_id }) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.color = color;
|
||||||
|
this.active = active;
|
||||||
|
this.external_id = external_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
toHexColor() {
|
||||||
|
let num = this.color;
|
||||||
|
num >>>= 0;
|
||||||
|
let b = num & 0xFF,
|
||||||
|
g = (num & 0xFF00) >>> 8,
|
||||||
|
r = (num & 0xFF0000) >>> 16,
|
||||||
|
a = ( (num & 0xFF000000) >>> 24 ) / 255 ;
|
||||||
|
return "#" + r.toString(16) + g.toString(16) + b.toString(16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeams() {
|
||||||
|
const res = await fetch(`api/teams`, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return (await res.json()).map((t) => new Team(t));
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTeam(tid) {
|
||||||
|
const res = await fetch(`api/teams/${tid}`, {headers: {'Accept': 'application/json'}})
|
||||||
|
if (res.status == 200) {
|
||||||
|
return new Team(await res.json());
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
@ -12,19 +12,36 @@ export class QATodo {
|
|||||||
this.id_team = id_team;
|
this.id_team = id_team;
|
||||||
this.id_exercice = id_exercice;
|
this.id_exercice = id_exercice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async delete(team) {
|
||||||
|
const res = await fetch(team?`api/teams/${team.id}/todo/${this.id}`:`api/teams/${this.id_team}/todo/${this.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {'Accept': 'application/json'}
|
||||||
|
});
|
||||||
|
if (res.status < 300) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getQATodo() {
|
export async function getQATodo(team) {
|
||||||
const res = await fetch(`api/qa_work.json`, {headers: {'Accept': 'application/json'}})
|
const res = await fetch(team?`api/teams/${team.id}/qa_work.json`:`api/qa_work.json`, {headers: {'Accept': 'application/json'}})
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return (await res.json()).map((t) => new QATodo(t));
|
const data = await res.json();
|
||||||
|
if (data === null) {
|
||||||
|
return []
|
||||||
|
} else {
|
||||||
|
return data.map((t) => new QATodo(t));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error((await res.json()).errmsg);
|
throw new Error((await res.json()).errmsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getQAWork() {
|
export async function getQAWork(team) {
|
||||||
const res = await fetch(`api/qa_mywork.json`, {headers: {'Accept': 'application/json'}})
|
const res = await fetch(team?`api/teams/${team.id}/qa_mywork.json`:`api/qa_mywork.json`, {headers: {'Accept': 'application/json'}})
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -37,8 +54,8 @@ export async function getQAWork() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getQAView() {
|
export async function getQAView(team) {
|
||||||
const res = await fetch(`api/qa_myexercices.json`, {headers: {'Accept': 'application/json'}})
|
const res = await fetch(team?`api/teams/${team.id}/qa_myexercices.json`:`api/qa_myexercices.json`, {headers: {'Accept': 'application/json'}})
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
if (data) {
|
if (data) {
|
||||||
@ -51,8 +68,8 @@ export async function getQAView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExerciceTested() {
|
export async function getExerciceTested(team) {
|
||||||
const res = await fetch(`api/qa_exercices.json`, {headers: {'Accept': 'application/json'}})
|
const res = await fetch(team?`api/teams/${team.id}/qa_exercices.json`:`api/qa_exercices.json`, {headers: {'Accept': 'application/json'}})
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
return await res.json();
|
return await res.json();
|
||||||
} else {
|
} else {
|
||||||
|
53
qa/ui/src/routes/teams/+page.svelte
Normal file
53
qa/ui/src/routes/teams/+page.svelte
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
|
import { teams } from '$lib/stores/teams';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Table,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
teams.refresh();
|
||||||
|
|
||||||
|
let query = "";
|
||||||
|
const fields = ["name", "color", "active", "external_id"];
|
||||||
|
|
||||||
|
function show(id) {
|
||||||
|
goto("teams/" + id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Container class="mt-2 mb-5">
|
||||||
|
<h2>
|
||||||
|
Équipes
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<input type="search" class="form-control" placeholder="Filtrer" bind:value={query} autofocus>
|
||||||
|
</p>
|
||||||
|
<Table class="table-hover table-bordered table-striped">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
{#each fields as field}
|
||||||
|
<th>
|
||||||
|
{field}
|
||||||
|
</th>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each $teams as team (team.id)}
|
||||||
|
{#if team.name.indexOf(query) >= 0}
|
||||||
|
<tr on:click={() => show(team.id)}>
|
||||||
|
{#each fields as field}
|
||||||
|
<td class:text-end={field == "image"}>
|
||||||
|
{team[field]}
|
||||||
|
</td>
|
||||||
|
{/each}
|
||||||
|
</tr>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Container>
|
197
qa/ui/src/routes/teams/[tid]/+page.svelte
Normal file
197
qa/ui/src/routes/teams/[tid]/+page.svelte
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
<script>
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Col,
|
||||||
|
Container,
|
||||||
|
Row,
|
||||||
|
Spinner,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import MyExercices from '$lib/components/MyExercices.svelte';
|
||||||
|
import MyTodo from '$lib/components/MyTodo.svelte';
|
||||||
|
import { exercices, exercicesByTheme, exercicesIdx } from '$lib/stores/exercices'
|
||||||
|
import { themes, themesIdx } from '$lib/stores/themes'
|
||||||
|
|
||||||
|
import { getTeam } from '$lib/teams';
|
||||||
|
|
||||||
|
exercices.refresh();
|
||||||
|
themes.refresh();
|
||||||
|
|
||||||
|
let teamtodos = null;
|
||||||
|
let teamP = getTeam($page.params.tid);
|
||||||
|
let todosP = null;
|
||||||
|
teamP.then((team) => {
|
||||||
|
teamtodos = team.todos;
|
||||||
|
todosP = team.todos.refresh();
|
||||||
|
});
|
||||||
|
|
||||||
|
let newTodo = 0;
|
||||||
|
async function submitNewTodo(team) {
|
||||||
|
const res = await fetch(`api/qa_work.json`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Accept': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id_team: team.id,
|
||||||
|
id_exercice: newTodo,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if (res.status == 200) {
|
||||||
|
newTodo = 0;
|
||||||
|
todosP = team.todos.refresh();
|
||||||
|
return await res.json();
|
||||||
|
} else {
|
||||||
|
throw new Error((await res.json()).errmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let newThemeTodo = 0;
|
||||||
|
async function submitNewThemeTodo(team) {
|
||||||
|
for(const e of $exercicesByTheme[newThemeTodo]) {
|
||||||
|
await fetch(`api/qa_work.json`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Accept': 'application/json'},
|
||||||
|
body: JSON.stringify({
|
||||||
|
id_team: team.id,
|
||||||
|
id_exercice: e.id,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
newThemeTodo = 0;
|
||||||
|
todosP = team.todos.refresh();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await teamP}
|
||||||
|
<Container class="mt-2 mb-5">
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
{:then team}
|
||||||
|
<Container class="mt-2 mb-5">
|
||||||
|
<h2 class="d-flex align-items-center">
|
||||||
|
<div
|
||||||
|
style={"width: 1em; height: 1em; background-color: " + team.toHexColor()}
|
||||||
|
class="me-2 rounded"
|
||||||
|
/>
|
||||||
|
{team.name}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<h3>Tâches de l'équipe</h3>
|
||||||
|
{#await todosP}
|
||||||
|
<div class="text-center">
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</div>
|
||||||
|
{:then}
|
||||||
|
<table class="table table-stripped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Scénario</th>
|
||||||
|
<th>Défi</th>
|
||||||
|
<th>Act</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{#each $teamtodos as todo (todo.id)}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{#if $exercicesIdx.length == 0 && $themesIdx.length == 0}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{:else}
|
||||||
|
<a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}">
|
||||||
|
{$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name}
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{#if $exercicesIdx.length == 0}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{:else}
|
||||||
|
{$exercicesIdx[todo.id_exercice].title}
|
||||||
|
{/if}
|
||||||
|
</td>
|
||||||
|
<td class="text-end">
|
||||||
|
<Button
|
||||||
|
color="danger"
|
||||||
|
size="sm"
|
||||||
|
on:click={todo.delete(team).then(() => { todosP = team.todos.refresh(); })}
|
||||||
|
>
|
||||||
|
🗑
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{/each}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<form
|
||||||
|
on:submit={() => submitNewTodo(team)}
|
||||||
|
>
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="input-group-text" for="exerciceTodo">Exercice</label>
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="exercice"
|
||||||
|
bind:value={newTodo}
|
||||||
|
>
|
||||||
|
{#each Object.keys($exercicesByTheme) as thid}
|
||||||
|
<optgroup label={$themesIdx[thid].name}>
|
||||||
|
{#each $exercicesByTheme[thid] as exercice (exercice.id)}
|
||||||
|
<option value={exercice.id}>{exercice.title}</option>
|
||||||
|
{/each}
|
||||||
|
</optgroup>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
color="success"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<form
|
||||||
|
on:submit={() => submitNewThemeTodo(team)}
|
||||||
|
>
|
||||||
|
<div class="input-group">
|
||||||
|
<label class="input-group-text" for="themeTodo">Thème</label>
|
||||||
|
<select
|
||||||
|
class="form-select"
|
||||||
|
id="themeTodo"
|
||||||
|
bind:value={newThemeTodo}
|
||||||
|
>
|
||||||
|
{#each Object.keys($exercicesByTheme) as thid}
|
||||||
|
<option value={$themesIdx[thid].id}>{$themesIdx[thid].name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
color="success"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
{/await}
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<MyExercices {team} />
|
||||||
|
<MyTodo {team} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Container>
|
||||||
|
{/await}
|
Loading…
x
Reference in New Issue
Block a user