TimeAccountingFlyout.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed, reactive } from 'vue'
  4. import Form from '#shared/components/Form/Form.vue'
  5. import type {
  6. FormSchemaNode,
  7. FormSubmitData,
  8. } from '#shared/components/Form/types.ts'
  9. import { useForm } from '#shared/components/Form/useForm.ts'
  10. import type { TicketArticleTimeAccountingFormData } from '#shared/entities/ticket/types.ts'
  11. import { EnumFormUpdaterId } from '#shared/graphql/types.ts'
  12. import { i18n } from '#shared/i18n.ts'
  13. import { useApplicationStore } from '#shared/stores/application.ts'
  14. import CommonFlyout from '#desktop/components/CommonFlyout/CommonFlyout.vue'
  15. import type { ActionFooterOptions } from '#desktop/components/CommonFlyout/types.ts'
  16. import { closeFlyout } from '#desktop/components/CommonFlyout/useFlyout.ts'
  17. import type { FormKitNode } from '@formkit/core'
  18. const emit = defineEmits<{
  19. 'account-time': [TicketArticleTimeAccountingFormData]
  20. skip: []
  21. }>()
  22. const { form } = useForm()
  23. const flyoutName = 'ticket-time-accounting'
  24. const submitForm = (
  25. formData: FormSubmitData<TicketArticleTimeAccountingFormData>,
  26. ) => {
  27. if (formData.time_unit) {
  28. formData.time_unit = formData.time_unit.replace(',', '.')
  29. }
  30. emit('account-time', formData)
  31. closeFlyout(flyoutName)
  32. }
  33. const onClose = (isCancel?: boolean) => {
  34. if (!isCancel) return
  35. emit('skip')
  36. }
  37. const { config } = useApplicationStore()
  38. const timeAccountingUnit = computed(() => {
  39. switch (config.time_accounting_unit) {
  40. case 'hour':
  41. return __('hour(s)')
  42. case 'quarter':
  43. return __('quarter-hour(s)')
  44. case 'minute':
  45. return __('minute(s)')
  46. case 'custom': {
  47. if (config.time_accounting_unit_custom)
  48. return config.time_accounting_unit_custom
  49. return null
  50. }
  51. default:
  52. return null
  53. }
  54. })
  55. const validationRuleTimeAccountingUnit = (node: FormKitNode<string>) => {
  56. if (!node.value) return false
  57. return !Number.isNaN(+node.value.replace(',', '.'))
  58. }
  59. const formSchema = [
  60. {
  61. isLayout: true,
  62. component: 'FormGroup',
  63. props: {
  64. class: '@container/form-group',
  65. },
  66. children: [
  67. {
  68. id: 'timeUnit',
  69. name: 'time_unit',
  70. label: __('Accounted Time'),
  71. type: 'text',
  72. required: true,
  73. placeholder: __('Enter the time you want to record'),
  74. validation: 'validationRuleTimeAccountingUnit',
  75. validationRules: {
  76. validationRuleTimeAccountingUnit,
  77. },
  78. validationMessages: {
  79. validationRuleTimeAccountingUnit: __(
  80. 'This field must contain a number.',
  81. ),
  82. },
  83. ...(timeAccountingUnit.value
  84. ? {
  85. sectionsSchema: {
  86. suffix: {
  87. // FIXME: Not working.
  88. // if: '$timeAccountingUnit',
  89. // children: '$timeAccountingUnit',
  90. $el: 'span',
  91. children: i18n.t(timeAccountingUnit.value || ''),
  92. attrs: {
  93. class:
  94. 'py-2.5 px-2.5 outline outline-1 -outline-offset-1 outline-blue-200 dark:outline-gray-700 bg-neutral-50 dark:bg-gray-500 rounded-e-md text-gray-100 dark:text-neutral-400',
  95. },
  96. },
  97. },
  98. }
  99. : {}),
  100. },
  101. {
  102. if: '$timeAccountingTypes === true',
  103. id: 'accountedTimeTypeId',
  104. name: 'accounted_time_type_id',
  105. label: __('Activity Type'),
  106. type: 'select',
  107. props: {
  108. clearable: true,
  109. },
  110. },
  111. ],
  112. },
  113. ] as FormSchemaNode[]
  114. const timeAccountingTypes = computed(() => config.time_accounting_types)
  115. const schemaData = reactive({
  116. // timeAccountingUnit,
  117. timeAccountingTypes,
  118. })
  119. const footerActionOptions = computed<ActionFooterOptions>(() => ({
  120. actionLabel: __('Account Time'),
  121. actionButton: { variant: 'submit', type: 'submit' },
  122. cancelLabel: __('Skip'),
  123. form: form.value,
  124. }))
  125. </script>
  126. <template>
  127. <CommonFlyout
  128. :header-title="__('Time Accounting')"
  129. :footer-action-options="footerActionOptions"
  130. header-icon="stopwatch"
  131. :name="flyoutName"
  132. no-close-on-action
  133. @close="onClose"
  134. >
  135. <div class="flex flex-col gap-3">
  136. <Form
  137. id="form-ticket-time-accounting"
  138. ref="form"
  139. :schema="formSchema"
  140. :schema-data="schemaData"
  141. should-autofocus
  142. :form-updater-id="
  143. EnumFormUpdaterId.FormUpdaterUpdaterTicketTimeAccounting
  144. "
  145. form-updater-initial-only
  146. @submit="
  147. submitForm(
  148. $event as FormSubmitData<TicketArticleTimeAccountingFormData>,
  149. )
  150. "
  151. />
  152. </div>
  153. </CommonFlyout>
  154. </template>