forked from ScoDoc/ScoDoc
Merge branch 'main96' of https://scodoc.org/git/iziram/ScoDoc
This commit is contained in:
commit
532fb3e701
@ -1476,3 +1476,14 @@ def is_assiduites_module_forced(
|
||||
except (TypeError, ValueError):
|
||||
retour = sco_preferences.get_preference("forcer_module", dept_id=dept_id)
|
||||
return retour
|
||||
|
||||
|
||||
def get_assiduites_time_config(config_type: str) -> str:
|
||||
from app.models import ScoDocSiteConfig
|
||||
match config_type:
|
||||
case "matin":
|
||||
return ScoDocSiteConfig.get("assi_morning_time", "08:00:00")
|
||||
case "aprem":
|
||||
return ScoDocSiteConfig.get("assi_afternoon_time", "18:00:00")
|
||||
case "pivot":
|
||||
return ScoDocSiteConfig.get("assi_lunch_time", "13:00:00")
|
||||
|
@ -1,15 +1,17 @@
|
||||
:root {
|
||||
--color-present: #6bdb83;
|
||||
--color-absent: #e62a11;
|
||||
--color-absent-clair: #F25D4A;
|
||||
--color-retard: #f0c865;
|
||||
--color-justi: #7059FF;
|
||||
--color-justi-clair: #6885E3;
|
||||
--color-justi-invalide: #a84476;
|
||||
--color-nonwork: #badfff;
|
||||
|
||||
--color-absent-justi: #e65ab7;
|
||||
--color-retard-justi: #ffef7a;
|
||||
|
||||
--color-error: #FF0000;
|
||||
--color-error: #e62a11;
|
||||
--color-warning: #eec660;
|
||||
--color-information: #658ef0;
|
||||
|
||||
@ -21,7 +23,7 @@
|
||||
|
||||
--color-defaut: #FFF;
|
||||
--color-defaut-dark: #444;
|
||||
|
||||
--color-default-text: #1F1F1F;
|
||||
|
||||
|
||||
--motif-justi: repeating-linear-gradient(135deg, transparent, transparent 4px, var(--color-justi) 4px, var(--color-justi) 8px);
|
||||
|
@ -953,6 +953,89 @@ function createAssiduite(etat, etudid) {
|
||||
);
|
||||
return !with_errors;
|
||||
}
|
||||
/**
|
||||
* Création d'une assiduité pour un étudiant
|
||||
* @param {String} etat l'état de l'étudiant
|
||||
* @param {Number | String} etudid l'identifiant de l'étudiant
|
||||
*
|
||||
* TODO : Rendre asynchrone
|
||||
*/
|
||||
function createAssiduiteComplete(assiduite, etudid) {
|
||||
if (!hasModuleImpl(assiduite) && window.forceModule) {
|
||||
const html = `
|
||||
<h3>Aucun module n'a été spécifié</h3>
|
||||
`;
|
||||
const div = document.createElement("div");
|
||||
div.innerHTML = html;
|
||||
openAlertModal("Erreur Module", div);
|
||||
return false;
|
||||
}
|
||||
|
||||
const path = getUrl() + `/api/assiduite/${etudid}/create`;
|
||||
|
||||
let with_errors = false;
|
||||
|
||||
sync_post(
|
||||
path,
|
||||
[assiduite],
|
||||
(data, status) => {
|
||||
//success
|
||||
if (data.success.length > 0) {
|
||||
let obj = data.success["0"].message.assiduite_id;
|
||||
}
|
||||
if (data.errors.length > 0) {
|
||||
console.error(data.errors["0"].message);
|
||||
if (data.errors["0"].message == "Module non renseigné") {
|
||||
const HTML = `
|
||||
<p>Attention, le module doit obligatoirement être renseigné.</p>
|
||||
<p>Cela vient de la configuration du semestre ou plus largement du département.</p>
|
||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||
`;
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.innerHTML = HTML;
|
||||
|
||||
openAlertModal("Sélection du module", content);
|
||||
}
|
||||
if (
|
||||
data.errors["0"].message == "L'étudiant n'est pas inscrit au module"
|
||||
) {
|
||||
const HTML = `
|
||||
<p>Attention, l'étudiant n'est pas inscrit à ce module.</p>
|
||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||
`;
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.innerHTML = HTML;
|
||||
|
||||
openAlertModal("Sélection du module", content);
|
||||
}
|
||||
if (
|
||||
data.errors["0"].message ==
|
||||
"Duplication: la période rentre en conflit avec une plage enregistrée"
|
||||
) {
|
||||
const HTML = `
|
||||
<p>L'assiduité n'a pas pu être enregistrée car une autre assiduité existe sur la période sélectionnée</p>
|
||||
<p>Si c'est une erreur, veuillez voir avec le ou les responsables de votre scodoc.</p>
|
||||
`;
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.innerHTML = HTML;
|
||||
|
||||
openAlertModal("Période conflictuelle", content);
|
||||
}
|
||||
with_errors = true;
|
||||
}
|
||||
},
|
||||
(data, status) => {
|
||||
//error
|
||||
console.error(data, status);
|
||||
errorAlert();
|
||||
with_errors = true;
|
||||
}
|
||||
);
|
||||
return !with_errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppression d'une assiduité
|
||||
|
234
app/templates/assiduites/pages/ajout_assiduites.j2
Normal file
234
app/templates/assiduites/pages/ajout_assiduites.j2
Normal file
@ -0,0 +1,234 @@
|
||||
{% include "assiduites/widgets/toast.j2" %}
|
||||
{% block pageContent %}
|
||||
<div class="pageContent">
|
||||
<h3>Ajouter une assiduité</h3>
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
{% if saisie_eval %}
|
||||
<div id="saisie_eval">
|
||||
<br>
|
||||
<h3>
|
||||
La saisie de l'assiduité a été préconfigurée en fonction de l'évaluation. <br>
|
||||
Une fois la saisie finie, cliquez sur le lien si dessous pour revenir sur la gestion de l'évaluation
|
||||
</h3>
|
||||
<a href="{{redirect_url}}">retourner sur la page de l'évaluation</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<section class="assi-form page">
|
||||
<fieldset>
|
||||
<div class="assi-row">
|
||||
<div class="assi-label">
|
||||
<legend for="assi_date_debut" required>Date de début</legend>
|
||||
<scodoc-datetime name="assi_date_debut" id="assi_date_debut"> </scodoc-datetime>
|
||||
<span>Journée entière</span> <input type="checkbox" name="assi_journee" id="assi_journee">
|
||||
</div>
|
||||
<div class="assi-label" id="date_fin">
|
||||
<legend for="assi_date_fin" required>Date de fin</legend>
|
||||
<scodoc-datetime name="assi_date_fin" id="assi_date_fin"></scodoc-datetime>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assi-row">
|
||||
<div class="assi-label">
|
||||
<legend for="assi_etat" required>Etat de l'assiduité</legend>
|
||||
<select name="assi_etat" id="assi_etat">
|
||||
<option value="absent" selected>Absent</option>
|
||||
<option value="retard">Retard</option>
|
||||
<option value="present">Présent</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="assi-row">
|
||||
<div class="assi-label">
|
||||
<legend for="assi_module" required>Module</legend>
|
||||
{% with moduleid="ajout_assiduite_module_impl",label=false %}
|
||||
{% include "assiduites/widgets/moduleimpl_dynamic_selector.j2" %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assi-row">
|
||||
<div class="assi-label">
|
||||
<legend for="assi_raison">Raison</legend>
|
||||
<textarea name="assi_raison" id="assi_raison" cols="50" rows="10" maxlength="500"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="assi-row">
|
||||
<button onclick="validerFormulaire(this)">Créer l'assiduité</button>
|
||||
<button onclick="effacerFormulaire()">Remettre à zero</button>
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
|
||||
</section>
|
||||
<section class="liste">
|
||||
<a class="icon filter" onclick="filterAssi()"></a>
|
||||
{% include "assiduites/widgets/tableau_assi.j2" %}
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.assi-row {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.assi-form fieldset {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.pageContent {
|
||||
max-width: var(--sco-content-max-width);
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.assi-label {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
[required]::after {
|
||||
content: "*";
|
||||
color: var(--color-error);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
|
||||
|
||||
function validateFields() {
|
||||
const field = document.querySelector('.assi-form')
|
||||
const { deb, fin } = getDates()
|
||||
const date_debut = new Date(deb);
|
||||
const date_fin = new Date(fin);
|
||||
|
||||
if (deb == "" || fin == "" || !date_debut.isValid() || !date_fin.isValid()) {
|
||||
openAlertModal("Erreur détéctée", document.createTextNode("Il faut indiquer une date de début et une date de fin valide."), "", color = "crimson");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (date_fin.isBefore(date_debut)) {
|
||||
openAlertModal("Erreur détéctée", document.createTextNode("La date de fin doit se trouver après la date de début."), "", color = "crimson");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function fieldsToAssiduite() {
|
||||
const field = document.querySelector('.assi-form.page')
|
||||
|
||||
const { deb, fin } = getDates()
|
||||
|
||||
const etat = field.querySelector('#assi_etat').value;
|
||||
const raison = field.querySelector('#assi_raison').value;
|
||||
const module = field.querySelector("#ajout_assiduite_module_impl").value;
|
||||
|
||||
return {
|
||||
date_debut: new Date(deb).toFakeIso(),
|
||||
date_fin: new Date(fin).toFakeIso(),
|
||||
etat: etat,
|
||||
description: raison,
|
||||
moduleimpl_id: module,
|
||||
}
|
||||
}
|
||||
|
||||
function validerFormulaire(btn) {
|
||||
if (!validateFields()) return
|
||||
|
||||
const assiduite = fieldsToAssiduite();
|
||||
let assiduite_id = null;
|
||||
|
||||
createAssiduiteComplete(assiduite, etudid);
|
||||
loadAll();
|
||||
btn.disabled = true;
|
||||
setTimeout(() => {
|
||||
btn.disabled = false;
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
|
||||
function effacerFormulaire() {
|
||||
const field = document.querySelector('.assi-form')
|
||||
|
||||
field.querySelector('#assi_date_debut').value = "";
|
||||
field.querySelector('#assi_date_fin').value = "";
|
||||
field.querySelector('#assi_etat').value = "attente";
|
||||
field.querySelector('#assi_raison').value = "";
|
||||
|
||||
}
|
||||
|
||||
function dayOnly() {
|
||||
const date_deb = document.getElementById("assi_date_debut");
|
||||
const date_fin = document.getElementById("assi_date_fin");
|
||||
|
||||
if (document.getElementById('assi_journee').checked) {
|
||||
date_deb.setAttribute("show", "date")
|
||||
date_fin.setAttribute("show", "date")
|
||||
document.querySelector(`legend[for="assi_date_fin"]`).removeAttribute("required")
|
||||
} else {
|
||||
date_deb.removeAttribute("show")
|
||||
date_fin.removeAttribute("show")
|
||||
document.querySelector(`legend[for="assi_date_fin"]`).setAttribute("required", "")
|
||||
}
|
||||
}
|
||||
|
||||
function getDates() {
|
||||
const date_deb = document.querySelector(".page #assi_date_debut")
|
||||
const date_fin = document.querySelector(".page #assi_date_fin")
|
||||
const journee = document.querySelector('.page #assi_journee').checked
|
||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||
if (journee) {
|
||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
||||
} else {
|
||||
fin = date_fin.valueAsObject.date + fin
|
||||
}
|
||||
|
||||
return {
|
||||
"deb": deb,
|
||||
"fin": fin,
|
||||
}
|
||||
}
|
||||
|
||||
const etudid = {{ sco.etud.id }};
|
||||
|
||||
const assi_limit_annee = "{{ assi_limit_annee }}" == "True" ? true : false;
|
||||
const assi_morning = '{{assi_morning}}';
|
||||
const assi_evening = '{{assi_evening}}';
|
||||
|
||||
{% if saisie_eval %}
|
||||
const saisie_eval = true;
|
||||
const date_deb = "{{date_deb}}";
|
||||
const date_fin = "{{date_fin}}";
|
||||
const moduleimpl = {{ moduleimpl_id }};
|
||||
{% else %}
|
||||
const saisie_eval = false;
|
||||
{% endif %}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
loadAll();
|
||||
document.getElementById('assi_journee').addEventListener('click', () => { dayOnly() });
|
||||
dayOnly()
|
||||
|
||||
if (saisie_eval) {
|
||||
document.getElementById("assi_date_debut").value = Date.removeUTC(date_deb);
|
||||
document.getElementById("assi_date_fin").value = Date.removeUTC(date_fin);
|
||||
} else {
|
||||
const today = (new Date()).format("YYYY-MM-DD");
|
||||
document.getElementById("assi_date_debut").valueAsObject = { date: today, time: assi_morning }
|
||||
document.getElementById("assi_date_fin").valueAsObject = { time: assi_evening }
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("assi_date_debut").addEventListener("blur", (event) => {
|
||||
updateSelect(null, "#ajout_assiduite_module_impl", event.target.valueAsObject.date)
|
||||
})
|
||||
|
||||
updateSelect(saisie_eval ? moduleimpl : "", "#ajout_assiduite_module_impl", document.getElementById("assi_date_debut").valueAsObject.date);
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock pageContent %}
|
@ -3,10 +3,7 @@
|
||||
<div class="pageContent">
|
||||
<h3>Justifier des absences ou retards</h3>
|
||||
{% include "assiduites/widgets/tableau_base.j2" %}
|
||||
<section class="liste">
|
||||
<a class="icon filter" onclick="filterJusti()"></a>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
</section>
|
||||
|
||||
|
||||
<section class="justi-form page">
|
||||
|
||||
@ -60,6 +57,10 @@
|
||||
</fieldset>
|
||||
|
||||
</section>
|
||||
<section class="liste">
|
||||
<a class="icon filter" onclick="filterJusti()"></a>
|
||||
{% include "assiduites/widgets/tableau_justi.j2" %}
|
||||
</section>
|
||||
|
||||
<div class="legende">
|
||||
|
||||
@ -224,12 +225,12 @@
|
||||
if (document.getElementById('justi_journee').checked) {
|
||||
date_deb.setAttribute("show", "date")
|
||||
date_fin.setAttribute("show", "date")
|
||||
document.getElementById("date_fin").classList.add("hidden");
|
||||
document.querySelector(`legend[for="justi_date_fin"]`).removeAttribute("required")
|
||||
|
||||
} else {
|
||||
date_deb.removeAttribute("show")
|
||||
date_fin.removeAttribute("show")
|
||||
document.getElementById("date_fin").classList.remove("hidden");
|
||||
|
||||
document.querySelector(`legend[for="justi_date_fin"]`).setAttribute("required", "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,8 +239,12 @@
|
||||
const date_fin = document.querySelector(".page #justi_date_fin")
|
||||
const journee = document.querySelector('.page #justi_journee').checked
|
||||
const deb = date_deb.valueAsObject.date + "T" + (journee ? assi_morning : date_deb.valueAsObject.time)
|
||||
const fin = (journee ? date_deb.valueAsObject.date : date_fin.valueAsObject.date) + "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||
|
||||
let fin = "T" + (journee ? assi_evening : date_fin.valueAsObject.time)
|
||||
if (journee) {
|
||||
fin = (date_fin.valueAsObject.date || date_deb.valueAsObject.date) + fin
|
||||
} else {
|
||||
fin = date_fin.valueAsObject.date + fin
|
||||
}
|
||||
return {
|
||||
"deb": deb,
|
||||
"fin": fin,
|
||||
|
@ -343,6 +343,9 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
const datePivot = "{{scu.get_assiduites_time_config("pivot")}}".split(":").map((el) => Number(el))
|
||||
|
||||
function getDaysBetweenDates(start, end) {
|
||||
let now = new Date(start);
|
||||
end = new Date(end);
|
||||
@ -476,7 +479,7 @@
|
||||
const matin = [new Date(date), new Date(date)]
|
||||
color = "sans_etat"
|
||||
matin[0].setHours(0, 0, 0, 0)
|
||||
matin[1].setHours(12, 59, 59)
|
||||
matin[1].setHours(...datePivot)
|
||||
|
||||
|
||||
|
||||
@ -515,7 +518,8 @@
|
||||
span_aprem.classList.add("color");
|
||||
const aprem = [new Date(date), new Date(date)]
|
||||
color = "sans_etat"
|
||||
aprem[0].setHours(13, 0, 0, 0)
|
||||
aprem[0].setHours(...datePivot)
|
||||
aprem[0].add(1, "seconds")
|
||||
aprem[1].setHours(23, 59, 59)
|
||||
|
||||
|
||||
|
@ -1,13 +1,24 @@
|
||||
<label for="moduleimpl_select">
|
||||
Module
|
||||
<div>
|
||||
{% if label != false%}
|
||||
<label for="moduleimpl_select">
|
||||
Module
|
||||
</label>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% if moduleid %}
|
||||
<select id="{{moduleid}}" class="dynaSelect">
|
||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||
</select>
|
||||
{% else %}
|
||||
<select id="moduleimpl_select" class="dynaSelect">
|
||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||
</select>
|
||||
{% endif %}
|
||||
|
||||
<div id="saved" style="display: none;">
|
||||
{% include "assiduites/widgets/simplemoduleimpl_select.j2" %}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
|
@ -88,6 +88,7 @@
|
||||
td.textContent = getModuleImpl(assiduite);
|
||||
} else if (k.indexOf('est_just') != -1) {
|
||||
td.textContent = assiduite[k] ? "Oui" : "Non"
|
||||
if (assiduite[k]) row.classList.add("est_just")
|
||||
} else if (k.indexOf('etudid') != -1) {
|
||||
const e = getEtudiant(assiduite.etudid);
|
||||
|
||||
|
@ -456,6 +456,7 @@
|
||||
td {
|
||||
border: 1px solid #dddddd;
|
||||
padding: 8px;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
th {
|
||||
@ -498,17 +499,25 @@
|
||||
|
||||
.l-absent,
|
||||
.l-invalid {
|
||||
background-color: var(--color-absent);
|
||||
background-color: var(--color-absent-clair);
|
||||
}
|
||||
|
||||
.l-valid {
|
||||
background-color: var(--color-primary);
|
||||
background-color: var(--color-justi-clair);
|
||||
}
|
||||
|
||||
.l-retard {
|
||||
background-color: var(--color-retard);
|
||||
}
|
||||
|
||||
.l-absent.est_just {
|
||||
background-color: var(--color-absent-justi);
|
||||
}
|
||||
|
||||
.l-retard.est_just {
|
||||
background-color: var(--color-retard-justi);
|
||||
}
|
||||
|
||||
/* Ajoutez des styles pour le conteneur de pagination et les boutons */
|
||||
.pagination-container {
|
||||
display: flex;
|
||||
|
@ -293,7 +293,8 @@ def signal_assiduites_etud():
|
||||
"js/date_utils.js",
|
||||
"js/etud_info.js",
|
||||
],
|
||||
cssstyles=[
|
||||
cssstyles=CSSSTYLES
|
||||
+ [
|
||||
"css/assiduites.css",
|
||||
],
|
||||
)
|
||||
@ -318,27 +319,43 @@ def signal_assiduites_etud():
|
||||
header,
|
||||
_mini_timeline(),
|
||||
render_template(
|
||||
"assiduites/pages/signal_assiduites_etud.j2",
|
||||
"assiduites/pages/ajout_assiduites.j2",
|
||||
sco=ScoData(etud),
|
||||
date=_dateiso_to_datefr(date),
|
||||
morning=morning,
|
||||
lunch=lunch,
|
||||
timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
||||
afternoon=afternoon,
|
||||
nonworkdays=_non_work_days(),
|
||||
forcer_module=sco_preferences.get_preference(
|
||||
"forcer_module", dept_id=g.scodoc_dept_id
|
||||
),
|
||||
diff=_differee(
|
||||
etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
||||
moduleimpl_select=select,
|
||||
assi_limit_annee=sco_preferences.get_preference(
|
||||
"assi_limit_annee",
|
||||
dept_id=g.scodoc_dept_id,
|
||||
),
|
||||
assi_morning=ScoDocSiteConfig.get("assi_morning_time", "08:00"),
|
||||
assi_evening=ScoDocSiteConfig.get("assi_afternoon_time", "18:00"),
|
||||
|
||||
saisie_eval=saisie_eval,
|
||||
date_deb=date_deb,
|
||||
date_fin=date_fin,
|
||||
redirect_url=redirect_url,
|
||||
moduleimpl_id=moduleimpl_id,
|
||||
),
|
||||
# render_template(
|
||||
# "assiduites/pages/signal_assiduites_etud.j2",
|
||||
# sco=ScoData(etud),
|
||||
# date=_dateiso_to_datefr(date),
|
||||
# morning=morning,
|
||||
# lunch=lunch,
|
||||
# timeline=_timeline(heures=",".join([f"'{s}'" for s in heures])),
|
||||
# afternoon=afternoon,
|
||||
# nonworkdays=_non_work_days(),
|
||||
# forcer_module=sco_preferences.get_preference(
|
||||
# "forcer_module", dept_id=g.scodoc_dept_id
|
||||
# ),
|
||||
# diff=_differee(
|
||||
# etudiants=[sco_etud.get_etud_info(etudid=etud.etudid, filled=True)[0]],
|
||||
# moduleimpl_select=select,
|
||||
# ),
|
||||
# saisie_eval=saisie_eval,
|
||||
# date_deb=date_deb,
|
||||
# date_fin=date_fin,
|
||||
# redirect_url=redirect_url,
|
||||
# moduleimpl_id=moduleimpl_id,
|
||||
# ),
|
||||
).build()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user