@riosst100/pwa-marketplace 2.9.6 → 2.9.7

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@riosst100/pwa-marketplace",
3
3
  "author": "riosst100@gmail.com",
4
- "version": "2.9.6",
4
+ "version": "2.9.7",
5
5
  "main": "src/index.js",
6
6
  "pwa-studio": {
7
7
  "targets": {
@@ -1,26 +1,42 @@
1
- import React from 'react';
1
+ import React, { useRef, useEffect } from 'react';
2
2
  import cn from 'classnames';
3
3
 
4
4
  const chatContent = (props) => {
5
5
  const { chatData } = props;
6
6
 
7
- return (
8
- <>
9
- <div className='flex flex-col gap-2 w-full'>
10
- {chatData.map((chat) => {
11
- return (
7
+
8
+ const orderedChatData = [...chatData].reverse();
9
+ const messagesContainerRef = useRef(null);
10
+
11
+ useEffect(() => {
12
+ if (messagesContainerRef.current) {
13
+ messagesContainerRef.current.scrollTop = messagesContainerRef.current.scrollHeight;
14
+ }
15
+ }, [orderedChatData]);
16
+
17
+ return (
18
+ <>
19
+ <div
20
+ ref={messagesContainerRef}
21
+ className='flex flex-col gap-2 w-full overflow-y-auto max-h-[400px]'
22
+ >
23
+ {orderedChatData.map((chat, idx) => (
12
24
  <div
25
+ key={idx}
13
26
  className={cn(
14
27
  'w-fit max-w-[70%] flex flex-col gap-1',
15
28
  chat.type === 'seller' ? 'self-start ' : 'self-end items-end',
16
-
17
29
  )}
18
30
  >
31
+ {chat.senderName && (
32
+ <span className='text-[12px] text-gray-500 font-semibold mb-[-2px]'>
33
+ {chat.senderName}
34
+ </span>
35
+ )}
19
36
  <div
20
37
  className={cn(
21
38
  'w-fit py-2 px-3 rounded-md flex flex-col',
22
39
  chat.type === 'seller' ? 'bg-gray-150 rounded-tl-none' : ' bg-[#D9D9D9] rounded-tr-none',
23
-
24
40
  )}
25
41
  >
26
42
  {chat.message}
@@ -29,11 +45,10 @@ const chatContent = (props) => {
29
45
  {chat.timeStamp}
30
46
  </span>
31
47
  </div>
32
- )
33
- })}
34
- </div>
35
- </>
36
- )
48
+ ))}
49
+ </div>
50
+ </>
51
+ )
37
52
  }
38
53
 
39
54
  export default chatContent
@@ -3,11 +3,11 @@ import { useIntl } from 'react-intl';
3
3
  import { StoreTitle } from '@magento/venia-ui/lib/components/Head';
4
4
  import Button from '@magento/venia-ui/lib/components/Button';
5
5
  import cn from 'classnames';
6
- import { Printer } from 'iconsax-react';
6
+ import { Printer, ConvertCard } from 'iconsax-react';
7
7
  import ItemsOrdered from './components/itemsOrdered';
8
8
  import RMAList from './components/rmaList';
9
9
  import useRmaPage from '@riosst100/pwa-marketplace/src/talons/RMAPage/useRmaPage';
10
- import { useLocation, useParams } from 'react-router-dom';
10
+ import { useLocation, useParams, useHistory } from 'react-router-dom';
11
11
  import useOrderHistoryPage from '@riosst100/pwa-marketplace/src/talons/OrderHistoryPage/useOrderHistoryPage';
12
12
 
13
13
  const OrderDetail = (props) => {
@@ -27,6 +27,45 @@ const OrderDetail = (props) => {
27
27
 
28
28
  // Keep a stable local state for order to avoid it turning undefined on re-renders
29
29
  const [order, setOrder] = useState(() => props.order || location.state?.order || null);
30
+ const history = useHistory();
31
+ // Button Return Items: tampil jika status Complete/Delivered
32
+ const isReturnAllowed = useMemo(() => {
33
+ if (!order) return false;
34
+ const status = order.status;
35
+ if (!status) return false;
36
+ return (
37
+ status === 'Complete' ||
38
+ status.toLowerCase() === 'delivered' // antisipasi backend bisa lowercase
39
+ );
40
+ }, [order]);
41
+
42
+ // Handler sama seperti orderRow
43
+ const handleNewReturn = useCallback(() => {
44
+ if (!order) return;
45
+ let orderWithImages = { ...order };
46
+ if (orderWithImages.items && Array.isArray(orderWithImages.items)) {
47
+ orderWithImages = {
48
+ ...orderWithImages,
49
+ items: orderWithImages.items.map(it => {
50
+ let product_image = '';
51
+ if (typeof it.product_image_url === 'string' && it.product_image_url.trim() !== '') {
52
+ product_image = it.product_image_url;
53
+ }
54
+ return {
55
+ ...it,
56
+ product_image
57
+ };
58
+ })
59
+ };
60
+ }
61
+ try {
62
+ localStorage.setItem('rma_order', JSON.stringify(orderWithImages));
63
+ } catch (e) {}
64
+ history.push({
65
+ pathname: `/return/create/${order.number}`,
66
+ state: { order: orderWithImages }
67
+ });
68
+ }, [order, history]);
30
69
 
31
70
  // Fallback: fetch order by orderNumber from order history if not found
32
71
  const { orders } = useOrderHistoryPage();
@@ -149,31 +188,52 @@ const OrderDetail = (props) => {
149
188
  </div>
150
189
  </div>
151
190
  <div className='flex flex-col md_flex-row gap-x-4 justify-end no-print'>
152
- <Button
153
- priority='high'
154
- classes={{
155
- content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
156
- }}
157
- className={cn(
158
- "bg-white px-6 py-2 rounded-md text-gray-900 hover_!text-white border border-solid",
159
- "border-gray-900 focus-visible_outline-none hover_bg-gray-900 min-h-[40px]"
191
+ <div className='flex flex-row gap-x-3'>
192
+ <Button
193
+ priority='high'
194
+ classes={{
195
+ content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
196
+ }}
197
+ className={cn(
198
+ "bg-white px-6 py-2 rounded-md text-gray-900 hover_!text-white border border-solid",
199
+ "border-gray-900 focus-visible_outline-none hover_bg-gray-900 min-h-[40px]"
200
+ )}
201
+ onClick={handlePrint}
202
+ aria-label={formatMessage({ id: 'order.printInvoices', defaultMessage: 'Print Invoices' })}
203
+ >
204
+ <span className='flex gap-x-3 justify-center group-hover_!text-white'>
205
+ <Printer
206
+ size="24"
207
+ className=''
208
+ />
209
+ {
210
+ formatMessage({
211
+ id: 'order.PrintInvoices',
212
+ defaultMessage: 'Print Invoices'
213
+ })
214
+ }
215
+ </span>
216
+ </Button>
217
+ {isReturnAllowed && (
218
+ <Button
219
+ priority='high'
220
+ classes={{
221
+ content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
222
+ }}
223
+ className={cn(
224
+ "bg-blue-700 px-6 py-2 rounded-md text-white border border-blue-700 min-h-[40px]",
225
+ "transition-all duration-300 ease-in-out flex items-center gap-2"
226
+ )}
227
+ onClick={handleNewReturn}
228
+ aria-label={formatMessage({ id: 'orderRow.returnProduct', defaultMessage: 'Return Product' })}
229
+ >
230
+ <span className='flex gap-x-3 justify-center'>
231
+ <ConvertCard size={22} color="#fff" />
232
+ {formatMessage({ id: 'orderRow.ReturnItems', defaultMessage: 'Return Items' })}
233
+ </span>
234
+ </Button>
160
235
  )}
161
- onClick={handlePrint}
162
- aria-label={formatMessage({ id: 'order.printInvoices', defaultMessage: 'Print Invoices' })}
163
- >
164
- <span className='flex gap-x-3 justify-center group-hover_!text-white'>
165
- <Printer
166
- size="24"
167
- className=''
168
- />
169
- {
170
- formatMessage({
171
- id: 'order.PrintInvoices',
172
- defaultMessage: 'Print Invoices'
173
- })
174
- }
175
- </span>
176
- </Button>
236
+ </div>
177
237
  </div>
178
238
  </div>
179
239
  <div className="block block-order-details-view">
@@ -60,15 +60,11 @@ const RMACreate = () => {
60
60
  // Set default values when checked true
61
61
  let defaults = {};
62
62
  if (checked) {
63
- const cfg = rmaConfigData && rmaConfigData.lofmpRmaConfigurations;
64
- const firstReason = cfg && cfg.reasons && cfg.reasons.length > 0 ? cfg.reasons[0].id : null;
65
- const firstCondition = cfg && cfg.conditions && cfg.conditions.length > 0 ? cfg.conditions[0].id : null;
66
- const firstResolution = cfg && cfg.resolutions && cfg.resolutions.length > 0 ? cfg.resolutions[0].id : null;
67
63
  defaults = {
68
64
  qty_requested: current.qty_requested != null ? current.qty_requested : 1,
69
- reason_id: current.reason_id != null ? current.reason_id : (firstReason != null ? firstReason : null),
70
- condition_id: current.condition_id != null ? current.condition_id : (firstCondition != null ? firstCondition : null),
71
- resolution_id: current.resolution_id != null ? current.resolution_id : (firstResolution != null ? firstResolution : null)
65
+ reason_id: '',
66
+ condition_id: '',
67
+ resolution_id: ''
72
68
  };
73
69
  }
74
70
  return {
@@ -82,6 +78,20 @@ const RMACreate = () => {
82
78
  });
83
79
  };
84
80
 
81
+
82
+ // Check if at least one item is checked and all checked items have required fields
83
+ const checkedItems = Object.values(itemReturnState).filter(state => state && state.checked);
84
+ const isAllCheckedItemsValid =
85
+ checkedItems.length > 0 &&
86
+ checkedItems.every(state => {
87
+ return (
88
+ state.qty_requested &&
89
+ state.reason_id !== undefined && state.reason_id !== null && state.reason_id !== '' && state.reason_id !== 0 && state.reason_id !== "" &&
90
+ state.condition_id !== undefined && state.condition_id !== null && state.condition_id !== '' && state.condition_id !== 0 && state.condition_id !== "" &&
91
+ state.resolution_id !== undefined && state.resolution_id !== null && state.resolution_id !== '' && state.resolution_id !== 0 && state.resolution_id !== ""
92
+ );
93
+ });
94
+
85
95
  // Build input mutation sesuai struktur backend
86
96
  const buildRmaInput = () => {
87
97
  if (!order || !order.items) return null;
@@ -151,16 +161,19 @@ const RMACreate = () => {
151
161
  const message = 'RMA request created successfully!';
152
162
  setSubmitSuccess(message);
153
163
  addToast({
154
- type: 'info',
155
- icon: <CheckCircleIcon size={18} />,
164
+ type: 'success',
165
+ icon: <CheckCircleIcon size={18} />,
156
166
  message,
157
167
  dismissable: true,
158
168
  timeout: 4000
159
169
  });
160
- // Redirect to RMA list after brief delay (optional immediate)
161
- setTimeout(() => {
162
- history.push('/return');
163
- }, 400); // small delay for user feedback
170
+ // Redirect to RMA detail page after brief delay
171
+ const rmaId = payload.rma_id || (payload.rma && payload.rma.id);
172
+ if (rmaId) {
173
+ setTimeout(() => {
174
+ history.push(`/return/view/${rmaId}`);
175
+ }, 400);
176
+ }
164
177
  } else {
165
178
  const message = (payload && payload.message) || 'Failed to create RMA.';
166
179
  setSubmitError(message);
@@ -176,104 +189,79 @@ const RMACreate = () => {
176
189
  const { formatMessage } = useIntl();
177
190
  const PAGE_TITLE = formatMessage({
178
191
  id: 'Quotes.pageTitleTextRMACreate',
179
- defaultMessage: 'New Return'
192
+ defaultMessage: 'New Return For Order'
180
193
  });
181
194
 
182
- const dummyChat = [
183
- {
184
- "message": "May I help you",
185
- "type": "seller",
186
- "timeStamp": "03-06-2024 2:59 pm"
187
- },
188
- {
189
- "message": "Yes",
190
- "type": "buyer",
191
- "timeStamp": "03-06-2024 2:59 pm"
192
- },
193
- {
194
- "message": "can i get a discount",
195
- "type": "buyer",
196
- "timeStamp": "03-06-2024 2:59 pm"
197
- },
198
- {
199
- "message": "you can redeem this code 'JunePlay'",
200
- "type": "seller",
201
- "timeStamp": "03-06-2024 2:59 pm"
202
- },
203
- {
204
- "message": "or this code 'collectfest'",
205
- "type": "seller",
206
- "timeStamp": "03-06-2024 2:59 pm"
207
- },
208
- {
209
- "message": "May I help you",
210
- "type": "seller",
211
- "timeStamp": "03-06-2024 2:59 pm"
212
- },
213
- {
214
- "message": "Yes",
215
- "type": "buyer",
216
- "timeStamp": "03-06-2024 2:59 pm"
217
- },
218
- {
219
- "message": "can i get a discount",
220
- "type": "buyer",
221
- "timeStamp": "03-06-2024 2:59 pm"
222
- },
223
- {
224
- "message": "you can redeem this code 'JunePlay'",
225
- "type": "seller",
226
- "timeStamp": "03-06-2024 2:59 pm"
227
- },
228
- {
229
- "message": "or this code 'collectfest'",
230
- "type": "seller",
231
- "timeStamp": "03-06-2024 2:59 pm"
232
- },
233
- ]
195
+ // const dummyChat = [
196
+ // {
197
+ // "message": "May I help you",
198
+ // "type": "seller",
199
+ // "timeStamp": "03-06-2024 2:59 pm"
200
+ // },
201
+ // {
202
+ // "message": "Yes",
203
+ // "type": "buyer",
204
+ // "timeStamp": "03-06-2024 2:59 pm"
205
+ // },
206
+ // {
207
+ // "message": "can i get a discount",
208
+ // "type": "buyer",
209
+ // "timeStamp": "03-06-2024 2:59 pm"
210
+ // },
211
+ // {
212
+ // "message": "you can redeem this code 'JunePlay'",
213
+ // "type": "seller",
214
+ // "timeStamp": "03-06-2024 2:59 pm"
215
+ // },
216
+ // {
217
+ // "message": "or this code 'collectfest'",
218
+ // "type": "seller",
219
+ // "timeStamp": "03-06-2024 2:59 pm"
220
+ // },
221
+ // {
222
+ // "message": "May I help you",
223
+ // "type": "seller",
224
+ // "timeStamp": "03-06-2024 2:59 pm"
225
+ // },
226
+ // {
227
+ // "message": "Yes",
228
+ // "type": "buyer",
229
+ // "timeStamp": "03-06-2024 2:59 pm"
230
+ // },
231
+ // {
232
+ // "message": "can i get a discount",
233
+ // "type": "buyer",
234
+ // "timeStamp": "03-06-2024 2:59 pm"
235
+ // },
236
+ // {
237
+ // "message": "you can redeem this code 'JunePlay'",
238
+ // "type": "seller",
239
+ // "timeStamp": "03-06-2024 2:59 pm"
240
+ // },
241
+ // {
242
+ // "message": "or this code 'collectfest'",
243
+ // "type": "seller",
244
+ // "timeStamp": "03-06-2024 2:59 pm"
245
+ // },
246
+ // ]
234
247
 
235
248
  return (
236
249
  <div className='relative grid gap-y-md'>
237
250
  <StoreTitle>{PAGE_TITLE}</StoreTitle>
238
251
  <div aria-live="polite" className="text-xl font-medium text-left">
239
- {PAGE_TITLE} {order ? `- ${order.number || order.increment_id}` : ''}
252
+ {PAGE_TITLE} {order ? `- #${order.number || order.increment_id}` : ''}
240
253
  </div>
241
254
  <div className='block relative'>
242
255
  <div aria-live="polite" className="text-lg font-medium text-left mb-4">
243
256
  {
244
257
  formatMessage({
245
258
  id: 'RMA.requestReturnInformation',
246
- defaultMessage: 'Request Return Information'
259
+ defaultMessage: 'Order Information'
247
260
  })
248
261
  }
249
262
  </div>
250
263
  <div className='rounded-md border border-gray-100 px-4 py-6 flex flex-col md_flex-row justify-between gap-x-10'>
251
264
  <div className='flex flex-col gap-y-4 w-full mb-4 md_0 md_w-6/12'>
252
- <div className='block p-2 -ml-2 -mr-2 bg-[#F1EFF6] rounded-md'>
253
- <p className='text-[13px] text-blue-700 flex justify-between'>
254
- <span className='font-medium block'>
255
- {
256
- formatMessage({
257
- id: 'RMA.Status',
258
- defaultMessage: 'Status Order'
259
- })
260
- }
261
- </span>
262
- <span className='font-medium block text-blue-700'>
263
- {order?.status}
264
- </span>
265
- </p>
266
- </div>
267
- <div className='block'>
268
- <p className='text-[13px] text-colorDefault flex justify-between'>
269
- <span className='font-medium block'>
270
- {formatMessage({ id: 'RMA.RMANumber', defaultMessage: 'RMA' })}
271
- </span>
272
- <span className='font-normal block'>
273
- {order ? order.increment_id || order.number : '-'}
274
- </span>
275
- </p>
276
- </div>
277
265
  <div className='block'>
278
266
  <p className='text-[13px] text-colorDefault flex justify-between'>
279
267
  <span className='font-medium block'>
@@ -329,8 +317,11 @@ const RMACreate = () => {
329
317
  </div>
330
318
  </div>
331
319
  <div className=' flex flex-col'>
332
- <div aria-live="polite" className="text-lg font-medium text-left mb-4">
333
- Items
320
+ <div aria-live="polite" className="text-lg font-medium text-left mb-1">
321
+ Select Items to Return
322
+ </div>
323
+ <div className="text-[13px] text-gray-500 mb-3">
324
+ Please check the items you want to return.
334
325
  </div>
335
326
  <div className='border-t border-gray-100 py-4 flex flex-col gap-y-4'>
336
327
  {order && order.items && order.items.length > 0 ? (
@@ -351,7 +342,26 @@ const RMACreate = () => {
351
342
  )}
352
343
  </div>
353
344
  </div>
354
- <div className=' flex flex-col mb-10'>
345
+ <div className='flex justify-end flex-row mb-4'>
346
+ <Button
347
+ priority='high'
348
+ onClick={handleSubmit}
349
+ disabled={createRmaLoading || !isAllCheckedItemsValid}
350
+ className={cn(
351
+ 'px-6 py-2 rounded-full text-white border flex items-center gap-2',
352
+ createRmaLoading || !isAllCheckedItemsValid
353
+ ? 'bg-gray-400 border-gray-400 cursor-not-allowed'
354
+ : 'bg-blue-700 border-blue-700 hover_bg-blue-700 hover_border-blue-700',
355
+ 'focus-visible_outline-none'
356
+ )}
357
+ >
358
+ {createRmaLoading && (
359
+ <span className="inline-block h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
360
+ )}
361
+ {createRmaLoading ? 'Submitting...' : 'Submit Return Request'}
362
+ </Button>
363
+ </div>
364
+ {/* <div className=' flex flex-col mb-10'>
355
365
  <div aria-live="polite" className="text-lg font-medium text-left mb-4">
356
366
  Messages
357
367
  </div>
@@ -381,26 +391,7 @@ const RMACreate = () => {
381
391
  </Button>
382
392
  </div>
383
393
  </div>
384
- <div className='flex justify-end flex-row gap-4 mt-6 mb-4'>
385
- <Button
386
- priority='high'
387
- onClick={handleSubmit}
388
- disabled={createRmaLoading}
389
- className={cn(
390
- 'px-6 py-2 rounded-full text-white border flex items-center gap-2',
391
- createRmaLoading
392
- ? 'bg-gray-400 border-gray-400 cursor-not-allowed'
393
- : 'bg-blue-700 border-blue-700 hover_bg-blue-700 hover_border-blue-700',
394
- 'focus-visible_outline-none'
395
- )}
396
- >
397
- {createRmaLoading && (
398
- <span className="inline-block h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
399
- )}
400
- {createRmaLoading ? 'Submitting...' : 'Submit RMA Request'}
401
- </Button>
402
- </div>
403
- </div>
394
+ </div> */}
404
395
  <style jsx="true">
405
396
  {`
406
397
  .chat-container::-webkit-scrollbar {
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useRef, useCallback } from 'react';
1
+ import React, { useMemo, useRef, useCallback, useEffect, useState } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import { StoreTitle } from '@magento/venia-ui/lib/components/Head';
4
4
  import Image from '@magento/venia-ui/lib/components/Image';
@@ -8,70 +8,57 @@ import ChatContent from '../LiveChat/chatContent';
8
8
  import cn from 'classnames';
9
9
  import { Send, Printer } from 'iconsax-react';
10
10
  import useRmaPage from '@riosst100/pwa-marketplace/src/talons/RMAPage/useRmaPage';
11
+ import { useToasts } from '@magento/peregrine/lib/Toasts';
12
+ import { CheckCircle as CheckCircleIcon } from 'react-feather';
11
13
 
12
14
  const RMADetail = () => {
13
15
  const { formatMessage } = useIntl();
14
16
  const { urlKey } = useParams();
15
17
  const rmaId = urlKey;
16
- const { rmaDetail, rmaDetailLoading, rmaDetailError } = useRmaPage({ rmaId });
18
+ const {
19
+ rmaDetail,
20
+ rmaDetailLoading,
21
+ rmaDetailError,
22
+ confirmShipping,
23
+ confirmShippingLoading,
24
+ confirmShippingError,
25
+ confirmShippingData,
26
+ refetchRmaDetail,
27
+ sendRmaMessage,
28
+ sendRmaMessageLoading,
29
+ sendRmaMessageError,
30
+ sendRmaMessageData
31
+ } = useRmaPage({ rmaId });
32
+ const [, { addToast }] = useToasts();
33
+ // State untuk input chat
34
+ const [chatInput, setChatInput] = useState('');
35
+ const [chatSending, setChatSending] = useState(false);
36
+ // Handler kirim pesan chat
37
+ const handleSendChat = useCallback(async () => {
38
+ if (!chatInput.trim()) return;
39
+ setChatSending(true);
40
+ const input = {
41
+ rma_id: rmaId,
42
+ text: chatInput.trim()
43
+ };
44
+ const { data, error } = await sendRmaMessage(input);
45
+ setChatSending(false);
46
+ if (error || !data?.lofmpSendRmaMessage?.success) {
47
+ addToast({
48
+ type: 'error',
49
+ message: (data?.lofmpSendRmaMessage?.message || error?.message || 'Failed to send message'),
50
+ dismissable: true,
51
+ timeout: 4000
52
+ });
53
+ return;
54
+ }
55
+ setChatInput('');
56
+ if (typeof refetchRmaDetail === 'function') refetchRmaDetail();
57
+ }, [chatInput, rmaId, sendRmaMessage, addToast, refetchRmaDetail]);
17
58
  const PAGE_TITLE = formatMessage({
18
59
  id: 'Quotes.pageTitleTextRMADetail',
19
60
  defaultMessage: 'Return Detail'
20
61
  });
21
-
22
- // Dummy chat data retained for development/demo and as a fallback
23
- const dummyChat = [
24
- {
25
- message: 'May I help you',
26
- type: 'seller',
27
- timeStamp: '03-06-2024 2:59 pm'
28
- },
29
- {
30
- message: 'Yes',
31
- type: 'buyer',
32
- timeStamp: '03-06-2024 2:59 pm'
33
- },
34
- {
35
- message: 'can i get a discount',
36
- type: 'buyer',
37
- timeStamp: '03-06-2024 2:59 pm'
38
- },
39
- {
40
- message: "you can redeem this code 'JunePlay'",
41
- type: 'seller',
42
- timeStamp: '03-06-2024 2:59 pm'
43
- },
44
- {
45
- message: "or this code 'collectfest'",
46
- type: 'seller',
47
- timeStamp: '03-06-2024 2:59 pm'
48
- },
49
- {
50
- message: 'May I help you',
51
- type: 'seller',
52
- timeStamp: '03-06-2024 2:59 pm'
53
- },
54
- {
55
- message: 'Yes',
56
- type: 'buyer',
57
- timeStamp: '03-06-2024 2:59 pm'
58
- },
59
- {
60
- message: 'can i get a discount',
61
- type: 'buyer',
62
- timeStamp: '03-06-2024 2:59 pm'
63
- },
64
- {
65
- message: "you can redeem this code 'JunePlay'",
66
- type: 'seller',
67
- timeStamp: '03-06-2024 2:59 pm'
68
- },
69
- {
70
- message: "or this code 'collectfest'",
71
- type: 'seller',
72
- timeStamp: '03-06-2024 2:59 pm'
73
- }
74
- ];
75
62
 
76
63
  const chatData = useMemo(() => {
77
64
  const messages = (rmaDetail && rmaDetail.messages) ? rmaDetail.messages : [];
@@ -79,7 +66,8 @@ const RMADetail = () => {
79
66
  const mapped = messages.map(m => ({
80
67
  message: m.text,
81
68
  type: email && m.sender_email === email ? 'buyer' : 'seller',
82
- timeStamp: m.created_at
69
+ timeStamp: m.created_at,
70
+ senderName: m.sender_name || ''
83
71
  }));
84
72
  return mapped.length > 0 ? mapped : dummyChat;
85
73
  }, [rmaDetail]);
@@ -101,7 +89,8 @@ const RMADetail = () => {
101
89
  orderDate: rmaDetail.order && rmaDetail.order.created_at ? toDate(rmaDetail.order.created_at) : '',
102
90
  dateRequested: rmaDetail.rma_date ? toDate(rmaDetail.rma_date) : '',
103
91
  shipping: rmaDetail.shipping_address,
104
- items: rmaDetail.items || []
92
+ items: rmaDetail.items || [],
93
+ rawStatus: rmaDetail.status_label,
105
94
  };
106
95
  }, [rmaDetail]);
107
96
  // Use same in-page print approach as OrderDetail: CSS hides everything except .print-area
@@ -129,6 +118,25 @@ const RMADetail = () => {
129
118
  window.print();
130
119
  }, []);
131
120
 
121
+ // Show toast and refetch on confirm shipping success
122
+ useEffect(() => {
123
+ if (
124
+ confirmShippingData &&
125
+ confirmShippingData.lofmpRmaShippingConfirm &&
126
+ confirmShippingData.lofmpRmaShippingConfirm.success
127
+ ) {
128
+ const message = confirmShippingData.lofmpRmaShippingConfirm.message || 'Shipping confirmed!';
129
+ addToast({
130
+ type: 'success',
131
+ icon: <CheckCircleIcon size={18} />,
132
+ message,
133
+ dismissable: true,
134
+ timeout: 4000
135
+ });
136
+ if (typeof refetchRmaDetail === 'function') refetchRmaDetail();
137
+ }
138
+ }, [confirmShippingData, refetchRmaDetail, addToast]);
139
+
132
140
  return (
133
141
  <div className='print-area relative grid gap-y-md' ref={printRef}>
134
142
  <StoreTitle>{PAGE_TITLE}</StoreTitle>
@@ -136,52 +144,68 @@ const RMADetail = () => {
136
144
  {PAGE_TITLE} {formatted.increment_id ? `#${formatted.increment_id}` : ''}
137
145
  </div>
138
146
  <div className='block relative'>
139
- <div className='flex gap-x-4 justify-end mb-2 no-print'>
140
- <Button
141
- priority='high'
142
- classes={{
143
- content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
144
- }}
145
- className={cn(
146
- "bg-white px-6 py-2 rounded-md text-gray-900 hover_!text-white border border-solid",
147
- "border-gray-900 focus-visible_outline-none hover_bg-gray-900"
148
- )}
149
- onClick={handlePrint}
150
- >
151
- <span className='flex gap-x-3 justify-center group-hover_!text-white'>
152
- <Printer
153
- size="24"
154
- className=''
155
- />
156
- {
157
- formatMessage({
158
- id: 'RMA.printRMAPackingSlip',
159
- defaultMessage: 'Print RMA Packing Slip'
160
- })
161
- }
162
- </span>
163
- </Button>
147
+ {(formatted.rawStatus === 'Approved' || formatted.rawStatus === 'approved') && (
148
+ <div className='flex flex-col gap-y-2 mb-2 no-print'>
149
+ <div className='flex gap-x-4 justify-end'>
150
+ <Button
151
+ priority='high'
152
+ classes={{
153
+ content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
154
+ }}
155
+ className={cn(
156
+ "bg-white px-6 py-2 rounded-md text-gray-900 hover_!text-white border border-solid",
157
+ "border-gray-900 focus-visible_outline-none hover_bg-gray-900"
158
+ )}
159
+ onClick={handlePrint}
160
+ >
161
+ <span className='flex gap-x-3 justify-center group-hover_!text-white'>
162
+ <Printer
163
+ size="24"
164
+ className=''
165
+ />
166
+ {
167
+ formatMessage({
168
+ id: 'RMA.printRMAPackingSlip',
169
+ defaultMessage: 'Print RMA Packing Slip'
170
+ })
171
+ }
172
+ </span>
173
+ </Button>
164
174
 
165
- <Button
166
- priority='high'
167
- classes={{
168
- content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
169
- }}
170
- className={cn(
171
- "bg-blue-600 px-6 py-2 rounded-md text-white border ",
172
- "border-blue-600 hover_bg-blue-700 hover_border-blue-700 focus-visible_outline-none"
175
+ <Button
176
+ priority='high'
177
+ classes={{
178
+ content: "flex justify-center gap-x-2.5 items-center text-[14px] font-medium capitalize"
179
+ }}
180
+ className={cn(
181
+ "bg-blue-600 px-6 py-2 rounded-md text-white border ",
182
+ "border-blue-600 hover_bg-blue-700 hover_border-blue-700 focus-visible_outline-none"
183
+ )}
184
+ onClick={async () => {
185
+ if (!confirmShippingLoading) await confirmShipping(Number(rmaId));
186
+ }}
187
+ disabled={confirmShippingLoading}
188
+ >
189
+ <span className='flex gap-x-3 justify-center'>
190
+ {confirmShippingLoading
191
+ ? formatMessage({ id: 'RMA.confirmShippingLoading', defaultMessage: 'Processing...' })
192
+ : formatMessage({ id: 'RMA.confirmShipping', defaultMessage: 'Confirm Shipping' })
193
+ }
194
+ </span>
195
+ </Button>
196
+ </div>
197
+ {confirmShippingError && (
198
+ <div className='text-[13px] text-red-600 text-right'>
199
+ {confirmShippingError.message}
200
+ </div>
173
201
  )}
174
- >
175
- <span className='flex gap-x-3 justify-center'>
176
- {
177
- formatMessage({
178
- id: 'RMA.confirmShipping',
179
- defaultMessage: 'Confirm Shipping'
180
- })
181
- }
182
- </span>
183
- </Button>
184
- </div>
202
+ {confirmShippingData && confirmShippingData.lofmpRmaShippingConfirm && !confirmShippingData.lofmpRmaShippingConfirm.success && (
203
+ <div className='text-[13px] text-red-600 text-right'>
204
+ {confirmShippingData.lofmpRmaShippingConfirm.message}
205
+ </div>
206
+ )}
207
+ </div>
208
+ )}
185
209
  <div aria-live="polite" className="text-lg font-medium text-left mb-4">
186
210
  {
187
211
  formatMessage({
@@ -267,13 +291,15 @@ const RMADetail = () => {
267
291
  </p>
268
292
  <p className='text-[13px] text-colorDefault whitespace-pre-wrap'>
269
293
  {
270
- `${formatted.shipping.firstname || ''} ${formatted.shipping.lastname || ''}\n` +
271
- `${formatted.shipping.email || ''}\n` +
272
- `${formatted.shipping.telephone || ''}\n` +
273
- `${formatted.shipping.street || ''}\n` +
274
- `${formatted.shipping.city || ''}\n` +
275
- `${formatted.shipping.region || ''}\n` +
276
- `${formatted.shipping.country_id || ''}\n`
294
+ formatted.shipping
295
+ ? `${formatted.shipping.firstname || ''} ${formatted.shipping.lastname || ''}\n` +
296
+ `${formatted.shipping.email || ''}\n` +
297
+ `${formatted.shipping.telephone || ''}\n` +
298
+ `${formatted.shipping.street || ''}\n` +
299
+ `${formatted.shipping.city || ''}\n` +
300
+ `${formatted.shipping.region || ''}\n` +
301
+ `${formatted.shipping.country_id || ''}\n`
302
+ : ''
277
303
  }
278
304
  </p>
279
305
  </div>
@@ -325,6 +351,10 @@ const RMADetail = () => {
325
351
  <textarea
326
352
  className='w-full focus-visible_outline-none border border-gray-100 p-1 rounded-md'
327
353
  cols={5}
354
+ value={chatInput}
355
+ onChange={e => setChatInput(e.target.value)}
356
+ placeholder={formatMessage({ id: 'RMA.chatPlaceholder', defaultMessage: 'Type your message...' })}
357
+ disabled={chatSending || sendRmaMessageLoading}
328
358
  />
329
359
  <Button
330
360
  priority='high'
@@ -335,11 +365,18 @@ const RMADetail = () => {
335
365
  "bg-blue-600 px-6 py-2 rounded-full text-white border ",
336
366
  "border-blue-600 hover_bg-blue-700 hover_border-blue-700 focus-visible_outline-none"
337
367
  )}
368
+ onClick={handleSendChat}
369
+ disabled={chatSending || sendRmaMessageLoading || !chatInput.trim()}
370
+ aria-label={formatMessage({ id: 'RMA.sendMessage', defaultMessage: 'Send Message' })}
338
371
  >
339
- <Send
340
- size="24"
341
- className='text-white'
342
- />
372
+ {chatSending || sendRmaMessageLoading ? (
373
+ <span className="inline-block h-4 w-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
374
+ ) : (
375
+ <Send
376
+ size="24"
377
+ className='text-white'
378
+ />
379
+ )}
343
380
  </Button>
344
381
  </div>
345
382
  </div>
@@ -37,8 +37,17 @@ const productItem = (props) => {
37
37
  if (onItemCheck) onItemCheck(e.target.checked);
38
38
  };
39
39
 
40
- // Input change handler
40
+
41
+ // Input change handler with qty_requested validation
41
42
  const handleFieldChange = (field, value) => {
43
+ if (field === 'qty_requested' && item && item.quantity_ordered) {
44
+ if (value > item.quantity_ordered) {
45
+ value = item.quantity_ordered;
46
+ }
47
+ if (value < 1) {
48
+ value = 1;
49
+ }
50
+ }
42
51
  if (onItemChange) onItemChange(field, value);
43
52
  };
44
53
 
@@ -89,43 +98,59 @@ const productItem = (props) => {
89
98
  <div className='block w-full lg_w-2/5'>
90
99
  {!!itemState.checked ? (
91
100
  <>
92
- <div className='flex flex-col mb-2.5' onChange={e => handleFieldChange('qty_requested', parseFloat(e.target.value))}>
101
+ <div className='flex flex-col mb-2.5'>
93
102
  <p className='mb-1'>
94
103
  Qty Return
95
104
  </p>
96
105
  <TextField
97
- value={itemState.qty_requested || ''}
98
- onChange={e => handleFieldChange('qty_requested', parseFloat(e.target.value))}
106
+ value={
107
+ itemState.qty_requested > item.quantity_ordered
108
+ ? item.quantity_ordered
109
+ : itemState.qty_requested || ''
110
+ }
111
+ onChange={e => {
112
+ let val = parseFloat(e.target.value);
113
+ if (isNaN(val)) val = '';
114
+ if (val > item.quantity_ordered) val = item.quantity_ordered;
115
+ if (val < 1 && val !== '') val = 1;
116
+ handleFieldChange('qty_requested', val);
117
+ }}
99
118
  type="number"
100
119
  min={1}
101
120
  max={item.quantity_ordered}
102
121
  />
103
122
  </div>
104
- <div className='flex flex-col mb-2.5' onChange={e => handleFieldChange('reason_id', parseInt(e.target.value, 10))}>
123
+ <div className='flex flex-col mb-2.5'>
105
124
  <p className='mb-1'>
106
125
  Return Reason
107
126
  </p>
108
127
  <Select
109
128
  options={reasonOptions}
110
129
  className='w-full'
130
+ value={itemState.reason_id !== undefined && itemState.reason_id !== null ? itemState.reason_id : ''}
131
+ onChange={e => handleFieldChange('reason_id', e.target.value)}
111
132
  />
112
133
  </div>
113
- <div className='flex flex-col mb-2.5' onChange={e => handleFieldChange('condition_id', parseInt(e.target.value, 10))}>
134
+ <div className='flex flex-col mb-2.5'>
114
135
  <p className='mb-1'>
115
136
  Item Condition
116
137
  </p>
117
138
  <Select
118
139
  options={conditionOptions}
119
140
  className='w-full'
141
+ value={itemState.condition_id !== undefined && itemState.condition_id !== null ? itemState.condition_id : ''}
142
+ onChange={e => handleFieldChange('condition_id', e.target.value)}
120
143
  />
121
144
  </div>
122
- <div className='flex flex-col mb-2.5' onChange={e => handleFieldChange('resolution_id', parseInt(e.target.value, 10))}>
145
+ <div className='flex flex-col mb-2.5'>
123
146
  <p className='mb-1'>
124
147
  Resolution
125
148
  </p>
126
149
  <Select
127
150
  options={resolutionOptions}
128
151
  className='w-full'
152
+ value={itemState.resolution_id !== undefined && itemState.resolution_id !== null ? itemState.resolution_id : ''}
153
+ onChange={e => handleFieldChange('resolution_id', e.target.value)}
129
154
  />
130
155
  </div>
131
156
  </>
@@ -28,7 +28,6 @@ const RmaRow = props => {
28
28
  rmaDetailError,
29
29
  rmaDetailData
30
30
  } = rma;
31
-
32
31
  const classes = useStyle(defaultClasses, props.classes);
33
32
  // Format date
34
33
  const isoFormattedDate = created_at ? created_at.replace(' ', 'T') : '';
@@ -54,6 +53,14 @@ const RmaRow = props => {
54
53
  return 'text-blue-700 text-[14px] font-medium py-[5px] border-none';
55
54
  }
56
55
 
56
+ // Inject parent rma_id (atau increment_id) ke setiap item agar key unik
57
+ const itemsWithParentId = Array.isArray(items)
58
+ ? items.map(it => ({
59
+ ...it,
60
+ __rmaParentId: rma_id || increment_id || ''
61
+ }))
62
+ : [];
63
+
57
64
  return (
58
65
  <li className={classes.root}>
59
66
  <div className='flex flex-col md_flex-row md_items-start justify-between mb-2.5'>
@@ -65,7 +72,7 @@ const RmaRow = props => {
65
72
  {sellerName}
66
73
  </span>
67
74
  <span>|</span>
68
- <span className="text-blue-700 text-[14px]">#{order_increment_id}</span>
75
+ <span className="text-blue-700 text-[14px]">#{increment_id}</span>
69
76
  </div>
70
77
  </div>
71
78
  </div>
@@ -81,14 +88,15 @@ const RmaRow = props => {
81
88
  <div className='flex flex-col ml-[14px]'>
82
89
  <div className='flex flex-row'>
83
90
  <div className={cn('mr-4 flex flex-col gap-8')}>
84
- {items && items.length > 0 && items.map((it, idx) => {
91
+ {itemsWithParentId.map((it, idx) => {
85
92
  const thumbnailProps = {
86
93
  alt: it.name,
87
94
  classes: { root: classes.thumbnail },
88
95
  width: 50
89
96
  };
97
+ // Use unique key: parent id + item.id
90
98
  return (
91
- <div key={it.id || idx} className={classes.productImage}>
99
+ <div key={`${it.__rmaParentId}-${it.id || idx}`} className={classes.productImage}>
92
100
  {it.image_url ? (
93
101
  <img src={it.image_url} alt={it.name} width={70} className={classes.thumbnail} />
94
102
  ) : (
@@ -99,20 +107,23 @@ const RmaRow = props => {
99
107
  })}
100
108
  </div>
101
109
  <div className='flex flex-col max-w-[375px] gap-8'>
102
- {items && items.length > 0 && items.map((it, idx) => (
103
- <div key={it.id || idx} className='flex flex-col gap-1 pb-2 last_pb-0'>
104
- <div className={cn(classes.productName, 'text-[14] font-medium')}>
105
- <span>{it.name}</span>
106
- </div>
107
- <div className="text-[14] text-gray-300">
108
- <span>Qty : {`${it.qty_requested || 0}`}</span>
109
- <br />
110
- <span>
111
- Price : {it.price ? <Price currencyCode={it.price.currency} value={it.price.value} /> : '-'}
112
- </span>
110
+ {itemsWithParentId.map((it, idx) => {
111
+ const qty = Number.isFinite(it.qty_requested) ? it.qty_requested : '-';
112
+ return (
113
+ <div key={`${it.__rmaParentId}-${it.id || idx}`} className='flex flex-col gap-1 pb-2 last_pb-0'>
114
+ <div className={cn(classes.productName, 'text-[14] font-medium')}>
115
+ <span>{it.name}</span>
116
+ </div>
117
+ <div className="text-[14] text-gray-300">
118
+ <span>Qty Requested : {qty}</span>
119
+ <br />
120
+ <span>
121
+ Price : {it.price ? <Price currencyCode={it.price.currency} value={it.price.value} /> : '-'}
122
+ </span>
123
+ </div>
113
124
  </div>
114
- </div>
115
- ))}
125
+ );
126
+ })}
116
127
  </div>
117
128
  </div>
118
129
  </div>
@@ -5,19 +5,23 @@ const Select = (props) => {
5
5
  const {
6
6
  wrapperClassname,
7
7
  className,
8
- options = []
8
+ options = [],
9
+ value,
10
+ onChange
9
11
  } = props;
10
12
 
11
13
  return (
12
14
  <div className={cx('flex', wrapperClassname)}>
13
15
  <select
14
16
  className={cx('border rounded-md border-gray-100 p-2 focus-visible_outline-none', className)}
17
+ value={value}
18
+ onChange={onChange}
15
19
  >
16
20
  <option value=''>
17
21
  Choose Option
18
22
  </option>
19
23
  {options.map((item) => (
20
- <option value={item.value}>
24
+ <option key={item.value} value={item.value}>
21
25
  {item.label}
22
26
  </option>
23
27
  ))}
@@ -42,6 +42,36 @@ const LofmpRmasFragment = gql`
42
42
  }
43
43
  `;
44
44
 
45
+ export const LOFMP_SEND_RMA_MESSAGE = gql`
46
+ mutation lofmpSendRmaMessage($input: SendMessageInput!) {
47
+ lofmpSendRmaMessage(input: $input) {
48
+ created_message {
49
+ attachments {
50
+ id
51
+ name
52
+ url
53
+ }
54
+ created_at
55
+ id
56
+ sender_email
57
+ sender_name
58
+ text
59
+ }
60
+ message
61
+ success
62
+ }
63
+ }
64
+ `;
65
+
66
+ export const LOFMP_RMA_SHIPPING_CONFIRM = gql`
67
+ mutation lofmpRmaShippingConfirm($rma_id: Int!) {
68
+ lofmpRmaShippingConfirm(rma_id: $rma_id) {
69
+ message
70
+ success
71
+ }
72
+ }
73
+ `;
74
+
45
75
  export const GET_LOFMP_RMA_CONFIGURATIONS = gql`
46
76
  query GetLofmpRmaConfigurations {
47
77
  lofmpRmaConfigurations {
@@ -166,5 +196,7 @@ export const GET_LOFMP_RMA_DETAIL = gql`
166
196
  export default {
167
197
  getLofmpRmasQuery: GET_LOFMP_RMAS,
168
198
  lofmpCreateRmaMutation: LOFMP_CREATE_RMA,
169
- getLofmpRmaDetailQuery: GET_LOFMP_RMA_DETAIL
199
+ getLofmpRmaDetailQuery: GET_LOFMP_RMA_DETAIL,
200
+ lofmpRmaShippingConfirmMutation: LOFMP_RMA_SHIPPING_CONFIRM,
201
+ lofmpSendRmaMessageMutation: LOFMP_SEND_RMA_MESSAGE
170
202
  };
@@ -12,7 +12,7 @@ import OrderProgressBar from './orderProgressBar';
12
12
  import OrderDetails from './OrderDetails';
13
13
  import defaultClasses from './orderRow.module.css';
14
14
  import cn from 'classnames';
15
- import { Verify, Shop } from 'iconsax-react';
15
+ import { Verify, Shop, ConvertCard } from 'iconsax-react';
16
16
  import PlaceholderImage from '@magento/venia-ui/lib/components/Image/placeholderImage';
17
17
  import { Link, useHistory } from 'react-router-dom';
18
18
 
@@ -203,30 +203,25 @@ const OrderRow = props => {
203
203
  </div>
204
204
  </div>
205
205
  <div className='flex flex-col md_flex-row justify-between mb-2.5'>
206
- <div className='flex flex-col ml-[5px]'>
207
- <div className='flex flex-row'>
208
- <div className={cn('flex flex-col gap-2')}>
209
- {items && items.length > 0 && items.map((it, idx) => {
210
- const { url, label } = getThumbnailForSku(it.product_sku, it.product_name, idx);
211
- return (
212
- <div key={it.id || idx} className={classes.productImage}>
213
- {url ? (
214
- <img
215
- src={url}
216
- alt={label}
217
- width={80}
218
- className={classes.thumbnail}
219
- />
220
- ) : (
221
- <PlaceholderImage alt={it.product_name} classes={{ root: classes.thumbnail }} width={60} />
222
- )}
223
- </div>
224
- );
225
- })}
226
- </div>
227
- <div className='flex flex-col max-w-[375px] gap-8'>
228
- {items && items.length > 0 && items.map((it, idx) => (
229
- <div key={it.id || idx} className='flex flex-col gap-1 pb-2 last_pb-0'>
206
+ <div className='flex flex-col ml-[5px] gap-y-[20px]'>
207
+ {/* Per-item horizontal flex: image + detail */}
208
+ {items && items.length > 0 && items.map((it, idx) => {
209
+ const { url, label } = getThumbnailForSku(it.product_sku, it.product_name, idx);
210
+ return (
211
+ <div key={it.id || idx} className='flex flex-row gap-4 mb-2'>
212
+ <div className={classes.productImage}>
213
+ {url ? (
214
+ <img
215
+ src={url}
216
+ alt={label}
217
+ width={80}
218
+ className={classes.thumbnail}
219
+ />
220
+ ) : (
221
+ <PlaceholderImage alt={it.product_name} classes={{ root: classes.thumbnail }} width={60} />
222
+ )}
223
+ </div>
224
+ <div className='flex flex-col gap-1 pb-2 last_pb-0 max-w-[375px]'>
230
225
  <div className={cn(classes.productName, 'text-[14] font-medium')}>
231
226
  <span>{it.product_name}</span>
232
227
  </div>
@@ -238,9 +233,9 @@ const OrderRow = props => {
238
233
  </span>
239
234
  </div>
240
235
  </div>
241
- ))}
242
- </div>
243
- </div>
236
+ </div>
237
+ );
238
+ })}
244
239
  </div>
245
240
  {/* Right column: bottom-aligned Order Total + CTA */}
246
241
  <div className='flex flex-col items-end gap-2 md_pl-10 md_self-end mr-4 mb-1'>
@@ -254,18 +249,6 @@ const OrderRow = props => {
254
249
  <div className="text-lg font-medium">{orderTotalPrice}</div>
255
250
  </div>
256
251
  <div className="flex flex-row gap-2 w-full justify-end items-center">
257
- {showNewReturnButton && (
258
- <button
259
- type="button"
260
- className="bg-blue-700 hover:bg-white hover:text-blue-700 hover:border hover:border-blue-700 rounded-full px-[30px] py-[5px] text-[13px] font-medium text-white transition-all duration-300 ease-in-out"
261
- onClick={handleNewReturn}
262
- >
263
- <FormattedMessage
264
- id={'orderRow.newReturn'}
265
- defaultMessage={'New Return'}
266
- />
267
- </button>
268
- )}
269
252
  <Link
270
253
  to={{
271
254
  pathname: `/order-history/view/${orderNumber}`,
@@ -279,6 +262,22 @@ const OrderRow = props => {
279
262
  />
280
263
  </span>
281
264
  </Link>
265
+ {showNewReturnButton && (
266
+ <div
267
+ className={cn(
268
+ "cursor-pointer border border-blue-700 bg-white text-blue-700 hover:bg-blue-50 hover:text-blue-700 hover:border-blue-700",
269
+ "rounded-full px-[10px] py-[3px] text-[13px] font-medium transition-all duration-300 ease-in-out flex items-center gap-2"
270
+ )}
271
+ onClick={handleNewReturn}
272
+ title={formatMessage({ id: 'orderRow.returnProduct', defaultMessage: 'Return Product' })}
273
+ >
274
+ <ConvertCard size={20} color="#f26313" />
275
+ <FormattedMessage
276
+ id={'orderRow.ReturnItems'}
277
+ defaultMessage={'Return Items'}
278
+ />
279
+ </div>
280
+ )}
282
281
  </div>
283
282
  </div>
284
283
  </div>
@@ -12,10 +12,27 @@ const PAGE_SIZE = 5;
12
12
  // Custom RMA Page talon with page-based pagination
13
13
  export const useRmaPage = (props = {}) => {
14
14
  const { rmaId, orderNumber } = props;
15
- const { getLofmpRmasQuery, getLofmpRmaDetailQuery, lofmpCreateRmaMutation } = operations;
15
+ const { getLofmpRmasQuery, getLofmpRmaDetailQuery, lofmpCreateRmaMutation, lofmpRmaShippingConfirmMutation, lofmpSendRmaMessageMutation } = operations;
16
+
17
+ // Mutation for sending RMA message
18
+ const [sendRmaMessageMutation, { data: sendRmaMessageData, error: sendRmaMessageError, loading: sendRmaMessageLoading }] = useMutation(lofmpSendRmaMessageMutation);
19
+
20
+ // Handler for sending RMA message
21
+ const sendRmaMessage = useCallback(async (input) => {
22
+ try {
23
+ const { data } = await sendRmaMessageMutation({ variables: { input } });
24
+ return { data, error: null };
25
+ } catch (error) {
26
+ return { data: null, error };
27
+ }
28
+ }, [sendRmaMessageMutation]);
29
+
16
30
  // Mutation for creating RMA
17
31
  const [createRmaMutation, { data: createRmaData, error: createRmaError, loading: createRmaLoading }] = useMutation(lofmpCreateRmaMutation);
18
32
 
33
+ // Mutation for confirming shipping
34
+ const [confirmShippingMutation, { data: confirmShippingData, error: confirmShippingError, loading: confirmShippingLoading }] = useMutation(lofmpRmaShippingConfirmMutation);
35
+
19
36
  // Handler for creating RMA
20
37
  const createRma = useCallback(async (input) => {
21
38
  try {
@@ -26,6 +43,16 @@ export const useRmaPage = (props = {}) => {
26
43
  }
27
44
  }, [createRmaMutation]);
28
45
 
46
+ // Handler for confirming shipping
47
+ const confirmShipping = useCallback(async (rma_id) => {
48
+ try {
49
+ const { data } = await confirmShippingMutation({ variables: { rma_id } });
50
+ return { data, error: null };
51
+ } catch (error) {
52
+ return { data: null, error };
53
+ }
54
+ }, [confirmShippingMutation]);
55
+
29
56
  const [
30
57
  ,
31
58
  {
@@ -47,7 +74,7 @@ export const useRmaPage = (props = {}) => {
47
74
  variables: {
48
75
  filter: orderNumber
49
76
  ? { order_number: orderNumber }
50
- : (searchText ? { order_increment_id: { match: searchText } } : undefined),
77
+ : (searchText ? { order_number: searchText } : undefined),
51
78
  pageSize,
52
79
  currentPage
53
80
  },
@@ -58,11 +85,13 @@ export const useRmaPage = (props = {}) => {
58
85
  const {
59
86
  data: rmaDetailData,
60
87
  error: rmaDetailError,
61
- loading: rmaDetailLoading
88
+ loading: rmaDetailLoading,
89
+ refetch: refetchRmaDetail
62
90
  } = useQuery(getLofmpRmaDetailQuery, {
63
91
  fetchPolicy: 'cache-and-network',
64
92
  variables: { rmaId },
65
- skip: !rmaId
93
+ skip: !rmaId,
94
+ pollInterval: 3000 // 1 detik auto-refresh
66
95
  });
67
96
 
68
97
  const rmasData = data && data.lofmpRmas ? data.lofmpRmas : undefined;
@@ -135,10 +164,15 @@ export const useRmaPage = (props = {}) => {
135
164
  createRmaLoading,
136
165
  createRmaError,
137
166
  createRmaData,
138
- // Detail
167
+ confirmShipping,
168
+ confirmShippingLoading,
169
+ confirmShippingError,
170
+ confirmShippingData,
139
171
  rmaDetail: rmaDetailData ? rmaDetailData.lofmpRmaDetail : undefined,
140
172
  rmaDetailLoading,
141
- rmaDetailError
173
+ rmaDetailError,
174
+ refetchRmaDetail
175
+ ,sendRmaMessage, sendRmaMessageData, sendRmaMessageError, sendRmaMessageLoading
142
176
  };
143
177
  };
144
178