@testdracul/media-frontend 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/.env.development +4 -0
  2. package/.env.example +3 -0
  3. package/.eslintrc.json +25 -0
  4. package/babel.config.js +5 -0
  5. package/dist/dracul-media-frontend.es.js +16238 -0
  6. package/dist/dracul-media-frontend.umd.js +586 -0
  7. package/dist/media-frontend.css +1 -0
  8. package/docs-en.md +45 -0
  9. package/docs-es.md +45 -0
  10. package/package.json +56 -0
  11. package/readme.md +36 -0
  12. package/src/components/CsvWebViewer/CsvWebViewer.vue +81 -0
  13. package/src/components/CsvWebViewer/index.ts +4 -0
  14. package/src/components/FileUpload/FileUpload.vue +94 -0
  15. package/src/components/FileUpload/index.ts +4 -0
  16. package/src/components/FileUploadButton/FileUploadButton.vue +127 -0
  17. package/src/components/FileUploadButton/index.ts +4 -0
  18. package/src/components/FileUploadExpiration/FileUploadExpiration.vue +274 -0
  19. package/src/components/FileUploadExpiration/index.ts +4 -0
  20. package/src/components/FileUploadExpress/FileUploadExpress.vue +208 -0
  21. package/src/components/FileUploadExpress/index.ts +4 -0
  22. package/src/components/FileView/FileView.vue +336 -0
  23. package/src/components/FileView/index.ts +4 -0
  24. package/src/components/GroupsShow/GroupsShow.vue +40 -0
  25. package/src/components/GroupsShow/index.ts +4 -0
  26. package/src/components/MediaField/MediaField.vue +62 -0
  27. package/src/components/MediaField/index.ts +4 -0
  28. package/src/components/PdfWebViewer/PdfWebViewer.vue +81 -0
  29. package/src/components/PdfWebViewer/index.ts +4 -0
  30. package/src/components/UsersShow/UsersShow.vue +39 -0
  31. package/src/components/UsersShow/index.ts +4 -0
  32. package/src/components/XlsxWebViewer/XlsxWebViewer.vue +70 -0
  33. package/src/components/XlsxWebViewer/index.ts +4 -0
  34. package/src/helpers/redeableBytes.ts +9 -0
  35. package/src/i18n/index.ts +22 -0
  36. package/src/i18n/messages/DocMessages.ts +31 -0
  37. package/src/i18n/messages/ExtraMessages.ts +29 -0
  38. package/src/i18n/messages/FileMessages.ts +223 -0
  39. package/src/i18n/messages/UserStorageMessages.ts +145 -0
  40. package/src/i18n/permissions/FilePermissionMessages.ts +50 -0
  41. package/src/i18n/permissions/OldPermissionMessages.ts +59 -0
  42. package/src/i18n/permissions/UserStoragePermissionMessages.ts +40 -0
  43. package/src/index.ts +70 -0
  44. package/src/mixins/readableBytesMixin.ts +9 -0
  45. package/src/pages/FileManagementPage/FileCreate/FileCreate.vue +108 -0
  46. package/src/pages/FileManagementPage/FileCreate/index.ts +3 -0
  47. package/src/pages/FileManagementPage/FileCrud/FileCrud.vue +133 -0
  48. package/src/pages/FileManagementPage/FileCrud/index.ts +4 -0
  49. package/src/pages/FileManagementPage/FileDelete/FileDelete.vue +61 -0
  50. package/src/pages/FileManagementPage/FileDelete/index.ts +3 -0
  51. package/src/pages/FileManagementPage/FileFilters/FileFilters.vue +150 -0
  52. package/src/pages/FileManagementPage/FileFilters/index.ts +3 -0
  53. package/src/pages/FileManagementPage/FileForm/FileForm.vue +184 -0
  54. package/src/pages/FileManagementPage/FileForm/UserCombobox.vue +66 -0
  55. package/src/pages/FileManagementPage/FileForm/index.ts +3 -0
  56. package/src/pages/FileManagementPage/FileList/FileEditButton.vue +410 -0
  57. package/src/pages/FileManagementPage/FileList/FileList.vue +178 -0
  58. package/src/pages/FileManagementPage/FileList/index.ts +4 -0
  59. package/src/pages/FileManagementPage/FileShow/FileShow.vue +23 -0
  60. package/src/pages/FileManagementPage/FileShow/FileShowData.vue +35 -0
  61. package/src/pages/FileManagementPage/FileShow/index.ts +3 -0
  62. package/src/pages/FileManagementPage/FileUpdate/FileUpdate.vue +107 -0
  63. package/src/pages/FileManagementPage/FileUpdate/index.ts +4 -0
  64. package/src/pages/FileManagementPage/index.vue +20 -0
  65. package/src/pages/MediaDocPage/MediaDocCard.vue +35 -0
  66. package/src/pages/MediaDocPage/MediaDocPage.vue +78 -0
  67. package/src/pages/UserStoragePage/UserStorage.vue +311 -0
  68. package/src/pages/UserStoragePage/UserStorageForm/UserStorageForm.vue +172 -0
  69. package/src/pages/UserStoragePage/UserStorageUpdate/UserStorageUpdate.vue +91 -0
  70. package/src/pages/UserStoragePage/index.vue +14 -0
  71. package/src/providers/FileMetricsProvider.ts +47 -0
  72. package/src/providers/FileProvider.ts +60 -0
  73. package/src/providers/UploadProvider.ts +32 -0
  74. package/src/providers/UserStorageProvider.ts +47 -0
  75. package/src/providers/gql/almacenamientoPorUsuario.graphql +10 -0
  76. package/src/providers/gql/cantidadArchivosPorUsuario.graphql +10 -0
  77. package/src/providers/gql/fetchMediaVariables.graphql +6 -0
  78. package/src/providers/gql/fileCreate.graphql +27 -0
  79. package/src/providers/gql/fileDelete.graphql +7 -0
  80. package/src/providers/gql/fileFetch.graphql +29 -0
  81. package/src/providers/gql/fileFind.graphql +29 -0
  82. package/src/providers/gql/fileGlobalMetrics.graphql +6 -0
  83. package/src/providers/gql/filePaginate.graphql +38 -0
  84. package/src/providers/gql/fileUpdate.graphql +29 -0
  85. package/src/providers/gql/fileUpload.graphql +29 -0
  86. package/src/providers/gql/fileUploadAnonymous.graphql +25 -0
  87. package/src/providers/gql/fileUserMetrics.graphql +9 -0
  88. package/src/providers/gql/userStorageFetch.graphql +17 -0
  89. package/src/providers/gql/userStorageFindByUser.graphql +17 -0
  90. package/src/providers/gql/userStorageUpdate.graphql +31 -0
  91. package/src/routes/index.ts +32 -0
  92. package/vite.config.ts +65 -0
  93. package/vue.config.js +22 -0
@@ -0,0 +1,9 @@
1
+ import redeableBytes from '../helpers/redeableBytes'
2
+
3
+ export default {
4
+ computed: {
5
+ redeableBytes(){
6
+ return (bytes) => redeableBytes(bytes)
7
+ }
8
+ }
9
+ }
@@ -0,0 +1,108 @@
1
+ <template>
2
+
3
+ <crud-show v-if="uploadedFile" :open="open" :title="title" @close="$emit('close')">
4
+ <file-view v-if="uploadedFile" :file="uploadedFile"/>
5
+ </crud-show>
6
+
7
+ <crud-create
8
+ v-else
9
+ :open="open"
10
+ :loading="loading"
11
+ :title="title"
12
+ :error-message="errorMessage"
13
+ @close="$emit('close')"
14
+ @create="onCreate"
15
+ :fullscreen="false"
16
+ >
17
+
18
+ <div class="text-center">
19
+
20
+ <file-form
21
+ v-model="form"
22
+ :input-errors="inputErrors"
23
+ ref="formRef"
24
+ @fileSelected="onFileSelected"
25
+ @save="onCreate"
26
+ creating
27
+ />
28
+
29
+ </div>
30
+
31
+ </crud-create>
32
+
33
+
34
+ </template>
35
+
36
+ <script setup>
37
+ import { ref } from 'vue'
38
+ import { useI18n } from 'vue-i18n'
39
+ import {CrudCreate, CrudShow, ClientError} from "@testdracul/common-frontend";
40
+ import FileView from "../../../components/FileView/FileView";
41
+ import FileForm from "../FileForm";
42
+ import uploadProvider from "../../../providers/UploadProvider";
43
+
44
+ const props = defineProps({
45
+ open: {type: Boolean, default: true},
46
+ })
47
+
48
+ const emit = defineEmits(['close', 'itemCreated'])
49
+
50
+ const { t } = useI18n()
51
+
52
+ const title = ref('media.file.creating')
53
+ const errorMessage = ref('')
54
+ const inputErrors = ref({})
55
+ const loading = ref(false)
56
+ const form = ref({
57
+ file: null,
58
+ expirationDate: null,
59
+ isPublic: false,
60
+ description: '',
61
+ tags: [],
62
+ groups: [],
63
+ users: []
64
+ })
65
+ const uploadedFile = ref(null)
66
+ const formRef = ref(null)
67
+
68
+ const onFileSelected = (file) => {
69
+ form.value.file = file
70
+ }
71
+
72
+ const onCreate = async () => {
73
+ if (form.value.file) {
74
+ loading.value = true;
75
+ errorMessage.value = '';
76
+
77
+ await uploadProvider.uploadFile(
78
+ form.value.file,
79
+ form.value.expirationDate,
80
+ form.value.isPublic,
81
+ form.value.description,
82
+ form.value.tags,
83
+ form.value.groups,
84
+ form.value.users)
85
+ .then(result => {
86
+ uploadedFile.value = result.data.fileUpload
87
+ emit('itemCreated')
88
+ })
89
+ .catch((error) => {
90
+ const expirationDateError = error.message.includes('Expiration date must be older than current date') ? true : false;
91
+ if(expirationDateError) {
92
+ errorMessage.value = t("media.file.wrongExpirationDate")
93
+ } else {
94
+ let clientError = new ClientError(error)
95
+ inputErrors.value = clientError.inputErrors
96
+ errorMessage.value = clientError.i18nMessage
97
+ }
98
+ })
99
+ .finally(() => loading.value = false)
100
+ } else {
101
+ errorMessage.value = t("media.file.noFile")
102
+ }
103
+ }
104
+ </script>
105
+
106
+ <style scoped>
107
+
108
+ </style>
@@ -0,0 +1,3 @@
1
+ import FileCreate from './FileCreate.vue'
2
+ export {FileCreate}
3
+ export default FileCreate
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <crud-layout
3
+ :title="title"
4
+ :subtitle="subtitle"
5
+ :add-button="topAddButton && hasPermission('FILE_CREATE')"
6
+ @add="create"
7
+ >
8
+
9
+ <template v-slot:list>
10
+ <file-list
11
+ ref="listRef"
12
+ @update="update"
13
+ @delete="remove"
14
+ @show="show"
15
+ @item-updated="onItemUpdated"
16
+ />
17
+ </template>
18
+
19
+ <add-button
20
+ v-if="!topAddButton && hasPermission('FILE_CREATE')"
21
+ @click="create"
22
+ />
23
+
24
+ <file-create v-if="creating"
25
+ :open="creating"
26
+ @item-created="onItemCreated"
27
+ @close="creating=false"
28
+ />
29
+
30
+ <file-update v-if="updating"
31
+ :open="updating"
32
+ :item="itemToEdit"
33
+ @item-updated="onItemUpdated"
34
+ @close="updating=false"
35
+ />
36
+
37
+ <file-show v-if="showing"
38
+ :open="showing"
39
+ :item="itemToShow"
40
+ @close="showing=false"
41
+ />
42
+
43
+ <file-delete v-if="deleting"
44
+ :open="deleting"
45
+ :item="itemToDelete"
46
+ @item-deleted="onItemDeleted"
47
+ @close="deleting=false"
48
+ />
49
+
50
+ <snackbar v-model="flash"/>
51
+
52
+ </crud-layout>
53
+ </template>
54
+
55
+ <script setup>
56
+ import { ref, computed, onBeforeMount } from 'vue'
57
+ import { useStore } from 'vuex'
58
+ import { useI18n } from 'vue-i18n'
59
+ import FileUpdate from "../FileUpdate";
60
+ import FileDelete from "../FileDelete";
61
+ import FileShow from "../FileShow";
62
+ import FileList from "../FileList";
63
+
64
+ import {CrudLayout, AddButton, Snackbar} from "@testdracul/common-frontend"
65
+ import FileCreate from "../FileCreate/FileCreate";
66
+
67
+ const props = defineProps({
68
+ topAddButton: Boolean
69
+ })
70
+
71
+ const emit = defineEmits([])
72
+
73
+ const { t } = useI18n()
74
+ const store = useStore()
75
+
76
+ const title = ref('media.file.title')
77
+ const subtitle = ref('media.file.subtitle')
78
+ const flash = ref(null)
79
+ const creating = ref(false)
80
+ const updating = ref(false)
81
+ const deleting = ref(false)
82
+ const showing = ref(false)
83
+ const itemToEdit = ref(null)
84
+ const itemToDelete = ref(null)
85
+ const itemToShow = ref(null)
86
+ const timeoutSnackbar = ref(10000)
87
+ const listRef = ref(null)
88
+
89
+ const hasPermission = (permission) => store.getters.hasPermission(permission)
90
+ const getRole = computed(() => store.getters.getRole)
91
+
92
+ onBeforeMount(() => {
93
+ if(getRole.value === "visualizer"){
94
+ subtitle.value = 'media.file.visualizerSubtitle'
95
+ }
96
+ })
97
+
98
+ const onItemCreated = () => {
99
+ listRef.value?.fetch()
100
+ flash.value = t("common.created")
101
+ }
102
+
103
+ const onItemUpdated = () => {
104
+ listRef.value?.fetch()
105
+ flash.value = t("common.updated")
106
+ }
107
+
108
+ const onItemDeleted = () => {
109
+ listRef.value?.fetch()
110
+ flash.value = t("common.deleted")
111
+ }
112
+
113
+ const create = () => {
114
+ creating.value = true
115
+ }
116
+
117
+ const update = (item) => {
118
+ updating.value = true
119
+ itemToEdit.value = item
120
+ }
121
+
122
+ const show = (item) => {
123
+ showing.value = true
124
+ itemToShow.value = item
125
+ }
126
+
127
+ const remove = (item) => {
128
+ deleting.value = true
129
+ itemToDelete.value = item
130
+ }
131
+ </script>
132
+
133
+
@@ -0,0 +1,4 @@
1
+ import FileCrud from './FileCrud'
2
+
3
+ export {FileCrud}
4
+ export default FileCrud
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <crud-delete :open="open"
3
+ :loading="loading"
4
+ :title="title"
5
+ :error-message="errorMessage"
6
+ @delete="remove"
7
+ @close="$emit('close')"
8
+ >
9
+
10
+ <v-card-text>
11
+ <file-show-data :item="item" />
12
+ </v-card-text>
13
+
14
+ <v-card-text>
15
+ <v-row justify="center">
16
+ <span class="title">{{areYouSure}}</span>
17
+ </v-row>
18
+ </v-card-text>
19
+
20
+ </crud-delete>
21
+ </template>
22
+
23
+ <script setup>
24
+ import { ref } from 'vue'
25
+ import { useI18n } from 'vue-i18n'
26
+ import FileProvider from "../../../providers/FileProvider";
27
+ import FileShowData from "../FileShow/FileShowData";
28
+ import {CrudDelete, ClientError} from '@testdracul/common-frontend'
29
+
30
+ const props = defineProps({
31
+ open: {type: Boolean, default: true},
32
+ item: {type: Object, required: true}
33
+ })
34
+
35
+ const emit = defineEmits(['close', 'itemDeleted'])
36
+
37
+ const { t } = useI18n()
38
+
39
+ const modal = ref(false)
40
+ const title = ref('media.file.deleting')
41
+ const areYouSure = ref(t('common.areYouSureDeleteRecord'))
42
+ const errorMessage = ref('')
43
+ const loading = ref(false)
44
+
45
+ const remove = () => {
46
+ loading.value = true
47
+ FileProvider.deleteFile(props.item.id).then(result => {
48
+ if (result.data.fileDelete.success) {
49
+ emit('itemDeleted', result.data.fileDelete)
50
+ emit('close')
51
+ } else {
52
+ errorMessage.value = 'Error on Delete'
53
+ }
54
+ }).catch(error => {
55
+ let clientError = new ClientError(error)
56
+ errorMessage.value = clientError.showMessage
57
+ }).finally(() => loading.value = false)
58
+ }
59
+ </script>
60
+
61
+
@@ -0,0 +1,3 @@
1
+ import FileDelete from './FileDelete.vue'
2
+ export {FileDelete}
3
+ export default FileDelete
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <filter-layout :title="'media.file.filters'" @clear="cleanFilters()" @apply="setFilters()" apply-button>
3
+ <v-col cols="12" sm="6" md="4">
4
+ <date-input
5
+ v-if="filters[0].field == 'dateFrom'"
6
+ v-model="filters[0].value"
7
+ :label="$t('media.file.from')"
8
+ prepend-inner-icon="mdi-calendar"
9
+ color="secondary"
10
+ hide-details
11
+ variant="underlined"
12
+ dense
13
+ />
14
+ </v-col>
15
+ <v-col cols="12" sm="6" md="4">
16
+ <date-input
17
+ v-model="filters[1].value"
18
+ :label="$t('media.file.until')"
19
+ prepend-inner-icon="mdi-calendar"
20
+ color="secondary"
21
+ hide-details
22
+ variant="underlined"
23
+ dense
24
+ />
25
+ </v-col>
26
+ <v-col cols="12" md="4" sm="6">
27
+ <v-text-field
28
+ v-model="filters[2].value"
29
+ :label="$t('media.file.filename')"
30
+ prepend-inner-icon="mdi-text"
31
+ color="secondary"
32
+ hide-details
33
+ variant="underlined"
34
+ density="compact"
35
+ />
36
+ </v-col>
37
+ <v-col cols="12" sm="6" md="3">
38
+ <v-select
39
+ v-model="filters[4].value"
40
+ :label="$t('media.file.type')"
41
+ :items="selectType"
42
+ prepend-inner-icon="mdi-file"
43
+ color="secondary"
44
+ variant="underlined"
45
+ density="compact"
46
+ hide-details
47
+ />
48
+ </v-col>
49
+ <v-col v-if="isUserAuthorized('FILE_SHOW_OWN')" cols="12" sm="6" md="3">
50
+ <v-select
51
+ prepend-inner-icon="mdi-eye"
52
+ v-model="filters[7].value"
53
+ :items="[{title: 'Público', value: 'true'}, {title: 'Privado', value: 'false'}]"
54
+ :label="$t('media.file.visibility')"
55
+ variant="underlined"
56
+ density="compact"
57
+ hide-details
58
+ ></v-select>
59
+ </v-col>
60
+ <v-col cols="12" sm="6" md="3">
61
+ <v-text-field
62
+ v-model="filters[5].value"
63
+ :label="$t('media.file.sizeGt') + ' (MB)'"
64
+ prepend-inner-icon="mdi-arrow-up-bold-circle-outline"
65
+ color="secondary"
66
+ variant="underlined"
67
+ density="compact"
68
+ hide-details
69
+ />
70
+ </v-col>
71
+ <v-col cols="12" sm="6" md="3">
72
+ <v-text-field
73
+ v-model="filters[6].value"
74
+ :label="$t('media.file.sizeLt') + ' (MB)'"
75
+ prepend-inner-icon="mdi-arrow-down-bold-circle-outline"
76
+ color="secondary"
77
+ variant="underlined"
78
+ density="compact"
79
+ hide-details
80
+ />
81
+ </v-col>
82
+ <v-col v-if="isUserAuthorized('FILE_SHOW_ALL')" cols="12" md="4" sm="6">
83
+ <user-autocomplete
84
+ v-model="filters[3].value"
85
+ label="media.file.createdBy"
86
+ >
87
+ </user-autocomplete>
88
+ </v-col>
89
+ <v-col v-if="isUserAuthorized('FILE_SHOW_ALL')" cols="12" md="4" sm="6">
90
+ <group-autocomplete
91
+ v-model="filters[8].value"
92
+ label="media.file.group"
93
+ >
94
+ </group-autocomplete>
95
+ </v-col>
96
+ <v-col v-if="isUserAuthorized('FILE_SHOW_ALL')" cols="12" md="4" sm="6">
97
+ <user-autocomplete
98
+ v-model="filters[9].value"
99
+ label="media.file.user"
100
+ >
101
+ </user-autocomplete>
102
+ </v-col>
103
+ </filter-layout>
104
+ </template>
105
+
106
+ <script setup>
107
+ import {computed} from 'vue'
108
+ import {useStore} from 'vuex'
109
+ import {DateInput} from '@testdracul/dayjs-frontend';
110
+ import {GroupAutocomplete, UserAutocomplete} from '@testdracul/user-frontend'
111
+ import {FilterLayout} from '@testdracul/common-frontend'
112
+
113
+ const props = defineProps({
114
+ modelValue: Array
115
+ })
116
+
117
+ const emit = defineEmits(['updateFilters', 'clearFilter', 'update:modelValue'])
118
+
119
+ const store = useStore()
120
+
121
+ const filters = computed({
122
+ get() {
123
+ return props.modelValue
124
+ },
125
+ set(val) {
126
+ emit('update:modelValue', val)
127
+ }
128
+ })
129
+
130
+ const me = computed(() => store.getters.me)
131
+
132
+ const selectType = [
133
+ {title: "text", value: "text"},
134
+ {title: "image", value: "image"},
135
+ {title: "application", value: "application"},
136
+ {title: "audio", value: "audio"}
137
+ ]
138
+
139
+ const setFilters = () => {
140
+ emit("updateFilters", filters.value);
141
+ }
142
+
143
+ const cleanFilters = () => {
144
+ emit("clearFilter", filters.value);
145
+ }
146
+
147
+ const isUserAuthorized = (permission) => {
148
+ return store.getters.hasPermission(permission)
149
+ }
150
+ </script>
@@ -0,0 +1,3 @@
1
+ import FileFilters from './FileFilters'
2
+ export {FileFilters}
3
+ export default FileFilters
@@ -0,0 +1,184 @@
1
+ <template>
2
+ <v-form ref="formRef" autocomplete="off" @submit.prevent="$emit('save')">
3
+ <v-row class="mt-2">
4
+
5
+ <v-col cols="12" md="6" sm="12">
6
+ <v-card variant="outlined" color="secondary" class="pa-2">
7
+ <date-time-input
8
+ v-model="form.expirationDate"
9
+ :label="$t('media.file.expirationDate')"
10
+ prepend-inner-icon="mdi-calendar-clock"
11
+ persistent-hint
12
+ color="secondary"
13
+ variant="filled"
14
+ density="comfortable"
15
+ :rules="fileExpirationTimeRules"
16
+ />
17
+ <div class="text-caption mt-1 px-2 text-grey-darken-1">
18
+ La fecha y hora en que el archivo será eliminado automáticamente.
19
+ </div>
20
+ </v-card>
21
+ </v-col>
22
+
23
+ <v-col cols="12" md="6" sm="12">
24
+ <v-select
25
+ prepend-inner-icon="mdi-eye"
26
+ v-model="form.isPublic"
27
+ :items="[{title: 'Público', value: true}, {title: 'Privado', value: false}]"
28
+ :label="$t('media.file.visibility')"
29
+ variant="underlined"
30
+ density="compact"
31
+ ></v-select>
32
+ </v-col>
33
+
34
+
35
+ <v-col cols="12" sm="12" md="12">
36
+ <v-combobox
37
+ prepend-inner-icon="mdi-tag-multiple"
38
+ v-model="form.tags"
39
+ :label="$t('media.file.tags')"
40
+ chips
41
+ multiple
42
+ color="secondary"
43
+ variant="underlined"
44
+ density="compact"
45
+ ></v-combobox>
46
+ </v-col>
47
+
48
+ <v-col cols="12" sm="12">
49
+ <v-text-field
50
+ prepend-inner-icon="mdi-text-box"
51
+ name="filename"
52
+ v-model="form.description"
53
+ :label="$t('media.file.description')"
54
+ :placeholder="$t('media.file.description')"
55
+ color="secondary"
56
+ variant="underlined"
57
+ density="compact"
58
+ ></v-text-field>
59
+ </v-col>
60
+ <v-col cols="12">
61
+ <group-autocomplete
62
+ v-model="form.groups"
63
+ multiple
64
+ label="media.file.groups"
65
+ ></group-autocomplete>
66
+ </v-col>
67
+
68
+ <v-col cols="12">
69
+ <user-autocomplete
70
+ v-model="form.users"
71
+ multiple
72
+ label="media.file.users"
73
+ ></user-autocomplete>
74
+ </v-col>
75
+
76
+ <v-col v-if="creating || updating" cols="12" align-self="center" class="text-center mt-4">
77
+ <v-divider class="mb-4"></v-divider>
78
+ <file-upload-button
79
+ :updating="updating"
80
+ @file-selected="onFileSelected"
81
+ :max-file-size="maxFileSize"
82
+ :loading="loading"
83
+ :old-file-extension="oldFileExtension"
84
+ ></file-upload-button>
85
+ </v-col>
86
+ </v-row>
87
+ </v-form>
88
+ </template>
89
+
90
+ <script setup>
91
+ import { ref, computed, onMounted, watch } from 'vue'
92
+ import { useI18n } from 'vue-i18n'
93
+ import { DateTimeInput } from '@testdracul/dayjs-frontend';
94
+ import UserStorageProvider from "../../../providers/UserStorageProvider"
95
+ import FileUploadButton from "../../../components/FileUploadButton";
96
+ import { GroupAutocomplete, UserAutocomplete } from '@testdracul/user-frontend'
97
+ import dayjs from 'dayjs'
98
+
99
+ const props = defineProps({
100
+ modelValue: {type: Object, required: true},
101
+ creating: {type: Boolean, default: false},
102
+ updating: {type: Boolean, default: false},
103
+ loading: {type: Boolean, default: false},
104
+ oldFileExtension: {type: String},
105
+ })
106
+
107
+ const emit = defineEmits(['update:modelValue', 'fileSelected', 'save'])
108
+
109
+ const { t } = useI18n()
110
+
111
+ const maxFileSize = ref(null)
112
+ const fileExpirationTime = ref(null)
113
+ const formRef = ref(null)
114
+
115
+ const form = computed({
116
+ get() {
117
+ return props.modelValue
118
+ },
119
+ set(val) {
120
+ emit('update:modelValue', val)
121
+ }
122
+ })
123
+
124
+ // Lógica de validación robusta para el error de RangeError
125
+ const differenceInDays = computed(() => {
126
+ if(form.value.expirationDate){
127
+ const today = dayjs();
128
+ const expirationDate = dayjs(form.value.expirationDate);
129
+ if (!expirationDate.isValid()) return null;
130
+ return expirationDate.diff(today, 'day');
131
+ }
132
+ return null
133
+ })
134
+
135
+ const fileExpirationTimeRules = computed(() => [
136
+ () => {
137
+ if (differenceInDays.value === null) return true;
138
+
139
+ if (differenceInDays.value < 0) {
140
+ return t("media.userStorage.fileExpirationTimeOlderThanToday")
141
+ } else if (fileExpirationTime.value && (differenceInDays.value !== null)) {
142
+ return (differenceInDays.value <= fileExpirationTime.value)
143
+ || `${t("media.userStorage.fileExpirationLimitExceeded")} ${fileExpirationTime.value} ${t("media.file.days")}`
144
+ } else {
145
+ return true
146
+ }
147
+ }
148
+ ])
149
+
150
+ const validate = async () => {
151
+ if (formRef.value) {
152
+ const { valid } = await formRef.value.validate()
153
+ return valid
154
+ }
155
+ return false
156
+ }
157
+
158
+ const onFileSelected = (file) => {
159
+ emit('fileSelected', file)
160
+ }
161
+
162
+ const findUserStorage = () => {
163
+ return UserStorageProvider.findUserStorageByUser().then((res) => {
164
+ if (res.data.userStorageFindByUser && res.data.userStorageFindByUser.maxFileSize) {
165
+ maxFileSize.value = res.data.userStorageFindByUser.maxFileSize;
166
+ fileExpirationTime.value = res.data.userStorageFindByUser.fileExpirationTime;
167
+ }
168
+ }).catch(err => console.error(err))
169
+ }
170
+
171
+ onMounted(() => {
172
+ findUserStorage()
173
+ })
174
+
175
+ defineExpose({ validate })
176
+ </script>
177
+
178
+ <style scoped>
179
+ /* Espaciado refinado para el formulario */
180
+ .v-row {
181
+ margin-left: -8px;
182
+ margin-right: -8px;
183
+ }
184
+ </style>