Skip to content

Commit

Permalink
Merge pull request #88
Browse files Browse the repository at this point in the history
* chore(deps): Add pinia

* refactor: Refactor TS types and setup article store

* docs: Add missing translations

* fix: Fix filter message translation issues
  • Loading branch information
botmaster authored Feb 11, 2024
1 parent 34cafd8 commit e0ee2c8
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 163 deletions.
151 changes: 94 additions & 57 deletions components/ArticleListActionBar.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
// Define props
import type { IMultiSelectTagOption } from '~/components/MultiSelectTag.vue';
import type { IArticleTag } from '~/types/types';
defineProps<{
tags: IMultiSelectTagOption[]
tags: IArticleTag[]
pending: boolean
}>();
Expand All @@ -13,77 +13,114 @@ const emit = defineEmits(['clearFilters']);
const { t } = useI18n();
// Define model
const selectedOptions = defineModel<IMultiSelectTagOption[]>('selectedOptions', { required: true });
const selectedOptions = defineModel<IArticleTag[]>('selectedOptions', { required: true });
const search = defineModel<string>('search', { required: true });
const status = defineModel<string>('status', { required: true });
const sort = defineModel<string>('sort', { required: true });
// Computed - Has any filters
const hasFilters = computed<boolean>(() => {
return status.value !== '' || search.value !== '';
return status.value !== '' || search.value !== '' || selectedOptions.value.length > 0;
});
// Map status
const mapStatus = computed<Record<string, string>>(() => {
return {
'all': t('pages.readings.filters.status.all'),
'To read': t('pages.readings.filters.status.toRead'),
'Read': t('pages.readings.filters.status.read'),
'Reading': t('pages.readings.filters.status.inProgress'),
'Canceled': t('pages.readings.filters.status.canceled'),
};
});
// Map sort
const mapSort = computed<Record<string, string>>(() => {
return {
'Created time': t('pages.readings.sort.createdTime'),
'Last edited time': t('pages.readings.sort.lastEditedTime'),
'Name': t('pages.readings.sort.name'),
'Score': t('pages.readings.sort.score'),
};
});
</script>

<template>
<div class="flex flex-col flex-wrap gap-x-6 gap-y-1.5 lg:flex-row lg:items-end">
<div class="lg:mr-8">
<input
v-model.lazy="search" autocomplete="search" name="search" type="text"
placeholder="Search an article" class="form-input min-w-64"
>
</div>
<div>
<div class="flex flex-col flex-wrap gap-x-4 gap-y-1.5 md:flex-row md:items-end">
<div class="md:mr-8">
<input
v-model.lazy="search" autocomplete="search" name="search" type="text"
:placeholder="t('pages.readings.filters.searchPlaceHolder')" class="form-input min-w-64"
>
</div>

<div>
<label class="mb-1 block" for="selectStatus">{{ t('pages.readings.filters.statusLabel') }}</label>
<select id="selectStatus" v-model="status" class="form-select">
<option value="">
{{ t('pages.readings.filters.status.all') }}
</option>
<option value="To read">
{{ t('pages.readings.filters.status.toRead') }}
</option>
<option value="Read">
{{ t('pages.readings.filters.status.read') }}
</option>
<option value="Reading">
{{ t('pages.readings.filters.status.inProgress') }}
</option>
<option value="Canceled">
{{ t('pages.readings.filters.status.canceled') }}
</option>
</select>
</div>
<div>
<label class="mb-1 block" for="selectStatus">{{ t('pages.readings.filters.statusLabel') }}</label>
<select id="selectStatus" v-model="status" class="form-select">
<option value="">
{{ t('pages.readings.filters.status.all') }}
</option>
<option value="To read">
{{ mapStatus['To read'] }}
</option>
<option value="Read">
{{ mapStatus.Read }}
</option>
<option value="Reading">
{{ mapStatus.Reading }}
</option>
<option value="Canceled">
{{ mapStatus.Canceled }}
</option>
</select>
</div>

<div>
<MultiSelectTag v-model="selectedOptions" :options="tags" />
</div>
<div>
<MultiSelectTag v-model="selectedOptions" :options="tags" :placeholder="t('pages.readings.filters.tagsPlaceHolder')" />
</div>

<div>
<label class="mb-1 block" for="selectSort">{{ t('pages.readings.sort.sortLabel') }}</label>
<select id="selectSort" v-model="sort" class="form-select">
<option value="Created time">
{{ t('pages.readings.sort.createdTime') }}
</option>
<option value="Last edited time">
{{ t('pages.readings.sort.lastEditedTime') }}
</option>
<option value="Name">
{{ t('pages.readings.sort.name') }}
</option>
<option value="Score">
{{ t('pages.readings.sort.score') }}
</option>
</select>
</div>
<div>
<label class="mb-1 block" for="selectSort">{{ t('pages.readings.sort.sortLabel') }}</label>
<select id="selectSort" v-model="sort" class="form-select">
<option value="Created time">
{{ mapSort['Created time'] }}
</option>
<option value="Last edited time">
{{ mapSort['Last edited time'] }}
</option>
<option value="Name">
{{ mapSort.Name }}
</option>
<option value="Score">
{{ mapSort.Score }}
</option>
</select>
</div>

<div v-if="hasFilters" class="leading-none lg:mb-2">
<button class="inline-flex items-center" @click="emit('clearFilters')">
<Icon class="mr-1.5" name="material-symbols:cancel" />
{{ t('common.clearFilters') }}
</button>
<Transition name="fade">
<AppLoader v-if="pending" class="m-1 text-2xl" />
</Transition>
</div>

<Transition name="fade">
<AppLoader v-if="pending" class="m-1 text-2xl" />
<div v-if="hasFilters" class="mt-4 flex items-start gap-6">
<p class="flex-grow text-sm">
{{ t('common.article', 1) }} <i18n-t v-if="status" keypath="pages.readings.filters.message.withStatus" tag="span" scope="global">
<strong>{{ mapStatus[status] }}</strong>
</i18n-t> <i18n-t v-if="selectedOptions.length > 0" tag="span" keypath="pages.readings.filters.message.withTags" scope="global">
<strong>{{ selectedOptions.map(tag => tag.name).toString() }}</strong>
</i18n-t> <i18n-t v-if="search" tag="span" keypath="pages.readings.filters.message.withSearch" scope="global">
<strong>{{ search }}</strong>
</i18n-t> <i18n-t tag="span" keypath="pages.readings.filters.message.sortedBy" scope="global">
<strong>{{ mapSort[sort] }}</strong>
</i18n-t>.
</p>
<button class="inline-flex flex-shrink-0 items-center" @click="emit('clearFilters')">
<Icon class="mr-1.5" name="material-symbols:cancel" />
{{ t('common.clearFilters') }}
</button>
</div>
</Transition>
</div>
</template>
11 changes: 7 additions & 4 deletions components/MultiSelectTag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
import { Combobox, ComboboxButton, ComboboxInput, ComboboxLabel, ComboboxOption, ComboboxOptions } from '@headlessui/vue';
// Interfaces
export interface IMultiSelectTagOption {
interface IMultiSelectTagOption {
id: string
name: string
color: 'blue' | 'brown' | 'default' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'yellow'
}
// Props
const props = defineProps<{
const props = withDefaults(defineProps<{
options: IMultiSelectTagOption[]
}>();
placeholder?: string
}>(), {
placeholder: 'Select an option',
});
// Model
const selectedOptions = defineModel<IMultiSelectTagOption[]>({ required: true });
Expand Down Expand Up @@ -44,7 +47,7 @@ function displayValue(options: IMultiSelectTagOption[]) {
class="multi-select__values-container"
>
<p class="multi-select__value">
<span v-if="selectedOptions.length === 0" class="multi-select__label">Select an option</span> <span
<span v-if="selectedOptions.length === 0" class="multi-select__label">{{ props.placeholder }}</span> <span
v-else
>{{ displayValue(selectedOptions) }}</span>
</p>
Expand Down
11 changes: 10 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"common": {
"article": "Article | Articles",
"clearFilters": "Clear filters",
"go": "Go",
"goToPage": "Go to page",
Expand Down Expand Up @@ -38,15 +39,23 @@
"articleList": "Article list",
"bdd": "Data base",
"filters": {
"message": {
"sortedBy": "sort by {0}",
"withSearch": "matching {0}",
"withStatus": "with the status {0}",
"withTags": "integrating tags {0}"
},
"pageSizes": "Articles by page",
"searchPlaceHolder": "Search an article",
"status": {
"all": "All",
"canceled": "Canceled",
"inProgress": "In Progress",
"read": "Read",
"toRead": "To read"
},
"statusLabel": "Status"
"statusLabel": "Status",
"tagsPlaceHolder": "Select tags"
},
"filtersTooRestrictive": "🤔 Filters too restrictive ?",
"sort": {
Expand Down
11 changes: 10 additions & 1 deletion locales/fr.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"common": {
"article": "Article | Articles",
"clearFilters": "Effacer les filtres",
"go": "Allez",
"goToPage": "Aller à la page",
Expand Down Expand Up @@ -38,15 +39,23 @@
"articleList": "Liste des articles",
"bdd": "Base de données",
"filters": {
"message": {
"sortedBy": "triés par {0}",
"withSearch": "correspondant à {0}",
"withStatus": "avec le status {0}",
"withTags": "intégrant les tags {0}"
},
"pageSizes": "Articles par page",
"searchPlaceHolder": "Rechercher un article",
"status": {
"all": "Tous",
"canceled": "Annulés",
"inProgress": "En cours",
"read": "Lus",
"toRead": "À lire"
},
"statusLabel": "Statut"
"statusLabel": "Statut",
"tagsPlaceHolder": "Sélectionner des tags"
},
"filtersTooRestrictive": "🤔 Filtres trop restrictifs ?",
"sort": {
Expand Down
1 change: 1 addition & 0 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export default defineNuxtConfig({
'nuxt-simple-robots',
'@nuxt/devtools',
'@nuxtjs/sitemap',
'@pinia/nuxt',
],

devtools: {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@nuxtjs/i18n": "^8.1.0",
"@nuxtjs/sitemap": "^5.1.0",
"@nuxtjs/tailwindcss": "^6.11.3",
"@pinia/nuxt": "^0.5.1",
"@tailwindcss/forms": "^0.5.7",
"@types/node": "^20.11.16",
"@types/splitting": "^1.0.6",
Expand Down
Loading

0 comments on commit e0ee2c8

Please sign in to comment.