Compare commits

..

2 Commits

Author SHA1 Message Date
c78665b374 chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-16 00:51:03 +00:00
8733b835b1 chore(deps): lock file maintenance
All checks were successful
continuous-integration/drone/push Build is passing
2024-09-13 11:54:25 +00:00
26 changed files with 2738 additions and 3014 deletions

View File

@ -564,18 +564,6 @@ angular.module("FICApp")
}) })
.controller("AllTeamAssociationsController", function($scope, $http) { .controller("AllTeamAssociationsController", function($scope, $http) {
$scope.newdqa = "";
$scope.addDelegatedQA = function () {
if ($scope.newdqa.length) {
if (!$scope.config.delegated_qa)
$scope.config.delegated_qa = [];
$scope.config.delegated_qa.push($scope.newdqa);
$scope.saveSettings();
$scope.newdqa = "";
}
}
$scope.allAssociations = []; $scope.allAssociations = [];
$http.get("api/teams-associations.json").then(function(response) { $http.get("api/teams-associations.json").then(function(response) {
$scope.allAssociations = response.data; $scope.allAssociations = response.data;
@ -637,16 +625,28 @@ angular.module("FICApp")
$scope.config.disablesubmitbutton = ""; $scope.config.disablesubmitbutton = "";
}; };
$scope.dropDelegatedQA = function (member) { $scope.newdqa = "";
if (!$scope.config.delegated_qa) { $scope.addDelegatedQA = function() {
if ($scope.newdqa.length) {
if (!$scope.config.delegated_qa)
$scope.config.delegated_qa = []; $scope.config.delegated_qa = [];
$scope.config.delegated_qa.push($scope.newdqa);
$scope.saveSettings();
$scope.newdqa = "";
} }
}
$scope.dropDelegatedQA = function(member) {
if ($scope.config.delegated_qa) {
$scope.config.delegated_qa = [];
angular.forEach($scope.config.delegated_qa, function(m, k) { angular.forEach($scope.config.delegated_qa, function(m, k) {
if (member == m) if (member == m)
$scope.config.delegated_qa.splice(k, 1); $scope.config.delegated_qa.splice(k, 1);
}); });
$scope.saveSettings(); $scope.saveSettings();
} }
}
$scope.saveChallengeInfo = function() { $scope.saveChallengeInfo = function() {
this.challenge.duration = $scope.duration; this.challenge.duration = $scope.duration;
@ -2800,11 +2800,9 @@ function presenceCal(scope, location, data) {
.attr("width", cellSize) .attr("width", cellSize)
.attr("height", cellSize) .attr("height", cellSize)
.attr("transform", function(d) { return "translate(" + (d.getHours() * cellSize) + "," + (d.getMinutes() / 15 * cellSize) + ")"; }) .attr("transform", function(d) { return "translate(" + (d.getHours() * cellSize) + "," + (d.getMinutes() / 15 * cellSize) + ")"; })
.attr("class", function (d) { .attr("class", function(d) { if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function(prev, cur){
if (d >= scope.settings.start && d < scope.settings.start + scope.settings.end - scope.settings.start) return color(data.reduce(function (prev, cur) {
cur = new Date(cur).getTime(); cur = new Date(cur).getTime();
dv = d.getTime(); dv = d.getTime();
return prev + ((dv <= cur && cur < dv+15*60000)?1:0); return prev + ((dv <= cur && cur < dv+15*60000)?1:0);
}, 0)); }, 0)); });
});
} }

View File

@ -306,7 +306,7 @@
</div> </div>
</form> </form>
<div class="card my-3"> <form ng-submit="addDelegatedQA()" class="card my-3">
<div class="card-header"> <div class="card-header">
<h3>Managers QA</h3> <h3>Managers QA</h3>
</div> </div>
@ -318,12 +318,10 @@
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button> </button>
</li> </li>
</ul> <li class="row">
<div class="col" ng-controller="AllTeamAssociationsController">
<form class="row" ng-controller="AllTeamAssociationsController" ng-submit="addDelegatedQA()">
<div class="col">
<select class="form-control form-control-sm" ng-model="newdqa"> <select class="form-control form-control-sm" ng-model="newdqa">
<option ng-selected="newdqa == m" ng-repeat="(i,m) in allAssociations" ng-value="m">{{ m }}</option> <option ng-repeat="(i,m) in allAssociations" ng-value="m">{{ m }}</option>
</select> </select>
</div> </div>
<div class="col input-group"> <div class="col input-group">
@ -332,9 +330,10 @@
<button class="btn btn-sm btn-success" ng-disabled="!newdqa.length"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button> <button class="btn btn-sm btn-success" ng-disabled="!newdqa.length"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span></button>
</span> </span>
</div> </div>
</li>
</ul>
</div>
</form> </form>
</div>
</div>
<form ng-submit="saveChallengeInfo()" class="card my-3"> <form ng-submit="saveChallengeInfo()" class="card my-3">
<div class="card-header"> <div class="card-header">

View File

@ -365,7 +365,7 @@
<div class="carousel slide" data-interval="12000" style="padding-bottom: 0px" autocarousel> <div class="carousel slide" data-interval="12000" style="padding-bottom: 0px" autocarousel>
<div class="carousel-inner"> <div class="carousel-inner">
<div class="carousel-item" ng-repeat="theme in themes" ng-class="{active: $first}"> <div class="carousel-item" ng-repeat="theme in themes" ng-class="{active: $first}">
<div class="carousel-caption text-indent" ng-if="theme.urlid !== '_'"> <div class="carousel-caption text-indent">
<div class="card-img-top theme-card" style="background-image: url('{{ theme.image.substr(0, theme.image.length-3) }}thumb.jpg')"></div> <div class="card-img-top theme-card" style="background-image: url('{{ theme.image.substr(0, theme.image.length-3) }}thumb.jpg')"></div>
<h3 class="text-left" ng-bind="theme.name"></h3> <h3 class="text-left" ng-bind="theme.name"></h3>
<p class="text-justify" style="font-size: 111%" ng-bind-html="theme.headline"></p> <p class="text-justify" style="font-size: 111%" ng-bind-html="theme.headline"></p>
@ -377,8 +377,7 @@
</div> </div>
<div class="card niceborder bg-dark" ng-if="s.type == 'exercice' && !s.params.hide"> <div class="card niceborder bg-dark" ng-if="s.type == 'exercice' && !s.params.hide">
<div class="card-img-top theme-card" style="background-image: url('{{exercices[s.params.exercice].image}}')" ng-if="exercices[s.params.exercice] && exercices[s.params.exercice].image"></div> <div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[s.params.exercice].theme_id].image}}')"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[s.params.exercice].theme_id].image}}')" ng-if="!exercices[s.params.exercice] || !exercices[s.params.exercice].image"></div>
<div class="card-body text-light"> <div class="card-body text-light">
<h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">Défi <em>{{ exercices[s.params.exercice].title }}</em> du thème <em>{{ themes[my.exercices[s.params.exercice].theme_id].name }}</em></h3> <h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">Défi <em>{{ exercices[s.params.exercice].title }}</em> du thème <em>{{ themes[my.exercices[s.params.exercice].theme_id].name }}</em></h3>
<p ng-bind-html="my.exercices[s.params.exercice].overview"></p> <p ng-bind-html="my.exercices[s.params.exercice].overview"></p>
@ -398,8 +397,7 @@
Challenges à la une Challenges à la une
</span> </span>
</div> </div>
<div class="card-img-top theme-card" style="background-image: url('{{exercices[lastExercice].image}}')" ng-if="exercices[lastExercice] && exercices[lastExercice].image"></div> <div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[lastExercice].theme_id].image}}')"></div>
<div class="card-img-top theme-card" style="background-image: url('{{themes[my.exercices[lastExercice].theme_id].image}}')" ng-if="!exercices[s.params.exercice] || !exercices[s.params.exercice].image"></div>
<div class="card-body text-light"> <div class="card-body text-light">
<h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"><em>{{ exercices[lastExercice].title }}</em> du thème <em>{{ themes[my.exercices[lastExercice].theme_id].name }}</em></h3> <h3 style="font-size: 1.0rem; text-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"><em>{{ exercices[lastExercice].title }}</em> du thème <em>{{ themes[my.exercices[lastExercice].theme_id].name }}</em></h3>
<p ng-bind-html="my.exercices[lastExercice].overview"></p> <p ng-bind-html="my.exercices[lastExercice].overview"></p>

View File

@ -1,6 +1,5 @@
<script> <script>
import { base } from '$app/paths'; import { base } from '$app/paths';
import { page } from '$app/stores';
import { import {
Badge, Badge,
@ -63,7 +62,7 @@
<NavbarToggler on:click={() => (isOpen = !isOpen)} /> <NavbarToggler on:click={() => (isOpen = !isOpen)} />
<Collapse {isOpen} navbar expand="md" on:update={handleUpdate}> <Collapse {isOpen} navbar expand="md" on:update={handleUpdate}>
<Nav navbar> <Nav navbar>
<NavItem active={$page.route && $page.route.id === "/"}> <NavItem>
<NavLink href="."> <NavLink href=".">
<Icon name="house" /> <Icon name="house" />
Accueil Accueil
@ -72,7 +71,7 @@
<NavThemes /> <NavThemes />
<NavTags /> <NavTags />
{#if $settings && $settings.end - $settings.start > 0 && $teams && Object.keys($teams).length} {#if $settings && $settings.end - $settings.start > 0 && $teams && Object.keys($teams).length}
<NavItem active={$page.route && $page.route.id === "/rank"}> <NavItem>
<NavLink href="rank"> <NavLink href="rank">
<Icon name="sort-down" /> <Icon name="sort-down" />
Classement Classement
@ -80,7 +79,7 @@
</NavItem> </NavItem>
{/if} {/if}
<HeaderIssues /> <HeaderIssues />
<NavItem active={$page.route && $page.route.id === "/rules"}> <NavItem>
<NavLink href="rules"> <NavLink href="rules">
<Icon name="signpost-split" /> <Icon name="signpost-split" />
Aide Aide

View File

@ -1,6 +1,4 @@
<script> <script>
import { page } from '$app/stores';
import { import {
Badge, Badge,
Icon, Icon,
@ -26,7 +24,7 @@
</script> </script>
{#if $issues.length} {#if $issues.length}
<NavItem active={$page.route && $page.route.id === "/issues"}> <NavItem>
<NavLink href="issues"> <NavLink href="issues">
<Icon name="bug" /> <Icon name="bug" />
Problèmes Problèmes

View File

@ -1,6 +1,4 @@
<script> <script>
import { page } from '$app/stores';
import { import {
Badge, Badge,
Dropdown, Dropdown,
@ -16,7 +14,7 @@
let filter = ""; let filter = "";
</script> </script>
<Dropdown nav inNavbar active={$page.params.tag}> <Dropdown nav inNavbar>
<DropdownToggle nav caret> <DropdownToggle nav caret>
<Icon name="tags" /> <Icon name="tags" />
Tags Tags

View File

@ -14,7 +14,6 @@
import { myThemes, themes } from '$lib/stores/mythemes.js'; import { myThemes, themes } from '$lib/stores/mythemes.js';
</script> </script>
{#if $themes.length > 0 && ($themes[0].id != 0 || $themes.length > 1)}
<Dropdown nav inNavbar active={$current_theme && $current_theme.id != 0}> <Dropdown nav inNavbar active={$current_theme && $current_theme.id != 0}>
<DropdownToggle nav caret> <DropdownToggle nav caret>
<Icon name="tv" /> <Icon name="tv" />
@ -55,7 +54,6 @@
</div> </div>
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
{/if}
{#if $themesStore && $themesStore["0"] && $themesStore["0"].exercices} {#if $themesStore && $themesStore["0"] && $themesStore["0"].exercices}
<Dropdown nav inNavbar active={$current_theme && $current_theme && $current_theme.id == 0}> <Dropdown nav inNavbar active={$current_theme && $current_theme && $current_theme.id == 0}>
<DropdownToggle nav caret> <DropdownToggle nav caret>

View File

@ -73,9 +73,7 @@
</Column> </Column>
</Table> </Table>
{:else} {:else}
<CardBody>
Vous n'avez fait aucune action vous faisant gagner ou perdre des points. Vous n'avez fait aucune action vous faisant gagner ou perdre des points.
</CardBody>
{/if} {/if}
<button class="btn btn-primary" on:click={refresh_scores}> <button class="btn btn-primary" on:click={refresh_scores}>
<Icon name="arrow-clockwise" /> <Icon name="arrow-clockwise" />

View File

@ -45,7 +45,6 @@
{#if exercices.length} {#if exercices.length}
<Row cols="3"> <Row cols="3">
{#key exercices}
{#each exercices as {theme, exercice, index} (index)} {#each exercices as {theme, exercice, index} (index)}
<Col class="mb-3"> <Col class="mb-3">
<CardTheme <CardTheme
@ -55,7 +54,6 @@
/> />
</Col> </Col>
{/each} {/each}
{/key}
</Row> </Row>
{:else} {:else}
<p class="lead"> <p class="lead">

View File

@ -56,26 +56,6 @@ func GetThemes() ([]*Theme, error) {
} }
} }
// GetThemesExtended returns a list of Themes including standalone exercices.
func GetThemesExtended() ([]*Theme, error) {
if themes, err := GetThemes(); err != nil {
return nil, err
} else {
// Append standalone exercices fake-themes
stdthm := &Theme{
Name: "Défis indépendants",
URLId: "_",
Path: "exercices",
}
if exercices, err := stdthm.GetExercices(); err == nil && len(exercices) > 0 {
themes = append(themes, stdthm)
}
return themes, nil
}
}
// GetTheme retrieves a Theme from its identifier. // GetTheme retrieves a Theme from its identifier.
func GetTheme(id int64) (*Theme, error) { func GetTheme(id int64) (*Theme, error) {
t := &Theme{} t := &Theme{}

View File

@ -38,10 +38,6 @@ func init() {
oidcRedirectURL = v oidcRedirectURL = v
} }
if v, ok := os.LookupEnv("FIC_GITLAB_BASEURL"); ok {
gitlabBaseURL = v
}
flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection") flag.StringVar(&oidcRedirectURL, "oidc-redirect", oidcRedirectURL, "Base URL for the redirect after connection")
flag.StringVar(&gitlabBaseURL, "gitlab-baseurl", gitlabBaseURL, "Base URL of the Gitlab instance") flag.StringVar(&gitlabBaseURL, "gitlab-baseurl", gitlabBaseURL, "Base URL of the Gitlab instance")
flag.StringVar(&gitlabClientID, "gitlab-clientid", os.Getenv("FIC_GITLAB_CLIENT_ID"), "ClientID for GitLab's OIDC") flag.StringVar(&gitlabClientID, "gitlab-clientid", os.Getenv("FIC_GITLAB_CLIENT_ID"), "ClientID for GitLab's OIDC")

View File

@ -118,7 +118,7 @@ func getExerciceQA(c *gin.Context) {
func exportQA(c *gin.Context) { func exportQA(c *gin.Context) {
var report string var report string
themes, err := fic.GetThemesExtended() themes, err := fic.GetThemes()
if err != nil { if err != nil {
log.Println("Unable to GetThemes: ", err.Error()) log.Println("Unable to GetThemes: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())})
@ -191,7 +191,7 @@ type ExportReport struct {
func exportQAJSON(c *gin.Context) { func exportQAJSON(c *gin.Context) {
report := map[string]ExportTheme{} report := map[string]ExportTheme{}
themes, err := fic.GetThemesExtended() themes, err := fic.GetThemes()
if err != nil { if err != nil {
log.Println("Unable to GetThemes: ", err.Error()) log.Println("Unable to GetThemes: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())}) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list themes: %s", err.Error())})

View File

@ -1,11 +1,8 @@
package api package api
import ( import (
"fmt"
"log"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"srs.epita.fr/fic-server/libfic" "srs.epita.fr/fic-server/libfic"
@ -20,7 +17,6 @@ func declareTodoRoutes(router *gin.RouterGroup) {
} }
func declareTodoManagerRoutes(router *gin.RouterGroup) { func declareTodoManagerRoutes(router *gin.RouterGroup) {
router.POST("/qa_assign_work", assignWork)
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)
@ -239,80 +235,3 @@ func deleteQATodo(c *gin.Context) {
c.Status(http.StatusOK) c.Status(http.StatusOK)
} }
} }
type QAAssignWork struct {
Turns int `json:"turns"`
Start int `json:"start"`
TeamPrefix string `json:"team_prefix"`
TeamAssistants string `json:"team_assistants"`
}
func assignWork(c *gin.Context) {
var uaw QAAssignWork
if err := c.ShouldBindJSON(&uaw); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if uaw.Turns == 0 {
uaw.Turns = 1
}
if uaw.TeamPrefix == "" {
uaw.TeamPrefix = "FIC Groupe "
}
if uaw.TeamAssistants == "" {
uaw.TeamAssistants = "assistants"
}
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
}
// Remove assistant team
for tid := len(teams) - 1; tid >= 0; tid-- {
team := teams[tid]
if strings.Contains(strings.ToLower(team.Name), uaw.TeamAssistants) {
teams = append(teams[:tid], teams[tid+1:]...)
}
}
exercices, err := fic.GetExercices()
if err != nil {
log.Println("Unable to GetExercices: ", err.Error())
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to list exercices: %s", err.Error())})
return
}
// Struct to store reported team (due to owned exercice)
var teamIdStack []int64
for i := 0; i < uaw.Turns; i++ {
for eid, ex := range exercices {
team := teams[(uaw.Start+eid+uaw.Turns*i)%len(teams)]
if len(teamIdStack) > 0 {
teamIdStack = append(teamIdStack, team.Id)
team, _ = fic.GetTeam(teamIdStack[0])
teamIdStack = append([]int64{}, teamIdStack[1:]...)
}
j := 0
// Find a team not responsible for this exercice
for (strings.Contains(ex.Path, "grp") && strings.Contains(ex.Path, "-grp"+strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) || (!strings.Contains(ex.Path, "grp") && strings.HasPrefix(ex.Path, strings.TrimPrefix(team.Name, uaw.TeamPrefix)+"-")) {
j += 1
teamIdStack = append(teamIdStack, team.Id)
team = teams[(uaw.Start+eid+uaw.Turns*i+j)%len(teams)]
}
_, err := team.NewQATodo(ex.Id)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
}
}
c.JSON(http.StatusOK, "true")
}

View File

@ -15,11 +15,6 @@ import (
func reloadSettings(config *settings.Settings) { func reloadSettings(config *settings.Settings) {
api.ManagerUsers = config.DelegatedQA api.ManagerUsers = config.DelegatedQA
fic.UnlockedChallengeDepth = config.UnlockedChallengeDepth
fic.UnlockedChallengeUpTo = config.UnlockedChallengeUpTo
fic.UnlockedStandaloneExercices = config.UnlockedStandaloneExercices
fic.UnlockedStandaloneExercicesByThemeStepValidation = config.UnlockedStandaloneExercicesByThemeStepValidation
fic.UnlockedStandaloneExercicesByStandaloneExerciceValidation = config.UnlockedStandaloneExercicesByStandaloneExerciceValidation
} }
func main() { func main() {

View File

@ -23,7 +23,6 @@
} from '@sveltestrap/sveltestrap'; } from '@sveltestrap/sveltestrap';
import { auth, gitlab, version } from '$lib/stores/auth'; import { auth, gitlab, version } from '$lib/stores/auth';
import { themes } from '$lib/stores/themes';
export let activemenu = ""; export let activemenu = "";
$: { $: {
@ -51,7 +50,6 @@
<span class="d-none d-md-inline">Accueil</span> <span class="d-none d-md-inline">Accueil</span>
</NavLink> </NavLink>
</NavItem> </NavItem>
{#if $themes.length}
<NavItem> <NavItem>
<NavLink <NavLink
href="themes" href="themes"
@ -61,7 +59,6 @@
<span class="d-none d-md-inline">Scénarios</span> <span class="d-none d-md-inline">Scénarios</span>
</NavLink> </NavLink>
</NavItem> </NavItem>
{/if}
<NavItem> <NavItem>
<NavLink <NavLink
href="exercices" href="exercices"
@ -81,6 +78,15 @@
<span class="d-none d-md-inline">Équipes</span> <span class="d-none d-md-inline">Équipes</span>
</NavLink> </NavLink>
</NavItem> </NavItem>
<NavItem>
<NavLink
href="repositories"
active={activemenu === 'repositories'}
>
<Icon name="archive" />
<span class="d-none d-md-inline">Dépôts</span>
</NavLink>
</NavItem>
{/if} {/if}
</Nav> </Nav>
<Nav class="ms-auto text-light" navbar> <Nav class="ms-auto text-light" navbar>

View File

@ -83,8 +83,6 @@
<td> <td>
{#if $exercicesIdx.length == 0 && $themesIdx.length == 0} {#if $exercicesIdx.length == 0 && $themesIdx.length == 0}
<Spinner size="sm" /> <Spinner size="sm" />
{:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]}
Défis indépendants
{:else} {:else}
<a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}"> <a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}">
{$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name} {$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name}

View File

@ -29,7 +29,6 @@
style="overflow-y: auto" style="overflow-y: auto"
> >
{#await themesP then themes} {#await themesP then themes}
{#if Object.keys(themes).length > 1}
<Row <Row
style={'min-width:'+15*Object.keys(themes).length + 'vw'} style={'min-width:'+15*Object.keys(themes).length + 'vw'}
> >
@ -70,48 +69,5 @@
</Col> </Col>
{/each} {/each}
</Row> </Row>
{:else}
{#each Object.keys(themes) as tname}
{#if themes[tname].exercices}
{#each themes[tname].exercices as exercice}
<Row
style={'min-width:'+15*Object.keys(themes).length + 'vw'}
>
<Col style="border-right: 1px solid lightgray">
<h3
class="text-center py-3 mb-3"
style="border-bottom: 2px solid black"
on:click={() => goto(`exercices/${exercice.exercice.id}`)}
>
{exercice.exercice.title}
</h3>
{#if exercice.reports}
{#each exercice.reports as report}
<Card
class="mb-3"
color={state2Color(report.report.state)}
style="cursor: pointer"
on:click={() => goto(`exercices/${exercice.exercice.id}/${report.report.id}`)}
>
<CardBody class="p-2">
{report.report.subject}
{#if report.comments}
<Badge
class="float-end"
>
{report.comments.length}
</Badge>
{/if}
</CardBody>
</Card>
{/each}
<hr />
{/if}
</Col>
</Row>
{/each}
{/if}
{/each}
{/if}
{/await} {/await}
</div> </div>

View File

@ -44,7 +44,6 @@ export const exercicesByTheme = derived(
const exercices_idx = { }; const exercices_idx = { };
for (const e of $exercices) { for (const e of $exercices) {
if (!e.id_theme) e.id_theme = 0;
if (!exercices_idx[e.id_theme]) { if (!exercices_idx[e.id_theme]) {
exercices_idx[e.id_theme] = [] exercices_idx[e.id_theme] = []
} }

View File

@ -16,7 +16,6 @@ export function createTodosStore(team) {
refresh: async () => { refresh: async () => {
const list = await getQATodo(team); const list = await getQATodo(team);
list.map((e) => e.id += 10000000);
list.push(...await getQAWork(team)); list.push(...await getQAWork(team));
update((m) => list); update((m) => list);
return list; return list;

View File

@ -22,14 +22,11 @@ export class Team {
toHexColor() { toHexColor() {
let num = this.color; let num = this.color;
num >>>= 0; num >>>= 0;
let b = (num & 0xFF).toString(16), let b = num & 0xFF,
g = ((num & 0xFF00) >>> 8).toString(16), g = (num & 0xFF00) >>> 8,
r = ((num & 0xFF0000) >>> 16).toString(16), r = (num & 0xFF0000) >>> 16,
a = ( (num & 0xFF000000) >>> 24 ) / 255 ; a = ( (num & 0xFF000000) >>> 24 ) / 255 ;
if (r.length <= 1) r = "0" + r; return "#" + r.toString(16) + g.toString(16) + b.toString(16);
if (g.length <= 1) g = "0" + g;
if (b.length <= 1) b = "0" + b;
return "#" + r + g + b;
} }
} }

View File

@ -23,12 +23,7 @@ export class Theme {
export async function getThemes() { export async function getThemes() {
const res = await fetch(`api/themes`, {headers: {'Accept': 'application/json'}}) const res = await fetch(`api/themes`, {headers: {'Accept': 'application/json'}})
if (res.status == 200) { if (res.status == 200) {
const data = await res.json(); return (await res.json()).map((t) => new Theme(t));
if (data) {
return data.map((t) => new Theme(t));
} else {
return [];
}
} else { } else {
throw new Error((await res.json()).errmsg); throw new Error((await res.json()).errmsg);
} }

View File

@ -43,7 +43,7 @@
<tbody> <tbody>
{#each exercices as exercice (exercice.id)} {#each exercices as exercice (exercice.id)}
{#if exercice.title.indexOf(query) >= 0} {#if exercice.title.indexOf(query) >= 0}
<tr on:click={() => show(exercice.id)} style="cursor: pointer"> <tr on:click={() => show(exercice.id)}>
{#each fieldsExercices as field} {#each fieldsExercices as field}
<td> <td>
{@html exercice[field]} {@html exercice[field]}

View File

@ -4,12 +4,7 @@
import { teams } from '$lib/stores/teams'; import { teams } from '$lib/stores/teams';
import { import {
Button,
Container, Container,
FormGroup,
Input,
Label,
Spinner,
Table, Table,
} from '@sveltestrap/sveltestrap'; } from '@sveltestrap/sveltestrap';
@ -21,44 +16,6 @@
function show(id) { function show(id) {
goto("teams/" + id) goto("teams/" + id)
} }
let start = 0;
let turns = 3;
let team_prefix = "";
let team_assistants = "";
let assignInProgress = false;
async function assignExercices() {
assignInProgress = true;
const res = await fetch(`api/qa_assign_work`, {
method: 'POST',
headers: {'Accept': 'application/json'},
body: JSON.stringify({
start,
turns,
team_prefix,
team_assistants,
}),
})
if (res.status == 200) {
teams.refresh();
assignInProgress = false;
} else {
assignInProgress = false;
throw new Error((await res.json()).errmsg);
}
}
async function deleteAssignation() {
const res = await fetch(`api/qa_assign_work`, {
method: 'DELETE',
});
if (res.status == 200) {
teams.refresh();
} else {
throw new Error((await res.json()).errmsg);
}
}
</script> </script>
<Container class="mt-2 mb-5"> <Container class="mt-2 mb-5">
@ -82,19 +39,10 @@
<tbody> <tbody>
{#each $teams as team (team.id)} {#each $teams as team (team.id)}
{#if team.name.indexOf(query) >= 0} {#if team.name.indexOf(query) >= 0}
<tr on:click={() => show(team.id)} style="cursor: pointer"> <tr on:click={() => show(team.id)}>
{#each fields as field} {#each fields as field}
<td class:text-end={field == "image"}> <td class:text-end={field == "image"}>
{#if field == "color"}
<div
class="badge"
style={"background-color: " + team.toHexColor()}
>
{team.toHexColor()}
</div>
{:else}
{team[field]} {team[field]}
{/if}
</td> </td>
{/each} {/each}
</tr> </tr>
@ -102,48 +50,4 @@
{/each} {/each}
</tbody> </tbody>
</Table> </Table>
<hr>
<h2>
Assigner des exercices aux équipes
</h2>
<form on:submit|preventDefault={assignExercices}>
<FormGroup>
<Label for="ae-start">Compteur de départ</Label>
<Input type="number" id="ae-start" bind:value={start} />
<p class="form-text">
Incrémenter de 1 pour chaque nouveau challenge blanc, cela décale l'attribution des exercices.
</p>
</FormGroup>
<FormGroup>
<Label for="ae-turns">Nombre d'itérations</Label>
<Input type="number" id="ae-turns" bind:value={turns} />
</FormGroup>
<FormGroup>
<Label for="ae-prefix">Préfixe des noms d'équipes</Label>
<Input id="ae-prefix" bind:value={team_prefix} placeholder="FIC Groupe" />
</FormGroup>
<FormGroup>
<Label for="ae-assistants">Nom de l'équipe assistants</Label>
<Input id="ae-assistants" bind:value={team_assistants} placeholder="Assistants" />
</FormGroup>
<Button
type="submit"
disabled={assignInProgress}
color="primary"
>
{#if assignInProgress}
<Spinner size="sm" />
{/if}
Assigner des exercices
</Button>
<Button
type="button"
color="danger"
on:click={deleteAssignation}
>
Supprimer toute assignation
</Button>
</form>
</Container> </Container>

View File

@ -102,8 +102,6 @@
<td> <td>
{#if $exercicesIdx.length == 0 && $themesIdx.length == 0} {#if $exercicesIdx.length == 0 && $themesIdx.length == 0}
<Spinner size="sm" /> <Spinner size="sm" />
{:else if !$themesIdx[$exercicesIdx[todo.id_exercice].id_theme]}
Défis indépendants
{:else} {:else}
<a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}"> <a href="themes/{$exercicesIdx[todo.id_exercice].id_theme}">
{$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name} {$themesIdx[$exercicesIdx[todo.id_exercice].id_theme].name}
@ -143,7 +141,7 @@
bind:value={newTodo} bind:value={newTodo}
> >
{#each Object.keys($exercicesByTheme) as thid} {#each Object.keys($exercicesByTheme) as thid}
<optgroup label={(thid != "0" ? $themesIdx[thid].name : "Exercices indépendants")}> <optgroup label={$themesIdx[thid].name}>
{#each $exercicesByTheme[thid] as exercice (exercice.id)} {#each $exercicesByTheme[thid] as exercice (exercice.id)}
<option value={exercice.id}>{exercice.title}</option> <option value={exercice.id}>{exercice.title}</option>
{/each} {/each}
@ -173,9 +171,7 @@
bind:value={newThemeTodo} bind:value={newThemeTodo}
> >
{#each Object.keys($exercicesByTheme) as thid} {#each Object.keys($exercicesByTheme) as thid}
{#if thid != "0"}
<option value={$themesIdx[thid].id}>{$themesIdx[thid].name}</option> <option value={$themesIdx[thid].id}>{$themesIdx[thid].name}</option>
{/if}
{/each} {/each}
</select> </select>
<Button <Button

View File

@ -60,7 +60,7 @@
<tbody> <tbody>
{#each $themes as theme (theme.id)} {#each $themes as theme (theme.id)}
{#if theme.name.indexOf(query) >= 0 || theme.authors.indexOf(query) >= 0 || theme.intro.indexOf(query) >= 0} {#if theme.name.indexOf(query) >= 0 || theme.authors.indexOf(query) >= 0 || theme.intro.indexOf(query) >= 0}
<tr on:click={() => show(theme.id)} style="cursor: pointer"> <tr on:click={() => show(theme.id)}>
{#each fields as field} {#each fields as field}
<td class:text-end={field == "image"}> <td class:text-end={field == "image"}>
{#if field == "image"} {#if field == "image"}

View File

@ -63,7 +63,7 @@
<tbody> <tbody>
{#each exercices as exercice (exercice.id)} {#each exercices as exercice (exercice.id)}
{#if exercice.title.indexOf(query) >= 0} {#if exercice.title.indexOf(query) >= 0}
<tr on:click={() => show(exercice.id)} style="cursor: pointer"> <tr on:click={() => show(exercice.id)}>
{#each fieldsExercices as field} {#each fieldsExercices as field}
<td> <td>
{@html exercice[field]} {@html exercice[field]}