mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	 ca579bd62a
			
		
	
	ca579bd62a
	
	
	
		
			
			# Pull Request Template ## Description Fixes https://linear.app/chatwoot/issue/CW-5573/feat-createedit-agent-capacity-policy-page ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/8de9e3c5d8824cd998d242636540dd18?sid=1314536f-c8d6-41fd-8139-cae9bf94f942 ### Screenshots **Light mode** <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/7e6d83a4-ce02-47a7-91f6-87745f8f5549" /> <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/7dd1f840-2e25-4365-aa1d-ed9dac13385a" /> **Dark mode** <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/0c787095-7146-4fb3-a61a-e2232973bcba" /> <img width="1666" height="1225" alt="image" src="https://github.com/user-attachments/assets/481c21fd-03b5-4c1f-b59e-7f8c8017f9ce" /> ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
		
			
				
	
	
		
			164 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			164 lines
		
	
	
		
			4.4 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
| <script setup>
 | |
| import { computed } from 'vue';
 | |
| import { useI18n } from 'vue-i18n';
 | |
| import Button from 'dashboard/components-next/button/Button.vue';
 | |
| import Spinner from 'dashboard/components-next/spinner/Spinner.vue';
 | |
| import AddDataDropdown from 'dashboard/components-next/AssignmentPolicy/components/AddDataDropdown.vue';
 | |
| 
 | |
| const props = defineProps({
 | |
|   inboxList: {
 | |
|     type: Array,
 | |
|     default: () => [],
 | |
|   },
 | |
|   isFetching: {
 | |
|     type: Boolean,
 | |
|     default: false,
 | |
|   },
 | |
| });
 | |
| 
 | |
| const emit = defineEmits(['delete', 'add', 'update']);
 | |
| 
 | |
| const inboxCapacityLimits = defineModel('inboxCapacityLimits', {
 | |
|   type: Array,
 | |
|   default: () => [],
 | |
| });
 | |
| 
 | |
| const { t } = useI18n();
 | |
| 
 | |
| const BASE_KEY = 'ASSIGNMENT_POLICY.AGENT_CAPACITY_POLICY';
 | |
| const DEFAULT_CONVERSATION_LIMIT = 10;
 | |
| const MIN_CONVERSATION_LIMIT = 1;
 | |
| const MAX_CONVERSATION_LIMIT = 100000;
 | |
| 
 | |
| const selectedInboxIds = computed(
 | |
|   () => new Set(inboxCapacityLimits.value.map(limit => limit.inboxId))
 | |
| );
 | |
| 
 | |
| const availableInboxes = computed(() =>
 | |
|   props.inboxList.filter(
 | |
|     inbox => inbox && !selectedInboxIds.value.has(inbox.id)
 | |
|   )
 | |
| );
 | |
| 
 | |
| const isLimitValid = limit => {
 | |
|   return (
 | |
|     limit.conversationLimit >= MIN_CONVERSATION_LIMIT &&
 | |
|     limit.conversationLimit <= MAX_CONVERSATION_LIMIT
 | |
|   );
 | |
| };
 | |
| 
 | |
| const inboxMap = computed(
 | |
|   () => new Map(props.inboxList.map(inbox => [inbox.id, inbox]))
 | |
| );
 | |
| 
 | |
| const handleAddInbox = inbox => {
 | |
|   emit('add', {
 | |
|     inboxId: inbox.id,
 | |
|     conversationLimit: DEFAULT_CONVERSATION_LIMIT,
 | |
|   });
 | |
| };
 | |
| 
 | |
| const handleRemoveLimit = limitId => {
 | |
|   emit('delete', limitId);
 | |
| };
 | |
| 
 | |
| const handleLimitChange = limit => {
 | |
|   if (isLimitValid(limit)) {
 | |
|     emit('update', limit);
 | |
|   }
 | |
| };
 | |
| 
 | |
| const getInboxName = inboxId => {
 | |
|   return inboxMap.value.get(inboxId)?.name || '';
 | |
| };
 | |
| </script>
 | |
| 
 | |
| <template>
 | |
|   <div class="py-4 flex-col flex gap-3">
 | |
|     <div class="flex items-center w-full gap-8 justify-between pt-1 pb-3">
 | |
|       <label class="text-sm font-medium text-n-slate-12">
 | |
|         {{ t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.LABEL`) }}
 | |
|       </label>
 | |
| 
 | |
|       <AddDataDropdown
 | |
|         :label="t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.ADD_BUTTON`)"
 | |
|         :search-placeholder="
 | |
|           t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.SELECT_INBOX`)
 | |
|         "
 | |
|         :items="availableInboxes"
 | |
|         @add="handleAddInbox"
 | |
|       />
 | |
|     </div>
 | |
| 
 | |
|     <div
 | |
|       v-if="isFetching"
 | |
|       class="flex items-center justify-center py-3 w-full text-n-slate-11"
 | |
|     >
 | |
|       <Spinner />
 | |
|     </div>
 | |
| 
 | |
|     <div
 | |
|       v-else-if="!inboxCapacityLimits.length"
 | |
|       class="custom-dashed-border flex items-center justify-center py-6 w-full"
 | |
|     >
 | |
|       <span class="text-sm text-n-slate-11">
 | |
|         {{ t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.EMPTY_STATE`) }}
 | |
|       </span>
 | |
|     </div>
 | |
| 
 | |
|     <div v-else class="flex-col flex gap-3">
 | |
|       <div
 | |
|         v-for="(limit, index) in inboxCapacityLimits"
 | |
|         :key="limit.id || `temp-${index}`"
 | |
|         class="flex items-center gap-3"
 | |
|       >
 | |
|         <div
 | |
|           class="flex items-start rounded-lg outline-1 outline cursor-not-allowed text-n-slate-11 outline-n-weak py-2.5 px-3 text-sm w-full"
 | |
|         >
 | |
|           {{ getInboxName(limit.inboxId) }}
 | |
|         </div>
 | |
| 
 | |
|         <div
 | |
|           class="py-2.5 px-3 rounded-lg gap-2 outline outline-1 flex-shrink-0 flex items-center"
 | |
|           :class="[
 | |
|             !isLimitValid(limit) ? 'outline-n-ruby-8' : 'outline-n-weak',
 | |
|           ]"
 | |
|         >
 | |
|           <label class="text-sm text-n-slate-12 ltr:pr-2 rtl:pl-2">
 | |
|             {{
 | |
|               t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.MAX_CONVERSATIONS`)
 | |
|             }}
 | |
|           </label>
 | |
| 
 | |
|           <div class="h-5 w-px bg-n-weak" />
 | |
| 
 | |
|           <input
 | |
|             v-model.number="limit.conversationLimit"
 | |
|             type="number"
 | |
|             :min="MIN_CONVERSATION_LIMIT"
 | |
|             :max="MAX_CONVERSATION_LIMIT"
 | |
|             class="reset-base bg-transparent focus:outline-none max-w-20 text-sm"
 | |
|             :class="[
 | |
|               !isLimitValid(limit)
 | |
|                 ? 'placeholder:text-n-ruby-9 !text-n-ruby-9'
 | |
|                 : 'placeholder:text-n-slate-10 text-n-slate-12',
 | |
|             ]"
 | |
|             :placeholder="
 | |
|               t(`${BASE_KEY}.FORM.INBOX_CAPACITY_LIMIT.FIELD.SET_LIMIT`)
 | |
|             "
 | |
|             @blur="handleLimitChange(limit)"
 | |
|           />
 | |
|         </div>
 | |
| 
 | |
|         <Button
 | |
|           type="button"
 | |
|           slate
 | |
|           icon="i-lucide-trash"
 | |
|           class="flex-shrink-0"
 | |
|           @click="handleRemoveLimit(limit.id)"
 | |
|         />
 | |
|       </div>
 | |
|     </div>
 | |
|   </div>
 | |
| </template>
 |