diff --git a/src/pages/members/feedback-member/FeedbackMember.tsx b/src/pages/members/feedback-member/FeedbackMember.tsx
index d8d8810..242b47c 100644
--- a/src/pages/members/feedback-member/FeedbackMember.tsx
+++ b/src/pages/members/feedback-member/FeedbackMember.tsx
@@ -1,17 +1,23 @@
import { Container, DataGridInner } from '@/components';
-import { ManageFeedbackMemberProvider } from './hooks/ManageFeedbackMemberContext';
+import { ManageFeedbackMemberProvider } from './hooks/ManageFeedbackMemberContext';
// import EditDialog from './blocks/EditDialog';
// import DeleteDialog from './blocks/DeleteDialog';
import { Breadcrumbs, Link } from '@mui/material';
import { Helmet } from 'react-helmet';
+import FeedbackDetail from './blocks/FeedbackDetail';
+import EditDialog from './blocks/EditDialog';
const FeedbackMemberMaster = () => {
return (
<>
-
TPAY | Manage Provider
+
+ TPAY | Manage Provider
+
- Manage Feedback Member
+
+ Manage Feedback Member
+
Dashboard
@@ -28,9 +34,12 @@ const FeedbackMemberMaster = () => {
+
+
{/*
*/}
+ {/* */}
>
diff --git a/src/pages/members/feedback-member/blocks/EditDialog.tsx b/src/pages/members/feedback-member/blocks/EditDialog.tsx
new file mode 100644
index 0000000..53a0715
--- /dev/null
+++ b/src/pages/members/feedback-member/blocks/EditDialog.tsx
@@ -0,0 +1,189 @@
+import { useState, useEffect } from 'react';
+import { Dialog, DialogContent } from '@/components/ui/dialog';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from '@/components/ui/select';
+import { useCallApi } from '@/hooks';
+import { apiConfig } from '@/config/api.config';
+import { getAuth } from '@/auth';
+import { useManageFeedbackContext } from '../hooks/useManageFeedbackMemberContext';
+import { Button } from '@/components/ui/button';
+import { toast } from 'sonner';
+import { X } from 'lucide-react';
+
+const API_URL = apiConfig.service_feedback;
+
+// Define status types
+type StatusCode = 'W' | 'N' | 'Y';
+
+// Status display mapping for Select component
+const statusDisplayMap: Record
= {
+ W: 'Waiting Follow Up',
+ N: 'Rejected',
+ Y: 'Accepted'
+};
+
+const EditDialog = () => {
+ const [loading, setLoading] = useState(false);
+ const { PutData, GetData } = useCallApi();
+ const { handleEditDialog, showEditDialog, selectedFeedback, handleDetailDialog, refreshData } =
+ useManageFeedbackContext();
+
+ const [reviewNotes, setReviewNotes] = useState('');
+ const [status, setStatus] = useState('Y'); // Default to Accepted
+
+ useEffect(() => {
+ if (showEditDialog && selectedFeedback) {
+ fetchFeedbackDetail();
+ }
+ }, [showEditDialog, selectedFeedback]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setLoading(true);
+
+ try {
+ const username = getAuth()?.user.username;
+ const payload = {
+ review_by: username,
+ review_notes: reviewNotes,
+ status: status
+ };
+
+ const response = await PutData(`${API_URL}/feedback/update/${selectedFeedback}`, payload);
+
+ if (response?.status) {
+ toast.success('Feedback reviewed successfully');
+
+ // Close the edit dialog
+ handleEditDialog(false, null);
+
+ // First close the detail dialog to reset its state
+ handleDetailDialog(false, null);
+
+ // Refresh the main data grid first
+ refreshData();
+
+ // Wait a tiny bit then reopen the detail with refreshed data
+ setTimeout(() => {
+ handleDetailDialog(true, selectedFeedback);
+ }, 300);
+ } else {
+ toast.error(response?.message || 'Failed to update feedback');
+ }
+ } catch (error) {
+ console.error('Error updating feedback:', error);
+ toast.error('An error occurred while updating feedback');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const fetchFeedbackDetail = async () => {
+ if (!selectedFeedback) return;
+ try {
+ setLoading(true);
+ const response = await GetData(`${API_URL}/feedback/detail/${selectedFeedback}`, {
+ id: selectedFeedback
+ });
+
+ if (!response || !response.data) {
+ console.error('Invalid response format');
+ return;
+ }
+
+ const data = response.data;
+ console.log('Fetched data:', data);
+
+ setReviewNotes(data.review_notes || '');
+
+ const currentStatus = (data.status as StatusCode) || 'Y';
+ setStatus(currentStatus);
+ } catch (error) {
+ console.error('Error fetching feedback details:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default EditDialog;
diff --git a/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx b/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx
index 648ac9e..c7d1c20 100644
--- a/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx
+++ b/src/pages/members/feedback-member/blocks/FeedbackDetail.tsx
@@ -1,199 +1,304 @@
import React, { useEffect, useState } from 'react';
-import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
-import { useCallApi } from '@/hooks';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle
+} from '@/components/ui/dialog';
import { apiConfig } from '@/config/api.config';
+import { useCallApi } from '@/hooks';
import { format } from 'date-fns';
+import { Image as ImageIcon, X } from 'lucide-react';
+import { useManageFeedbackContext } from '../hooks/useManageFeedbackMemberContext';
+import { Button } from '@/components/ui/button';
+import FeedbackEditDialog from './EditDialog';
-interface FeedbackDetailProps {
- showDialog: boolean;
- handleDialog: (show: boolean) => void;
- feedbackId: string | null;
+interface ImageModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ imageUrl: string;
}
-interface FeedbackDetailData {
- Feedback_id: string;
- Feedback_feedback_notes: string;
- Feedback_feedback_screenshoot: string;
- Feedback_review_notes: string;
- Feedback_emotion: string;
- Feedback_review_screenshoot: string | null;
- Feedback_category: string;
- Feedback_status: string;
- Feedback_created_at: string;
- Feedback_review_by: string | null;
- Feedback_review_at: string;
- Feedback_deleted_by: string | null;
- Feedback_deleted_at: string | null;
- Feedback_createdById: string;
-}
-
-const API_URL = apiConfig.service_feedback;
-
-const FeedbackDetail: React.FC = ({ showDialog, handleDialog, feedbackId }) => {
- const [feedbackDetail, setFeedbackDetail] = useState(null);
- const [loading, setLoading] = useState(false);
- const { GetData } = useCallApi();
-
- useEffect(() => {
- const fetchFeedbackDetail = async () => {
- if (!feedbackId) return;
-
- setLoading(true);
- try {
- const response = await GetData(`${API_URL}/feedback/${feedbackId}`,{});
- if (response?.data) {
- setFeedbackDetail(response.data);
- }
- } catch (error) {
- console.error('Error fetching feedback detail:', error);
- } finally {
- setLoading(false);
- }
- };
-
- if (showDialog && feedbackId) {
- fetchFeedbackDetail();
- } else {
- setFeedbackDetail(null);
- }
- }, [showDialog, feedbackId, GetData]);
-
- const formatDate = (dateString: string | null) => {
- if (!dateString) return '-';
- try {
- return format(new Date(dateString), 'yyyy-MM-dd HH:mm:ss');
- } catch (e) {
- return dateString;
- }
- };
-
- const getEmotionText = (emotion: string) => {
- const emotions: Record = {
- '1': 'Very Disappointed',
- '2': 'Disappointed',
- '3': 'Happy',
- '4': 'Very Happy',
- '5': 'Extremely Happy'
- };
- return emotions[emotion] || emotion;
- };
-
- const getStatusText = (status: string) => {
- const statuses: Record = {
- 'W': 'Waiting',
- 'P': 'Processed',
- 'D': 'Done'
- };
- return statuses[status] || status;
- };
+// New Image Modal Component
+const ImageModal: React.FC = ({ isOpen, onClose, imageUrl }) => {
+ if (!isOpen) return null;
return (
-
+
+
+
+

+
+
);
};
-export default FeedbackDetail;
\ No newline at end of file
+const ScreenshotGrid = ({ title, images }: { title: string; images: string[] }) => {
+ const [selectedImage, setSelectedImage] = useState(null);
+
+ const openImageModal = (url: string) => {
+ setSelectedImage(url);
+ };
+
+ const closeImageModal = () => {
+ setSelectedImage(null);
+ };
+
+ return (
+
+
{title}
+ {images && images.length > 0 ? (
+
+ {images.map((url, index) => (
+
url && openImageModal(url)}
+ >
+ {url ? (
+

+ ) : (
+
+
+
+ )}
+
+ ))}
+
+ ) : (
+
N/A
+ )}
+
+
+
+ );
+};
+
+const FeedbackDetail = () => {
+ const [loading, setLoading] = useState(false);
+ const [feedbackDetail, setFeedbackDetail] = useState(null);
+ const { GetData } = useCallApi();
+ const API_URL = apiConfig.service_feedback;
+ const {
+ handleDetailDialog,
+ selectedFeedback,
+ showDetailDialog,
+ handleEditDialog,
+ showEditDialog
+ } = useManageFeedbackContext();
+
+ const formatDateTime = (dateString: string) => {
+ if (!dateString) return 'N/A';
+ try {
+ return format(new Date(dateString), 'MMM dd, yyyy HH:mm');
+ } catch {
+ return 'N/A';
+ }
+ };
+
+ const formatValue = (value: any) => {
+ if (value === null || value === undefined || (typeof value === 'string' && value.trim() === ''))
+ return 'N/A';
+ return value;
+ };
+
+ const getEmotionLabel = (emotion: string) => {
+ const emotions: Record = {
+ '1': 'Very Dissatisfied',
+ '2': 'Dissatisfied',
+ '3': 'Neutral',
+ '4': 'Satisfied',
+ '5': 'Very Satisfied'
+ };
+ return emotions[emotion] || 'N/A';
+ };
+
+ const getStatusLabel = (status: string) => {
+ if (!status) return { label: 'N/A', color: 'text-gray-500' };
+
+ const statuses: Record = {
+ W: { label: 'Waiting', color: 'text-yellow-500' },
+ Y: { label: 'Accepted', color: 'text-green-500' },
+ N: { label: 'Rejected', color: 'text-red-500' },
+ C: { label: 'Completed', color: 'text-blue-500' },
+ R: { label: 'Rejected', color: 'text-red-500' }
+ };
+ return statuses[status] || { label: 'N/A', color: 'text-gray-500' };
+ };
+
+ const fetchFeedbackDetail = async () => {
+ if (!selectedFeedback) return;
+ try {
+ setLoading(true);
+ const timestamp = new Date().getTime();
+ const response = await GetData(`${API_URL}/feedback/detail/${selectedFeedback}`, {
+ id: selectedFeedback,
+ _t: timestamp
+ });
+
+ if (!response || !response.data) {
+ console.error('Invalid response format');
+ return;
+ }
+
+ const data = response.data;
+
+ const normalizedData = {
+ ...data,
+ feedback_screenshoot: Array.isArray(data?.feedback_screenshoot)
+ ? data.feedback_screenshoot
+ : data?.feedback_screenshoot
+ ? [data.feedback_screenshoot]
+ : [],
+ review_screenshoot: Array.isArray(data?.review_screenshoot)
+ ? data.review_screenshoot
+ : data?.review_screenshoot
+ ? [data.review_screenshoot]
+ : []
+ };
+
+ setFeedbackDetail(normalizedData);
+ } catch (error) {
+ console.error('Error fetching feedback detail:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ if (showDetailDialog && selectedFeedback) {
+ fetchFeedbackDetail();
+ } else if (!showDetailDialog) {
+ setFeedbackDetail(null);
+ }
+ }, [showDetailDialog, selectedFeedback]);
+
+ const handleReviewClick = () => {
+ handleEditDialog(true, selectedFeedback);
+ };
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default FeedbackDetail;
diff --git a/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx b/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx
index d971d08..8b93158 100644
--- a/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx
+++ b/src/pages/members/feedback-member/hooks/ManageFeedbackMemberContext.tsx
@@ -2,10 +2,11 @@ import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
import { apiConfig } from '@/config/api.config';
import { useCallApi } from '@/hooks';
import { ColumnDef } from '@tanstack/react-table';
-import React, { createContext, useCallback, useMemo, useState } from 'react';
+import React, { createContext, useCallback, useMemo, useState, useEffect } from 'react';
import { Toaster } from 'sonner';
-// import FeedbackDetail from './blocks/FeedbackDetail';
import FeedbackDetail from '../blocks/FeedbackDetail';
+import FeedbackEditDialog from '../blocks/EditDialog';
+import { useNavigate } from 'react-router';
interface feedbackProps {
id: string;
@@ -17,17 +18,20 @@ interface feedbackProps {
agentId: string;
}
+interface FetchParams {
+ pageIndex: number;
+ pageSize: number;
+ sorting: any[];
+ columnFilters: any[];
+}
+
interface ContextProps {
feedback: feedbackProps[];
- showEditDialog: boolean;
- handleEditDialog: (show: boolean, selected_sucos: string | null) => void;
- showAddDialog: boolean;
- handleAddDialog: (show: boolean) => void;
- showDeleteDialog: boolean;
- handleDeleteDialog: (show: boolean, selected_sucos: string | null) => void;
showDetailDialog: boolean;
- handleDetailDialog: (show: boolean, selected_sucos: string | null) => void;
- selectedfeedback: string | null;
+ handleDetailDialog: (show: boolean, selected_feedback: string | null) => void;
+ selectedFeedback: string | null;
+ showEditDialog: boolean;
+ handleEditDialog: (show: boolean, selected_feedback: string | null) => void;
getfeedbackLists: (
limit: number,
page: number,
@@ -35,20 +39,18 @@ interface ContextProps {
order_field: any,
order_direction: any
) => Promise<{ data: feedbackProps[]; totalCount: number } | undefined>;
+ refreshData: () => void;
}
const initialProps: ContextProps = {
feedback: [],
- showEditDialog: false,
- handleEditDialog: () => {},
- showAddDialog: false,
- handleAddDialog: () => {},
- showDeleteDialog: false,
- handleDeleteDialog: () => {},
showDetailDialog: false,
handleDetailDialog: () => {},
- selectedfeedback: null,
- getfeedbackLists: async () => undefined
+ selectedFeedback: null,
+ showEditDialog: false,
+ handleEditDialog: () => {},
+ getfeedbackLists: async () => undefined,
+ refreshData: () => {}
};
const ManageFeedbackMemberContext = createContext(initialProps);
@@ -60,32 +62,37 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
const [showEditDialog, setShowEditDialog] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
const [showDetailDialog, setShowDetailDialog] = useState(false);
- const [selectedfeedback, setSelectedfeedback] = useState(null);
+ const [selectedFeedback, setSelectedFeedback] = useState(null);
const { GetData } = useCallApi();
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
+
+ const [lastFetchParams, setLastFetchParams] = useState(null);
+
const handleAddDialog = useCallback((show: boolean) => {
setShowAddDialog(show);
}, []);
- const handleEditDialog = useCallback((show: boolean, selected_feedback: string | null) => {
- setShowEditDialog(show);
- setSelectedfeedback(show ? selected_feedback : null);
- }, []);
-
- const handleDeleteDialog = useCallback((show: boolean, selected_feedback: string | null) => {
- setShowDeleteDialog(show);
- setSelectedfeedback(show ? selected_feedback : null);
- }, []);
-
const handleDetailDialog = useCallback((show: boolean, selected_feedback: string | null) => {
setShowDetailDialog(show);
- setSelectedfeedback(show ? selected_feedback : null);
+ setSelectedFeedback(show ? selected_feedback : null);
+ }, []);
+
+ const handleEditDialog = useCallback((show: boolean, selected_feedback: string | null) => {
+ setShowEditDialog(show);
+ if (show) {
+ setSelectedFeedback(selected_feedback);
+ }
+ }, []);
+
+ const refreshData = useCallback(() => {
+ setRefreshTrigger((prev) => prev + 1);
}, []);
const columns = useMemo[]>(
() => [
{
- accessorFn: (row) => row.customer,
+ accessorFn: (row) => row.customerid,
id: 'customer_id',
header: ({ column }) => ,
enableSorting: false,
@@ -95,7 +102,7 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
}
},
{
- accessorFn: (row) => row.Feedback_emotion,
+ accessorFn: (row) => row.feedbacks_emotion,
id: 'feedback_emotion',
header: ({ column }) => ,
enableSorting: false,
@@ -105,7 +112,7 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
}
},
{
- accessorFn: (row) => row.Feedback_feedback_notes,
+ accessorFn: (row) => row.feedbacks_feedback_notes,
id: 'feedback_notes',
header: ({ column }) => ,
enableSorting: false,
@@ -125,25 +132,14 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
<>
- {/*
- */}
>
);
},
@@ -153,20 +149,26 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
}
}
],
- [handleEditDialog, handleDeleteDialog, handleDetailDialog]
+ []
);
const getfeedbackLists = async (page: number, limit: number, sorting: any, filter: any) => {
try {
- sorting = sorting.length == 0 ? [{ id: 'name', desc: false }] : sorting;
+ setLastFetchParams({
+ pageIndex: page,
+ pageSize: limit,
+ sorting: sorting,
+ columnFilters: filter
+ });
+
+ sorting = sorting.length == 0 ? [{ id: 'feedbacks_created_at', desc: true }] : sorting;
filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() };
const response = await GetData(`${API_URL}/feedback/list`, {
limit,
page: page + 1,
with_deleted: false,
order_field: sorting[0].id,
- order_direction: sorting[0].desc == false ? 'ASC' : 'DESC',
- // filter: JSON.stringify(filter)
+ order_direction: sorting[0].desc == false ? 'ASC' : 'DESC'
});
// console.log(response?.data);
setFeedback(response?.data.list);
@@ -176,37 +178,37 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
}
};
+ useEffect(() => {
+ if (refreshTrigger > 0 && lastFetchParams) {
+ getfeedbackLists(
+ lastFetchParams.pageIndex,
+ lastFetchParams.pageSize,
+ lastFetchParams.sorting,
+ lastFetchParams.columnFilters
+ );
+ }
+ }, [refreshTrigger]);
+
return (
-
- {/* Feedback Detail Modal Component */}
- handleDetailDialog(show, show ? selectedfeedback : null)}
- feedbackId={selectedfeedback}
- />
}
layout={{ card: true }}
- sorting={[{ id: 'created_at', desc: true }]}
+ sorting={[{ id: 'feedbacks_created_at', desc: true }]}
serverSide={true}
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
getfeedbackLists(pageIndex, pageSize, sorting, columnFilters)
@@ -219,4 +221,4 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
};
export { ManageFeedbackMemberContext, ManageFeedbackMemberProvider };
-export type { feedbackProps };
\ No newline at end of file
+export type { feedbackProps };
diff --git a/src/pages/members/feedback-member/hooks/useManageFeedbackMemberContext.tsx b/src/pages/members/feedback-member/hooks/useManageFeedbackMemberContext.tsx
index e45bbc7..1fa69e3 100644
--- a/src/pages/members/feedback-member/hooks/useManageFeedbackMemberContext.tsx
+++ b/src/pages/members/feedback-member/hooks/useManageFeedbackMemberContext.tsx
@@ -1,7 +1,7 @@
import { useContext } from 'react';
import { ManageFeedbackMemberContext } from './ManageFeedbackMemberContext';
-const useManageProviderContext = () => {
+const useManageFeedbackContext = () => {
const context = useContext(ManageFeedbackMemberContext);
if (!context) throw new Error('useManageProviderContext must be used within AuthProvider');
@@ -9,4 +9,4 @@ const useManageProviderContext = () => {
return context;
};
-export { useManageProviderContext };
+export { useManageFeedbackContext };
diff --git a/src/pages/notification/hooks/ManageNotificationContext.tsx b/src/pages/notification/hooks/ManageNotificationContext.tsx
index 4c1a754..e40e41b 100644
--- a/src/pages/notification/hooks/ManageNotificationContext.tsx
+++ b/src/pages/notification/hooks/ManageNotificationContext.tsx
@@ -8,6 +8,8 @@ import { useCallApi } from '@/hooks';
import moment from 'moment';
interface SelectedNotification {
+ all_customers: string;
+ customer_name: string;
id: string;
content: string;
subject: string;
@@ -62,6 +64,21 @@ const ManageNotifContextProvider = ({ children }: { children: React.ReactNode })
const columns = useMemo[]>(
() => [
+ {
+ accessorFn: (row) => {
+ if (row.all_customers === 'Y') {
+ return 'All Customers';
+ }
+ return row.customer_name || '';
+ },
+ id: 'send',
+ header: ({ column }) => ,
+ enableSorting: false,
+ enableHiding: false,
+ meta: {
+ headerClassName: 'w-[200px]'
+ }
+ },
{
accessorFn: (row) => row.content,
id: 'content',