Skip to content
Snippets Groups Projects
Project_detail.vue 31.90 KiB
<template>
  <div v-frag>
    <div v-frag v-if="permissions && permissions.can_view_project && project">
      <div id="message" class="fullwidth">
        <div v-if="tempMessage" class="ui positive message">
          <!-- <i class="close icon"></i> -->
          <!-- <div class="header">You are eligible for a reward</div> -->

          <p><i class="check icon"></i> {{ tempMessage }}</p>
        </div>
      </div>
      <div id="message_info" class="fullwidth">
        <div
          v-if="infoMessage"
          class="ui info message"
          style="text-align: left"
        >
          <div class="header">
            <i class="info circle icon"></i> Informations
          </div>
          <ul class="list">
            {{
              infoMessage
            }}
          </ul>
        </div>
      </div>

      <div class="row">
        <div class="four wide middle aligned column">
          <img
            class="ui small spaced image"
            :src="
              project.thumbnail.includes('default')
                ? require('@/assets/img/default.png')
                : DJANGO_BASE_URL + project.thumbnail + refreshId()
            "
          />
          <div class="ui hidden divider"></div>
          <div class="ui basic teal label" data-tooltip="Membres">
            <i class="user icon"></i>{{ project.nb_contributors }}
          </div>
          <div class="ui basic teal label" data-tooltip="Signalements publiés">
            <i class="map marker icon"></i>{{ project.nb_published_features }}
          </div>
          <div class="ui basic teal label" data-tooltip="Commentaires">
            <i class="comment icon"></i
            >{{ project.nb_published_features_comments }}
          </div>
        </div>
        <div class="ten wide column">
          <h1 class="ui header">
            <div class="content">
              {{ project.title }}
              <div v-if="arraysOffline.length > 0">
                {{ arraysOffline.length }} modification<span v-if="arraysOffline.length>1">s</span> en attente
                <button
                  :disabled="isOffline()"
                  @click="sendOfflineFeatures()"
                  class="ui fluid teal icon button"
                >
                  <i class="upload icon"></i> Envoyer au serveur
                </button>
              </div>
              <div class="ui icon right floated compact buttons">
                <a
                  v-if="
                    user &&
                    permissions &&
                    permissions.can_view_project &&
                    isOffline() !== true
                  "
                  id="subscribe-button"
                  class="ui button button-hover-green"
                  data-tooltip="S'abonner au projet"
                  data-position="top center"
                  data-variation="mini"
                  @click="isModalOpen = true"
                >
                  <i class="inverted grey envelope icon"></i>
                </a>
                <router-link
                  v-if="
                    permissions &&
                    permissions.can_update_project &&
                    isOffline() !== true
                  "
                  :to="{ name: 'project_edit', params: { slug: project.slug } }"
                  class="ui button button-hover-orange"
                  data-tooltip="Modifier le projet"
                  data-position="top center"
                  data-variation="mini"
                >
                  <i class="inverted grey pencil alternate icon"></i>
                </router-link>
              </div>
              <div class="ui hidden divider"></div>
              <div class="sub header">
                {{ project.description }}
              </div>
            </div>
          </h1>
        </div>
      </div>

      <div class="row">
        <div class="seven wide column">
          <h3 class="ui header">Types de signalements</h3>
          <div class="ui middle aligned divided list">
            <div
              :class="{ active : projectInfoLoading }"
              class="ui inverted dimmer"
            >
              <div class="ui text loader">
                Récupération des types de signalements en cours...
              </div>
            </div>
            <div
              :class="{ active: featureTypeImporting }"
              class="ui inverted dimmer"
            >
              <div class="ui text loader">
                Traitement du fichier en cours ...
              </div>
            </div>
            <div
              v-for="(type, index) in feature_types"
              :key="type.title + '-' + index"
              class="item"
            >
              <div class="feature-type-container">
                <router-link
                  :to="{
                    name: 'details-type-signalement',
                    params: { feature_type_slug: type.slug },
                  }"
                  class="feature-type-title"
                >
                  <img
                    v-if="type.geom_type === 'point'"
                    class="list-image-type"
                    src="@/assets/img/marker.png"
                  />
                  <img
                    v-if="type.geom_type === 'linestring'"
                    class="list-image-type"
                    src="@/assets/img/line.png"
                  />
                  <img
                    v-if="type.geom_type === 'polygon'"
                    class="list-image-type"
                    src="@/assets/img/polygon.png"
                  />
                  {{ type.title }}
                </router-link>
                <!-- {% if project and feature_types and
                permissions|lookup:'can_create_feature' %} -->
                <!-- // ? should we get type.is_editable ? -->
                <!-- v-if="
                    project &&
                    permissions.can_create_feature &&
                    type.is_editable
                  " -->
                <div class="middle aligned content">
                  <router-link
                    v-if="
                      project && permissions && permissions.can_create_feature
                    "
                    :to="{
                      name: 'ajouter-signalement',
                      params: { slug_type_signal: type.slug },
                    }"
                    class="
                      ui
                      compact
                      small
                      icon
                      right
                      floated
                      button button-hover-green
                    "
                    data-tooltip="Ajouter un signalement"
                    data-position="left center"
                    data-variation="mini"
                  >
                    <i class="ui plus icon"></i>
                  </router-link>
                  <router-link
                    :to="{
                      name: 'dupliquer-type-signalement',
                      params: { slug_type_signal: type.slug },
                    }"
                    v-if="
                      project &&
                      permissions &&
                      permissions.can_create_feature_type &&
                      isOffline() !== true
                    "
                    class="
                      ui
                      compact
                      small
                      icon
                      right
                      floated
                      button button-hover-green
                    "
                    data-tooltip="Dupliquer un type de signalement"
                    data-position="left center"
                    data-variation="mini"
                  >
                    <i class="inverted grey copy alternate icon"></i>
                  </router-link>
                  <div
                    v-if="isImporting(type)"
                    class="import-message"
                  >
                    <i class="info circle icon" />
                    Import en cours
                  </div>
                  <div v-else v-frag>
                  <router-link
                    :to="{
                      name: 'editer-symbologie-signalement',
                      params: { slug_type_signal: type.slug },
                    }"
                    v-if="
                      project &&
                      type.geom_type === 'point' &&
                      permissions &&
                      permissions.can_create_feature_type &&
                      isOffline() != true
                    "
                    class="
                      ui
                      compact
                      small
                      icon
                      right
                      floated
                      button button-hover-green
                    "
                    data-tooltip="Éditer la symbologie du type de signalement"
                    data-position="left center"
                    data-variation="mini"
                  >
                    <i class="inverted grey paint brush alternate icon"></i>
                  </router-link>
                  <router-link
                    :to="{
                      name: 'editer-type-signalement',
                      params: { slug_type_signal: type.slug },
                    }"
                    v-if="
                      project &&
                      type.is_editable &&
                      permissions &&
                      permissions.can_create_feature_type &&
                      isOffline() !== true
                    "
                    class="
                      ui
                      compact
                      small
                      icon
                      right
                      floated
                      button button-hover-green
                    "
                    data-tooltip="Éditer le type de signalement"
                    data-position="left center"
                    data-variation="mini"
                  >
                    <i class="inverted grey pencil alternate icon"></i>
                  </router-link>
                  </div>
                </div>
              </div>
            </div>
            <div v-if="feature_types.length === 0">
              <i> Le projet ne contient pas encore de type de signalements. </i>
            </div>
          </div>

          <div class="nouveau-type-signalement">
            <router-link
              v-if="
                permissions &&
                permissions.can_update_project &&
                isOffline() !== true
              "
              :to="{
                name: 'ajouter-type-signalement',
                params: { slug: project.slug },
              }"
              class="ui compact basic button button-hover-green"
            >
              <i class="ui plus icon"></i>Créer un nouveau type de signalement
            </router-link>
          </div>
          <div class="nouveau-type-signalement">
            <a
              v-if="
                permissions &&
                permissions.can_update_project &&
                isOffline() !== true
              "
              class="
                ui
                compact
                basic
                button button-hover-green
                important-flex
                align-center
                text-left
              "
            >
              <i class="ui plus icon"></i>
              <label class="ui" for="json_file">
                <span class="label"
                  >Créer un nouveau type de signalement à partir d'un
                  GeoJSON</span
                >
              </label>
              <input
                type="file"
                accept="application/json, .json, .geojson"
                style="display: none"
                name="json_file"
                id="json_file"
                @change="onFileChange"
              />
            </a>
            <br />
            <div id="button-import" v-if="fileToImport.size > 0">
              <button
                :disabled="fileToImport.size === 0"
                @click="toNewFeatureType"
                class="ui fluid teal icon button"
              >
                <i class="upload icon"></i> Lancer l'import avec le fichier
                {{ fileToImport.name }}
              </button>
            </div>
          </div>
        </div>
        <div class="seven wide column">
          <div id="map" ref="map"></div>
        </div>
      </div>

      <div class="row">
        <div class="fourteen wide column">
          <div class="ui two stackable cards">
            <div class="red card">
              <div class="content">
                <div class="center aligned header">Derniers signalements</div>
                <div class="center aligned description">
                  <div
                    :class="{ active: featuresLoading }"
                    class="ui inverted dimmer"
                  >
                    <div class="ui text loader">
                      Récupération des signalements en cours...
                    </div>
                  </div>
                  <div class="ui relaxed list">
                    <div
                      v-for="(item, index) in features"
                      :key="item.title + index"
                      class="item"
                    >
                      <div class="content">
                        <div>
                          <router-link
                            :to="{
                              name: 'details-signalement',
                              params: {
                                slug: project.slug,
                                slug_type_signal: item.feature_type.slug,
                                slug_signal: item.feature_id,
                              },
                            }"
                            >{{ item.title || item.feature_id }}</router-link
                          >
                        </div>
                        <div class="description">
                          <i
                            >[{{ item.created_on | setDate
                            }}<span v-if="user && item.display_creator"
                              >, par {{ item.display_creator }}
                            </span>
                            ]</i
                          >
                        </div>
                      </div>
                    </div>
                    <i v-if="features.length === 0"
                      >Aucun signalement pour le moment.</i
                    >
                  </div>
                </div>
              </div>
            </div>
            <div class="orange card">
              <div class="content">
                <div class="center aligned header">Derniers commentaires</div>
                <div class="center aligned description">
                  <div
                    :class="{ active: projectInfoLoading }"
                    class="ui inverted dimmer"
                  >
                    <div class="ui text loader">
                      Récupération des commentaires en cours...
                    </div>
                  </div>
                  <div class="ui relaxed list">
                    <div
                      v-for="(item, index) in last_comments"
                      :key="'comment ' + index"
                      class="item"
                    >
                      <div class="content">
                        <div>
                          <router-link
                            :to="getRouteUrl(item.related_feature.feature_url)"
                            >"{{ item.comment }}"</router-link
                          >
                        </div>
                        <div class="description">
                          <i
                            >[ {{ item.created_on
                            }}<span v-if="user && item.display_author"
                              >, par {{ item.display_author }}
                            </span>
                            ]</i
                          >
                        </div>
                      </div>
                    </div>
                    <i v-if="!last_comments || last_comments.length === 0"
                      >Aucun commentaire pour le moment.</i
                    >
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div class="row">
        <div class="fourteen wide column">
          <div class="ui grey segment">
            <h3 class="ui header">Paramètres du projet</h3>
            <div class="ui five stackable cards">
              <div class="card">
                <div class="center aligned content">
                  <h4 class="ui center aligned icon header">
                    <i class="disabled grey archive icon"></i>
                    <div class="content">Délai avant archivage automatique</div>
                  </h4>
                </div>
                <div class="center aligned extra content">
                  {{ project.archive_feature }} jours
                </div>
              </div>
              <div class="card">
                <div class="content">
                  <h4 class="ui center aligned icon header">
                    <i class="disabled grey trash alternate icon"></i>
                    <div class="content">
                      Délai avant suppression automatique
                    </div>
                  </h4>
                </div>
                <div class="center aligned extra content">
                  {{ project.delete_feature }} jours
                </div>
              </div>
              <div class="card">
                <div class="content">
                  <h4 class="ui center aligned icon header">
                    <i class="disabled grey eye icon"></i>
                    <div class="content">
                      Visibilité des signalements publiés
                    </div>
                  </h4>
                </div>
                <div class="center aligned extra content">
                  {{ project.access_level_pub_feature }}
                </div>
              </div>
              <div class="card">
                <div class="content">
                  <h4 class="ui center aligned icon header">
                    <i class="disabled grey eye icon"></i>
                    <div class="content">
                      Visibilité des signalements archivés
                    </div>
                  </h4>
                </div>
                <div class="center aligned extra content">
                  {{ project.access_level_arch_feature }}
                </div>
              </div>
              <div class="card">
                <div class="content">
                  <h4 class="ui center aligned icon header">
                    <i class="disabled grey cogs icon"></i>
                    <div class="content">Modération</div>
                  </h4>
                </div>
                <div class="center aligned extra content">
                  {{ project.moderation ? "Oui" : "Non" }}
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <span v-else>
      <i class="icon exclamation triangle"></i>
      <span
        >Vous ne disposez pas des droits nécessaires pour consulter ce
        projet.</span
      >
    </span>

    <div
      v-if="isModalOpen"
      class="ui dimmer modals page transition visible active"
      style="display: flex !important"
    >
      <div
        :class="[
          'ui mini modal subscription',
          { 'transition visible active': isModalOpen },
        ]"
      >
        <i @click="isModalOpen = false" class="close icon"></i>
        <div class="ui icon header">
          <i class="envelope icon"></i>
          Notifications du projet
        </div>

        <div class="content">
          <button
            @click="subscribeProject"
            :class="['ui compact fluid button', is_suscriber ? 'red' : 'green']"
          >
            {{
              is_suscriber
                ? "Se désabonner de ce projet"
                : "S'abonner à ce projet"
            }}
          </button>
        </div>
      </div>
    </div>
    <div
      :class="isFileSizeModalOpen ? 'active' : ''"
      class="ui dimmer"
    >
      <div
        :class="isFileSizeModalOpen ? 'active' : ''"
        class="ui modal tiny"
        style="top: 20%"
      >
        <div class="header">
          Fichier trop grand!
        </div>
        <div class="content">
          <p>
            Impossible de créer un type de signalement à partir d'un fichier GeoJSON 
            de plus de 10Mo (celui importé fait {{ fileSize }} Mo).
          </p>
        </div>
        <div class="actions">
          <div
            class="ui button teal"
            @click="closeFileSizeModal"
          >
            Fermer
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import frag from "vue-frag";
import { mapUtil } from "@/assets/js/map-util.js";
import { mapGetters, mapState, mapActions, mapMutations } from "vuex";
import projectAPI from "@/services/project-api";
import featureAPI from "@/services/feature-api";

import axios from "@/axios-client.js";

import { fileConvertSizeToMo } from '@/assets/js/utils';

export default {
  name: "Project_details",

  props: ["message"],

  directives: {
    frag,
  },

  filters: {
    setDate(value) {
      const date = new Date(value);
      const d = date.toLocaleDateString("fr", {
        year: "2-digit",
        month: "numeric",
        day: "numeric",
      });
      return d;
    },
  },

  data() {
    return {
      infoMessage: "",
      importMessage: null,
      arraysOffline: [],
      arraysOfflineErrors: [],
      geojsonImport: [],
      fileToImport: { name: "", size: 0 },
      slug: this.$route.params.slug,
      isModalOpen: false,
      is_suscriber: false,
      tempMessage: null,
      projectInfoLoading: true,
      featureTypeImporting: false,
      featuresLoading: true,
      isFileSizeModalOpen: false
    };
  },

  computed: {
    ...mapGetters([
      'project',
      'permissions'
    ]),
    ...mapState('feature_type', [
      'feature_types',
      'importFeatureTypeData'
    ]),
    ...mapState('feature', [
      'features'
    ]),
    ...mapState([
      'last_comments',
      'user',
      'reloadIntervalId'
    ]),
    DJANGO_BASE_URL() {
      return this.$store.state.configuration.VUE_APP_DJANGO_BASE;
    },
    API_BASE_URL() {
      return this.$store.state.configuration.VUE_APP_DJANGO_API_BASE;
    },
    fileSize() {
      return fileConvertSizeToMo(this.fileToImport.size);
    }
  },

  watch: {
    feature_types: {
      deep: true,
      handler(newValue, oldValue) {
        if (newValue && newValue !== oldValue) {
          this.GET_IMPORTS({
            project_slug: this.$route.params.slug
          });
        }
      }
    },

    importFeatureTypeData: {
      deep: true,
      handler(newValue) {
        if (newValue && newValue.some(el => el.status === 'pending') && !this.reloadIntervalId) {
          this.SET_RELOAD_INTERVAL_ID(setInterval(() => {
            this.GET_IMPORTS({
              project_slug: this.$route.params.slug
            });
          }, this.$store.state.configuration.VUE_APP_RELOAD_INTERVAL));
        } else if (newValue && !newValue.some(el => el.status === 'pending') && this.reloadIntervalId) {
          this.GET_PROJECT_FEATURE_TYPES(this.project.slug);
          this.CLEAR_RELOAD_INTERVAL_ID();
        }
      }
    }
  },

  methods: {
    ...mapMutations([
      'SET_RELOAD_INTERVAL_ID',
      'CLEAR_RELOAD_INTERVAL_ID'
    ]),
    ...mapActions([
      'GET_PROJECT_INFO'
    ]),
    ...mapActions('feature_type', [
      'GET_IMPORTS'
    ]),
    ...mapActions('feature', [
      'GET_PROJECT_FEATURES'
    ]),
    ...mapActions('feature_type', [
      'GET_PROJECT_FEATURE_TYPES'
    ]),
    refreshId() {
      return "?ver=" + Math.random();
    },
    getRouteUrl(url) {
      return "/" + url.replace(this.$store.state.configuration.BASE_URL, ""); // remove duplicate /geocontrib
    },
    isOffline() {
      return navigator.onLine === false;
    },
    isImporting(type) {
      if (this.importFeatureTypeData) {
        const singleImportData = this.importFeatureTypeData.find(el => el.feature_type_title === type.slug);
        return singleImportData && singleImportData.status === 'pending'
      }
      return false;
    },

    checkForOfflineFeature() {
      let arraysOffline = [];
      let localStorageArray = localStorage.getItem("geocontrib_offline");
      if (localStorageArray) {
        arraysOffline = JSON.parse(localStorageArray);
        this.arraysOffline = arraysOffline.filter(
          (x) => x.project === this.project.slug
        );
      }
    },

    sendOfflineFeatures() {
      var promises = [];
      let self = this;
      this.arraysOfflineErrors = [];
      this.arraysOffline.forEach((feature) => {
        console.log(feature);
        if (feature.type === "post") {
          promises.push(
            axios
              .post(`${this.API_BASE_URL}features/`, feature.geojson)
              .then((response) => {
                if (response.status === 201 && response.data) {
                  return "OK"
                }
                else{
                  self.arraysOfflineErrors.push(feature);
                }
              })
              .catch((error) => {
                console.log(error);
                self.arraysOfflineErrors.push(feature);
              })
          );
        } else if (feature.type === "put") {
          promises.push(
            axios
              .put(
                `${this.API_BASE_URL}features/${feature.featureId}`,
                feature.geojson
              )
              .then((response) => {
                console.log(response);
                if (response.status === 200 && response.data) {
                  return "OK"
                }
                else{
                  self.arraysOfflineErrors.push(feature);
                }
              })
              .catch((error) => {
                console.log(error);
                self.arraysOfflineErrors.push(feature);
              })
          );
        }
      });
      Promise.all(promises).then(() => {
        this.updateLocalStorage();
        window.location.reload();
      });
    },

    updateLocalStorage() {
      let arraysOffline = [];
      let localStorageArray = localStorage.getItem("geocontrib_offline");
      if (localStorageArray) {
        arraysOffline = JSON.parse(localStorageArray);
      }
      let arraysOfflineOtherProject = arraysOffline.filter(
        (x) => x.project !== this.project.slug
      );
      this.arraysOffline = [];
      arraysOffline = arraysOfflineOtherProject.concat(this.arraysOfflineErrors);
      localStorage.setItem("geocontrib_offline", JSON.stringify(arraysOffline));
    },

    toNewFeatureType() {
      this.featureTypeImporting = true;
      this.$router.push({
        name: "ajouter-type-signalement",
        params: {
          geojson: this.geojsonImport,
          fileToImport: this.fileToImport,
        },
      });
      this.featureTypeImporting = false;
    },

    onFileChange(e) {
      this.featureTypeImporting = true;
      var files = e.target.files || e.dataTransfer.files;
      if (!files.length) return;
      this.fileToImport = files[0];
      // TODO : VALIDATION IF FILE IS JSON
      if (parseFloat(fileConvertSizeToMo(this.fileToImport.size)) > 10) {
        this.isFileSizeModalOpen = true;
      } else if (this.fileToImport.size > 0) {
        const fr = new FileReader();
        try {
          fr.onload = (e) => {
            this.geojsonImport = JSON.parse(e.target.result);
            this.featureTypeImporting = false;
          };
          fr.readAsText(this.fileToImport);
          //* stock filename to import features afterward
          this.$store.commit(
            "feature_type/SET_FILE_TO_IMPORT",
            this.fileToImport
          );
        } catch (err) {
          console.error(err);
          this.featureTypeImporting = false
        }
      } else {
        this.featureTypeImporting = false;
      }
    },

    closeFileSizeModal() {
      this.fileToImport = { name: "", size: 0 };
      this.featureTypeImporting = false;
      this.isFileSizeModalOpen = false;
    },

    subscribeProject() {
      projectAPI
        .subscribeProject({
          suscribe: !this.is_suscriber,
          projectSlug: this.$route.params.slug,
        })
        .then((data) => {
          this.is_suscriber = data.is_suscriber;
          this.isModalOpen = false;
          if (this.is_suscriber)
            this.infoMessage =
              "Vous êtes maintenant abonné aux notifications de ce projet.";
          else
            this.infoMessage =
              "Vous ne recevrez plus les notifications de ce projet.";
          setTimeout(() => (this.infoMessage = ""), 3000);
        });
    },
    initMap() {
      if (this.project && this.permissions.can_view_project) {
        this.$store.dispatch("map/INITIATE_MAP", this.$refs.map);
        this.checkForOfflineFeature();
        let project_id = this.$route.params.slug.split("-")[0];
        const mvtUrl = `${this.API_BASE_URL}features.mvt/?tile={z}/{x}/{y}&project_id=${project_id}`;
        mapUtil.addVectorTileLayer(
          mvtUrl,
          this.$route.params.slug,
          this.$store.state.feature_type.feature_types
        );

        this.arraysOffline.forEach((x) => (x.geojson.properties.color = "red"));
        const features = this.arraysOffline.map((x) => x.geojson);
        mapUtil.addFeatures(
          features,
          {},
          true,
          this.$store.state.feature_type.feature_types
        );

        featureAPI
          .getFeaturesBbox(this.project.slug)
          .then((bbox) => {
            if (bbox) {
              mapUtil.getMap().fitBounds(bbox, { padding: [25, 25] });
            }
          });
      }
    },
  },

  created() {
    if (this.user) {
      projectAPI
        .getProjectSubscription({ projectSlug: this.$route.params.slug })
        .then((data) => (this.is_suscriber = data.is_suscriber));
    }
    this.$store.commit("feature_type/SET_FEATURE_TYPES", []); //* empty feature_types remaining from previous project
  },

  mounted() {
    this.GET_PROJECT_INFO(this.slug)
      .then(() => {
        this.projectInfoLoading = false;
        setTimeout(this.initMap, 1000);
      })
      .catch(() => {
        this.projectInfoLoading = false;
      });
    this.GET_PROJECT_FEATURES({
      project_slug: this.slug,
      ordering: '-created_on',
      limit: 5
    })
      .then(() => {
        this.featuresLoading = false;
      })
      .catch(() => {
        this.featuresLoading = false;
      });

    if (this.message) {
      this.tempMessage = this.message;
      document
        .getElementById("message")
        .scrollIntoView({ block: "end", inline: "nearest" });
      setTimeout(() => (this.tempMessage = null), 5000); //* hide message after 5 seconds
    }
  },
};
</script>

<style>
@import "../../assets/resources/semantic-ui-2.4.2/semantic.min.css";
#map {
  width: 100%;
  height: 100%;
  min-height: 250px;
}
.list-image-type {
  margin-right: 5px;
  height: 25px;
  vertical-align: bottom;
}
/* // ! missing style in semantic.min.css, je ne comprends pas comment... */
.ui.right.floated.button {
  float: right;
  margin: 0 0 0 1em;
}

.feature-type-container {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.feature-type-container > .middle.aligned.content {
  width: 50%;
}

.feature-type-title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  line-height: 1.5em;
}

.nouveau-type-signalement {
  cursor: pointer;
  padding-top: 1em;
}
.nouveau-type-signalement > a > .ui > .label{
  cursor: pointer;
}

#button-import {
  padding-top: 0.5em;
}
.fullwidth {
  width: 100%;
}
.important-flex {
  display: flex !important;
}
.align-center {
  align-items: center !important;
}
.text-left {
  text-align: left !important;
}

.import-message {
  width: fit-content;
  line-height: 2em;
  color: teal;
}
</style>