Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added user attrs to the UserDialog #589

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions agate-ui/src/components/SystemUserAttributes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
<q-td key="required" :props="props">
<q-icon :name="props.row.required ? 'check_box' : 'check_box_outline_blank'" size="sm" dense />
</q-td>
</q-tr> </template
>/
</q-tr>
</template>
</q-table>

<confirm-dialog
Expand Down Expand Up @@ -156,6 +156,7 @@ function onCancel() {
}

function onSavedAttribute() {
selected.value = undefined;
showEdit.value = false;
systemStore.init();
}
Expand Down
6 changes: 5 additions & 1 deletion agate-ui/src/components/UserDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,11 @@
</div>
</div>
</q-form>

<user-attributes-list class="q-mt-lg" v-model="selected!.attributes" />
</q-card-section>


<q-separator />

<q-card-actions align="right" class="bg-grey-3">
Expand All @@ -180,9 +183,10 @@
</template>

<script setup lang="ts">
import { copyToClipboard } from 'quasar';
import type { UserDto } from 'src/models/Agate';
import { notifyError, notifyInfo, notifySuccess } from 'src/utils/notify';
import { copyToClipboard } from 'quasar';
import UserAttributesList from 'src/components/attributes/UserAttributesList.vue';

const { t } = useI18n();
const userStore = useUserStore();
Expand Down
103 changes: 103 additions & 0 deletions agate-ui/src/components/attributes/UserAttributeDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<template>
<q-dialog v-model="showDialog" persistent @hide="onHide">
<q-card class="dialog-md">
<q-card-section>
<div class="text-h6">{{ editMode ? t('user.attributes.update') : t('user.attributes.add') }}</div>
</q-card-section>

<q-separator />
<q-card-section>
<q-form ref="formRef">
<q-input
v-model="newAttribue.name"
dense
type="text"
:label="t('name') + ' *'"
class="q-mb-md"
lazy-rules
:rules="[validateRequired, validateUnique]"
:disable="editMode"
>
</q-input>
<q-input
v-model="newAttribue.value"
dense
type="text"
:label="t('value')"
class="q-mb-md"
lazy-rules
/>
</q-form>
</q-card-section>

<q-separator />

<q-card-actions align="right" class="bg-grey-3">
<q-btn flat :label="t('cancel')" color="secondary" @click="onCancel" v-close-popup />
<q-btn flat :label="t('save')" color="primary" @click="onSave" />
</q-card-actions>
</q-card>
</q-dialog>
</template>

<script setup lang="ts">
import type { AttributeDto } from 'src/models/Agate';

interface DialogProps {
modelValue: boolean;
attributes: AttributeDto[];
attribute: AttributeDto | undefined;
}

const { t } = useI18n();
const props = defineProps<DialogProps>();
const emit = defineEmits(['update:modelValue', 'saved', 'cancel']);
const formRef = ref();
const showDialog = ref(props.modelValue);
const newAttribue = ref<AttributeDto>({} as AttributeDto);
const editMode = computed(() => !!props.attribute && !!props.attribute.name);
const attributes = computed<AttributeDto[]>(() => props.attributes || [] as AttributeDto[]);

function validateRequired(value: string) {
return !!value || t('name_required');
}

function validateUnique(value: string) {
if (attributes) {
const exists = attributes.value.find((attr) => attr.name === value);
return !exists || t('user.attributes.name_exists');
}
return true;
}

watch(
() => props.modelValue,
(value) => {
if (value) {
if (props.attribute) {
newAttribue.value = { ...props.attribute } as AttributeDto;
} else {
newAttribue.value = {} as AttributeDto;
}
}

showDialog.value = value;
},
);

function onHide() {
emit('update:modelValue', false);
}

function onCancel() {
emit('cancel');
}

async function onSave() {
const valid = await formRef.value.validate();
if (valid) {
emit('saved', newAttribue.value);
emit('update:modelValue', false);
}
}
</script>
173 changes: 173 additions & 0 deletions agate-ui/src/components/attributes/UserAttributesList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<template>
<div>
<span class="text-h6">
{{ t('user.attributes.title') }}
</span>
<q-table
:rows="filteredAttributes"
flat
row-key="name"
:columns="columns"
:pagination="initialPagination"
:hide-pagination="filteredAttributes.length <= initialPagination.rowsPerPage"
>
<template v-slot:top-left>
<q-btn size="sm" icon="add" color="primary" :label="t('add')" @click="onAdd" />
</template>
<template v-slot:top-right>
<q-input v-model="filter" debounce="300" :placeholder="t('search')" dense clearable class="q-mr-md">
<template v-slot:prepend>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:body="props">
<q-tr :props="props" @mouseover="onOverRow(props.row)" @mouseleave="onLeaveRow(props.row)">
<q-td key="name" :props="props">
<span class="text-primary">{{ props.row.name }}</span>
<div class="float-right">
<q-btn
rounded
dense
flat
size="sm"
color="secondary"
:icon="toolsVisible[props.row.name] ? 'edit' : 'none'"
:title="t('edit')"
class="q-ml-xs"
@click="onShowEdit(props.row)"
/>
<q-btn
rounded
dense
flat
size="sm"
color="secondary"
:title="t('delete')"
:icon="toolsVisible[props.row.name] ? 'delete' : 'none'"
class="q-ml-xs"
@click="onShowDelete(props.row)"
/>
</div>
</q-td>
<q-td key="value" :props="props">
<span>{{ props.row.value }}</span>
</q-td>
</q-tr>
</template>
</q-table>

<confirm-dialog
v-model="showDelete"
:title="t('user.attributes.remove')"
:text="t('user.attributes.remove_confirm', { name: selected?.name })"
@confirm="onDelete"
/>

<user-attribute-dialog
v-model="showEdit"
:attributes="userAttributes"
:attribute="selected"
@saved="onSavedAttribute"
@cancel="onCancel"
/>
</div>
</template>

<script setup lang="ts">
import type { AttributeDto } from 'src/models/Agate';
import { DefaultAlignment } from 'src/components/models';
import ConfirmDialog from 'src/components/ConfirmDialog.vue';
import UserAttributeDialog from 'src/components/attributes/UserAttributeDialog.vue';

interface Props {
modelValue: AttributeDto[] | undefined;
}

const props = defineProps<Props>();
const emit = defineEmits(['update:modelValue']);
const { t } = useI18n();

const userAttributes = computed({
get: () => props.modelValue ?? ([] as AttributeDto[]),
set: (value: AttributeDto[]) => {
emit('update:modelValue', value);
},
});

const toolsVisible = ref<{ [key: string]: boolean }>({});
const initialPagination = ref({
descending: false,
page: 1,
rowsPerPage: 5,
});

const filteredAttributes = computed(
() =>
userAttributes.value.filter((attr) =>
filter.value ? attr.name.toLowerCase().includes(filter.value.toLowerCase()) : true,
) || [],
);

const filter = ref('');
const selected = ref();
const showEdit = ref(false);
const showDelete = ref(false);

const columns = computed(() => [
{ name: 'name', label: t('name'), field: 'name', align: DefaultAlignment },
{ name: 'value', label: t('value'), field: 'value', align: DefaultAlignment },
]);

function onOverRow(row: AttributeDto) {
toolsVisible.value[row.name] = true;
}

function onLeaveRow(row: AttributeDto) {
toolsVisible.value[row.name] = false;
}

function onAdd() {
selected.value = undefined;
showEdit.value = true;
}

function onShowEdit(row: AttributeDto) {
selected.value = row;
showEdit.value = true;
}

function onShowDelete(row: AttributeDto) {
selected.value = row;
showDelete.value = true;
}

function onDelete() {
if (selected.value) {
const index = userAttributes.value.indexOf(selected.value);
if (index !== -1) {
userAttributes.value.splice(index, 1);
emit('update:modelValue', userAttributes.value)
}
}
}

function onCancel() {
showEdit.value = false;
}

function onSavedAttribute(newAttribute: AttributeDto) {
if (newAttribute) {
const index = userAttributes.value.findIndex((attr) => attr.name === newAttribute.name);
if (index !== -1) {
userAttributes.value[index] = newAttribute;
} else {
userAttributes.value.push(newAttribute);
}
emit('update:modelValue', userAttributes.value);
}

selected.value = undefined;
showEdit.value = false;
}
</script>
12 changes: 12 additions & 0 deletions agate-ui/src/i18n/en/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export default {
PENDING: 'User is pending approval.',
},
update_password: 'Update password',
attributes: {
title: 'User Attributes',
add: 'Add User Attribute',
update: 'Update User Attribute',
updated: 'User attribute updated successfully',
update_failed: 'Failed to update user attribute',
remove: 'Remove User Attribute',
remove_confirm: 'Please confirm the removal of the user attribute: {name}',
name_exists: 'User attribute name already exists',
}
},
realm: {
activate: 'Activate',
Expand Down Expand Up @@ -240,6 +250,8 @@ export default {
values_hint: 'Comma separated values',
add: 'Add Attribute',
update: 'Update Attribute',
updated: 'Attribute updated successfully',
update_failed: 'Failed to update attribute',
remove: 'Remove Attribute',
remove_confirm: 'Please confirm the removal of the attribute: {name}',
name_exists: 'Attribute name already exists',
Expand Down
12 changes: 12 additions & 0 deletions agate-ui/src/i18n/fr/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ export default {
PENDING: "Usager en attente d'approbation.",
},
update_password: 'Mettre à jour le mot de passe',
attributes: {
title: 'Attributs utilisateur',
add: 'Ajouter un attribut d\'utilisateur',
update: "Mettre à Jour l'attribut d'utilisateur",
updated: 'Attribut utilisateur mis à jour avec succès',
update_failed: 'Échec de la mise à jour de l\'attribut utilisateur',
remove: "Supprimer l'attribut utilisateur",
remove_confirm: "Veuillez confirmer la suppression de l'attribut utilisateur : {name}",
name_exists: 'Le nom de l\'attribut utilisateur existe déjà',
}
},
realm: {
activate: 'Activer',
Expand Down Expand Up @@ -240,6 +250,8 @@ export default {
values_hint: 'Valeurs séparées par des virgules',
add: 'Ajouter un Attribut',
update: "Mettre à Jour l'Attribut",
updated: 'Attribut mis à jour',
update_failed: 'Échec de la mise à jour de l\'attribut',
remove: "Supprimer l'Attribut",
remove_confirm: "Veuillez confirmer la suppression de l'attribut : {name}",
name_exists: "Le nom de l'attribut existe déjà",
Expand Down