feedbackmember+detail+review
This commit is contained in:
@ -1,17 +1,23 @@
|
|||||||
import { Container, DataGridInner } from '@/components';
|
import { Container, DataGridInner } from '@/components';
|
||||||
import { ManageFeedbackMemberProvider } from './hooks/ManageFeedbackMemberContext';
|
import { ManageFeedbackMemberProvider } from './hooks/ManageFeedbackMemberContext';
|
||||||
// import EditDialog from './blocks/EditDialog';
|
// import EditDialog from './blocks/EditDialog';
|
||||||
// import DeleteDialog from './blocks/DeleteDialog';
|
// import DeleteDialog from './blocks/DeleteDialog';
|
||||||
import { Breadcrumbs, Link } from '@mui/material';
|
import { Breadcrumbs, Link } from '@mui/material';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import FeedbackDetail from './blocks/FeedbackDetail';
|
||||||
|
import EditDialog from './blocks/EditDialog';
|
||||||
|
|
||||||
const FeedbackMemberMaster = () => {
|
const FeedbackMemberMaster = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet><title>TPAY | Manage Provider</title></Helmet>
|
<Helmet>
|
||||||
|
<title>TPAY | Manage Provider</title>
|
||||||
|
</Helmet>
|
||||||
<ManageFeedbackMemberProvider>
|
<ManageFeedbackMemberProvider>
|
||||||
<Container>
|
<Container>
|
||||||
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">Manage Feedback Member</h1>
|
<h1 className="text-xl font-medium leading-none text-gray-900 mb-3">
|
||||||
|
Manage Feedback Member
|
||||||
|
</h1>
|
||||||
<Breadcrumbs sx={{ mb: 2 }}>
|
<Breadcrumbs sx={{ mb: 2 }}>
|
||||||
<Link underline="none" color="inherit" href="/">
|
<Link underline="none" color="inherit" href="/">
|
||||||
<span className="text-sm hover:underline">Dashboard</span>
|
<span className="text-sm hover:underline">Dashboard</span>
|
||||||
@ -28,9 +34,12 @@ const FeedbackMemberMaster = () => {
|
|||||||
<div className="grid gap-5 lg:gap-7.5">
|
<div className="grid gap-5 lg:gap-7.5">
|
||||||
<DataGridInner />
|
<DataGridInner />
|
||||||
</div>
|
</div>
|
||||||
|
<FeedbackDetail />
|
||||||
|
<EditDialog />
|
||||||
{/* <AddDialog />
|
{/* <AddDialog />
|
||||||
<EditDialog />
|
<EditDialog />
|
||||||
<DeleteDialog /> */}
|
<DeleteDialog /> */}
|
||||||
|
{/* <FeedbackDetail/> */}
|
||||||
</Container>
|
</Container>
|
||||||
</ManageFeedbackMemberProvider>
|
</ManageFeedbackMemberProvider>
|
||||||
</>
|
</>
|
||||||
|
|||||||
189
src/pages/members/feedback-member/blocks/EditDialog.tsx
Normal file
189
src/pages/members/feedback-member/blocks/EditDialog.tsx
Normal file
@ -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<StatusCode, string> = {
|
||||||
|
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<StatusCode>('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 (
|
||||||
|
<Dialog open={showEditDialog} onOpenChange={(open) => handleEditDialog(open, null)}>
|
||||||
|
<DialogContent className="p-0 w-full sm:max-w-2xl bg-white rounded-lg overflow-hidden">
|
||||||
|
<div className="relative">
|
||||||
|
<div className="p-6 flex items-center justify-between border-b">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900">Review Feedback</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">Review and update feedback status</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="absolute right-6 top-6 text-gray-400 hover:text-gray-600"
|
||||||
|
onClick={() => handleEditDialog(false, null)}
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="p-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<label className="block text-gray-700 text-lg">
|
||||||
|
Review Notes <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="w-full min-h-48 p-4 text-gray-700 bg-white border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Enter your review notes here..."
|
||||||
|
value={reviewNotes}
|
||||||
|
onChange={(e) => setReviewNotes(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<label className="block text-gray-700 text-lg">
|
||||||
|
Status <span className="text-red-500">*</span>
|
||||||
|
</label>
|
||||||
|
<Select
|
||||||
|
value={status}
|
||||||
|
onValueChange={(value: string) => setStatus(value as StatusCode)}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full p-3 text-gray-700">
|
||||||
|
<SelectValue placeholder="Select status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Y">Accept</SelectItem>
|
||||||
|
<SelectItem value="N">Reject</SelectItem>
|
||||||
|
<SelectItem value="W">Waiting</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-4 mt-8">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleEditDialog(false, null)}
|
||||||
|
disabled={loading}
|
||||||
|
className="px-6 py-3 h-12 border border-gray-300 text-gray-700 bg-white hover:bg-gray-50 rounded-md"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="px-6 py-3 h-12 bg-blue-600 hover:bg-blue-700 text-white rounded-md"
|
||||||
|
>
|
||||||
|
{loading ? 'Saving...' : 'Save Changes'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditDialog;
|
||||||
@ -1,199 +1,304 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import {
|
||||||
import { useCallApi } from '@/hooks';
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
import { apiConfig } from '@/config/api.config';
|
import { apiConfig } from '@/config/api.config';
|
||||||
|
import { useCallApi } from '@/hooks';
|
||||||
import { format } from 'date-fns';
|
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 {
|
interface ImageModalProps {
|
||||||
showDialog: boolean;
|
isOpen: boolean;
|
||||||
handleDialog: (show: boolean) => void;
|
onClose: () => void;
|
||||||
feedbackId: string | null;
|
imageUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FeedbackDetailData {
|
// New Image Modal Component
|
||||||
Feedback_id: string;
|
const ImageModal: React.FC<ImageModalProps> = ({ isOpen, onClose, imageUrl }) => {
|
||||||
Feedback_feedback_notes: string;
|
if (!isOpen) return null;
|
||||||
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<FeedbackDetailProps> = ({ showDialog, handleDialog, feedbackId }) => {
|
|
||||||
const [feedbackDetail, setFeedbackDetail] = useState<FeedbackDetailData | null>(null);
|
|
||||||
const [loading, setLoading] = useState<boolean>(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<string, string> = {
|
|
||||||
'1': 'Very Disappointed',
|
|
||||||
'2': 'Disappointed',
|
|
||||||
'3': 'Happy',
|
|
||||||
'4': 'Very Happy',
|
|
||||||
'5': 'Extremely Happy'
|
|
||||||
};
|
|
||||||
return emotions[emotion] || emotion;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusText = (status: string) => {
|
|
||||||
const statuses: Record<string, string> = {
|
|
||||||
'W': 'Waiting',
|
|
||||||
'P': 'Processed',
|
|
||||||
'D': 'Done'
|
|
||||||
};
|
|
||||||
return statuses[status] || status;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showDialog} onOpenChange={handleDialog}>
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75">
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
<div className="relative w-11/12 h-5/6 max-w-4xl">
|
||||||
<DialogHeader>
|
<button
|
||||||
<DialogTitle>Feedback Detail</DialogTitle>
|
onClick={onClose}
|
||||||
</DialogHeader>
|
className="absolute top-4 right-4 bg-white rounded-full p-1 shadow-lg z-10"
|
||||||
|
>
|
||||||
{loading ? (
|
<X size={24} className="text-gray-800" />
|
||||||
<div className="flex justify-center items-center p-8">
|
</button>
|
||||||
<div className="spinner-border text-primary" role="status">
|
<img src={imageUrl} alt="Enlarged view" className="w-full h-full object-contain" />
|
||||||
<span className="visually-hidden">Loading...</span>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
) : feedbackDetail ? (
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Feedback ID</label>
|
|
||||||
<div>{feedbackDetail.Feedback_id}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Category</label>
|
|
||||||
<div>{feedbackDetail.Feedback_category}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Emotion</label>
|
|
||||||
<div>{getEmotionText(feedbackDetail.Feedback_emotion)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Status</label>
|
|
||||||
<div>{getStatusText(feedbackDetail.Feedback_status)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Created At</label>
|
|
||||||
<div>{formatDate(feedbackDetail.Feedback_created_at)}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Created By</label>
|
|
||||||
<div>{feedbackDetail.Feedback_createdById}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Feedback Notes</label>
|
|
||||||
<div className="p-3 bg-gray-50 rounded">{feedbackDetail.Feedback_feedback_notes}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{feedbackDetail.Feedback_feedback_screenshoot && (
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Feedback Screenshot</label>
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
src={feedbackDetail.Feedback_feedback_screenshoot.replace(/^'|'$/g, '')}
|
|
||||||
alt="Feedback Screenshot"
|
|
||||||
className="max-h-64 rounded"
|
|
||||||
onError={(e) => {
|
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
|
||||||
(e.target as HTMLImageElement).parentElement?.appendChild(
|
|
||||||
Object.assign(document.createElement('div'), {
|
|
||||||
className: 'text-sm text-gray-500',
|
|
||||||
textContent: 'Image not available or invalid URL'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Review Notes</label>
|
|
||||||
<div className="p-3 bg-gray-50 rounded">{feedbackDetail.Feedback_review_notes || '-'}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{feedbackDetail.Feedback_review_screenshoot && (
|
|
||||||
<div className="form-group">
|
|
||||||
<label className="form-label font-semibold">Review Screenshot</label>
|
|
||||||
<div>
|
|
||||||
<img
|
|
||||||
src={feedbackDetail.Feedback_review_screenshoot}
|
|
||||||
alt="Review Screenshot"
|
|
||||||
className="max-h-64 rounded"
|
|
||||||
onError={(e) => {
|
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
|
||||||
(e.target as HTMLImageElement).parentElement?.appendChild(
|
|
||||||
Object.assign(document.createElement('div'), {
|
|
||||||
className: 'text-sm text-gray-500',
|
|
||||||
textContent: 'Image not available or invalid URL'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-4 text-center text-gray-500">No feedback details found</div>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FeedbackDetail;
|
const ScreenshotGrid = ({ title, images }: { title: string; images: string[] }) => {
|
||||||
|
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const openImageModal = (url: string) => {
|
||||||
|
setSelectedImage(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeImageModal = () => {
|
||||||
|
setSelectedImage(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-semibold text-gray-600">{title}</h3>
|
||||||
|
{images && images.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{images.map((url, index) => (
|
||||||
|
<div
|
||||||
|
key={`${title}-${index}`}
|
||||||
|
className="relative h-48 rounded-md overflow-hidden border border-gray-200 bg-white cursor-pointer hover:opacity-90 transition-opacity"
|
||||||
|
onClick={() => url && openImageModal(url)}
|
||||||
|
>
|
||||||
|
{url ? (
|
||||||
|
<img
|
||||||
|
src={url}
|
||||||
|
alt={`${title} ${index + 1}`}
|
||||||
|
className="w-full h-full object-cover rounded-md"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center items-center h-full bg-gray-100">
|
||||||
|
<ImageIcon size={48} className="text-gray-400" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-800">N/A</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<ImageModal
|
||||||
|
isOpen={!!selectedImage}
|
||||||
|
onClose={closeImageModal}
|
||||||
|
imageUrl={selectedImage || ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FeedbackDetail = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [feedbackDetail, setFeedbackDetail] = useState<any>(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<string, string> = {
|
||||||
|
'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<string, { label: string; color: string }> = {
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
open={showDetailDialog}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
setFeedbackDetail(null);
|
||||||
|
}
|
||||||
|
handleDetailDialog(open, open ? selectedFeedback : null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="sm:max-w-4xl rounded-2xl p-6 shadow-lg border border-gray-200">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-xl font-bold">Feedback Detail</DialogTitle>
|
||||||
|
<DialogDescription className="text-sm text-gray-500"></DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="flex justify-center items-center h-60">
|
||||||
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary" />
|
||||||
|
</div>
|
||||||
|
) : feedbackDetail ? (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="p-4 border border-gray-200 rounded-lg">
|
||||||
|
<h3 className="text-md font-semibold mb-4 border-b pb-2">Feedback Information</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Feedback ID</p>
|
||||||
|
<p className="text-sm text-gray-800">{formatValue(feedbackDetail.id)}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Created By</p>
|
||||||
|
<p className="text-sm text-gray-800">
|
||||||
|
{formatValue(feedbackDetail.created_by?.fullname)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Created Date</p>
|
||||||
|
<p className="text-sm text-gray-800">
|
||||||
|
{formatDateTime(feedbackDetail.created_at)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Emotion</p>
|
||||||
|
<p className="text-sm text-gray-800">
|
||||||
|
{getEmotionLabel(feedbackDetail.emotion)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Feedback Notes</p>
|
||||||
|
<p className="text-sm text-gray-800 whitespace-pre-wrap mt-1 p-2 bg-gray-50 rounded">
|
||||||
|
{formatValue(feedbackDetail.feedback_notes)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 border border-gray-200 rounded-lg">
|
||||||
|
<h3 className="text-md font-semibold mb-4 border-b pb-2">Feedback Screenshots</h3>
|
||||||
|
<ScreenshotGrid title="" images={feedbackDetail.feedback_screenshoot} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4 border border-gray-200 rounded-lg">
|
||||||
|
<h3 className="text-md font-semibold mb-4 border-b pb-2">Review Information</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Reviewed By</p>
|
||||||
|
<p className="text-sm text-gray-800">{formatValue(feedbackDetail.review_by)}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Status</p>
|
||||||
|
<p
|
||||||
|
className={`text-sm font-semibold ${getStatusLabel(feedbackDetail.status).color}`}
|
||||||
|
>
|
||||||
|
{getStatusLabel(feedbackDetail.status).label}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-4">
|
||||||
|
<p className="text-sm font-semibold text-gray-600">Review Notes</p>
|
||||||
|
<p className="text-sm text-gray-800 whitespace-pre-wrap mt-1 p-2 bg-gray-50 rounded">
|
||||||
|
{formatValue(feedbackDetail.review_notes)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{feedbackDetail.status !== 'C' && feedbackDetail.status !== 'R' && (
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={handleReviewClick}
|
||||||
|
className="bg-primary hover:bg-primary/90 text-white"
|
||||||
|
>
|
||||||
|
Review Feedback
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FeedbackDetail;
|
||||||
|
|||||||
@ -2,10 +2,11 @@ import { DataGridColumnHeader, DataGridProvider, KeenIcon } from '@/components';
|
|||||||
import { apiConfig } from '@/config/api.config';
|
import { apiConfig } from '@/config/api.config';
|
||||||
import { useCallApi } from '@/hooks';
|
import { useCallApi } from '@/hooks';
|
||||||
import { ColumnDef } from '@tanstack/react-table';
|
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 { Toaster } from 'sonner';
|
||||||
// import FeedbackDetail from './blocks/FeedbackDetail';
|
|
||||||
import FeedbackDetail from '../blocks/FeedbackDetail';
|
import FeedbackDetail from '../blocks/FeedbackDetail';
|
||||||
|
import FeedbackEditDialog from '../blocks/EditDialog';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
|
||||||
interface feedbackProps {
|
interface feedbackProps {
|
||||||
id: string;
|
id: string;
|
||||||
@ -17,17 +18,20 @@ interface feedbackProps {
|
|||||||
agentId: string;
|
agentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FetchParams {
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
sorting: any[];
|
||||||
|
columnFilters: any[];
|
||||||
|
}
|
||||||
|
|
||||||
interface ContextProps {
|
interface ContextProps {
|
||||||
feedback: feedbackProps[];
|
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;
|
showDetailDialog: boolean;
|
||||||
handleDetailDialog: (show: boolean, selected_sucos: string | null) => void;
|
handleDetailDialog: (show: boolean, selected_feedback: string | null) => void;
|
||||||
selectedfeedback: string | null;
|
selectedFeedback: string | null;
|
||||||
|
showEditDialog: boolean;
|
||||||
|
handleEditDialog: (show: boolean, selected_feedback: string | null) => void;
|
||||||
getfeedbackLists: (
|
getfeedbackLists: (
|
||||||
limit: number,
|
limit: number,
|
||||||
page: number,
|
page: number,
|
||||||
@ -35,20 +39,18 @@ interface ContextProps {
|
|||||||
order_field: any,
|
order_field: any,
|
||||||
order_direction: any
|
order_direction: any
|
||||||
) => Promise<{ data: feedbackProps[]; totalCount: number } | undefined>;
|
) => Promise<{ data: feedbackProps[]; totalCount: number } | undefined>;
|
||||||
|
refreshData: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialProps: ContextProps = {
|
const initialProps: ContextProps = {
|
||||||
feedback: [],
|
feedback: [],
|
||||||
showEditDialog: false,
|
|
||||||
handleEditDialog: () => {},
|
|
||||||
showAddDialog: false,
|
|
||||||
handleAddDialog: () => {},
|
|
||||||
showDeleteDialog: false,
|
|
||||||
handleDeleteDialog: () => {},
|
|
||||||
showDetailDialog: false,
|
showDetailDialog: false,
|
||||||
handleDetailDialog: () => {},
|
handleDetailDialog: () => {},
|
||||||
selectedfeedback: null,
|
selectedFeedback: null,
|
||||||
getfeedbackLists: async () => undefined
|
showEditDialog: false,
|
||||||
|
handleEditDialog: () => {},
|
||||||
|
getfeedbackLists: async () => undefined,
|
||||||
|
refreshData: () => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ManageFeedbackMemberContext = createContext<ContextProps>(initialProps);
|
const ManageFeedbackMemberContext = createContext<ContextProps>(initialProps);
|
||||||
@ -60,32 +62,37 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
|
|||||||
const [showEditDialog, setShowEditDialog] = useState(false);
|
const [showEditDialog, setShowEditDialog] = useState(false);
|
||||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||||
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
const [showDetailDialog, setShowDetailDialog] = useState(false);
|
||||||
const [selectedfeedback, setSelectedfeedback] = useState<string | null>(null);
|
const [selectedFeedback, setSelectedFeedback] = useState<string | null>(null);
|
||||||
const { GetData } = useCallApi();
|
const { GetData } = useCallApi();
|
||||||
|
|
||||||
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||||
|
|
||||||
|
const [lastFetchParams, setLastFetchParams] = useState<FetchParams | null>(null);
|
||||||
|
|
||||||
const handleAddDialog = useCallback((show: boolean) => {
|
const handleAddDialog = useCallback((show: boolean) => {
|
||||||
setShowAddDialog(show);
|
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) => {
|
const handleDetailDialog = useCallback((show: boolean, selected_feedback: string | null) => {
|
||||||
setShowDetailDialog(show);
|
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<ColumnDef<any>[]>(
|
const columns = useMemo<ColumnDef<any>[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorFn: (row) => row.customer,
|
accessorFn: (row) => row.customerid,
|
||||||
id: 'customer_id',
|
id: 'customer_id',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Customer Name" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Customer Name" column={column} />,
|
||||||
enableSorting: false,
|
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',
|
id: 'feedback_emotion',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Emotion" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Emotion" column={column} />,
|
||||||
enableSorting: false,
|
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',
|
id: 'feedback_notes',
|
||||||
header: ({ column }) => <DataGridColumnHeader title="Feedback Notes" column={column} />,
|
header: ({ column }) => <DataGridColumnHeader title="Feedback Notes" column={column} />,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
@ -125,25 +132,14 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-icon btn-clear btn-light"
|
className="btn btn-sm btn-icon btn-clear btn-light"
|
||||||
onClick={() => handleDetailDialog(true, row.Feedback_id)}
|
onClick={() => {
|
||||||
|
setSelectedFeedback(row.feedbacks_id);
|
||||||
|
handleDetailDialog(true, row.feedbacks_id);
|
||||||
|
}}
|
||||||
title="View Details"
|
title="View Details"
|
||||||
>
|
>
|
||||||
<KeenIcon icon="eye" />
|
<KeenIcon icon="eye" />
|
||||||
</button>
|
</button>
|
||||||
{/* <button
|
|
||||||
className="btn btn-sm btn-icon btn-clear btn-light"
|
|
||||||
onClick={() => handleEditDialog(true, row.Feedback_id)}
|
|
||||||
title="Edit"
|
|
||||||
>
|
|
||||||
<KeenIcon icon="notepad-edit" />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-icon btn-clear btn-light"
|
|
||||||
onClick={() => handleDeleteDialog(true, row.Feedback_id)}
|
|
||||||
title="Delete"
|
|
||||||
>
|
|
||||||
<KeenIcon icon="trash" />
|
|
||||||
</button> */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -153,20 +149,26 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[handleEditDialog, handleDeleteDialog, handleDetailDialog]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getfeedbackLists = async (page: number, limit: number, sorting: any, filter: any) => {
|
const getfeedbackLists = async (page: number, limit: number, sorting: any, filter: any) => {
|
||||||
try {
|
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() };
|
filter = filter.length == 0 ? {} : { any: filter[0].value?.toLowerCase() };
|
||||||
const response = await GetData(`${API_URL}/feedback/list`, {
|
const response = await GetData(`${API_URL}/feedback/list`, {
|
||||||
limit,
|
limit,
|
||||||
page: page + 1,
|
page: page + 1,
|
||||||
with_deleted: false,
|
with_deleted: false,
|
||||||
order_field: sorting[0].id,
|
order_field: sorting[0].id,
|
||||||
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC',
|
order_direction: sorting[0].desc == false ? 'ASC' : 'DESC'
|
||||||
// filter: JSON.stringify(filter)
|
|
||||||
});
|
});
|
||||||
// console.log(response?.data);
|
// console.log(response?.data);
|
||||||
setFeedback(response?.data.list);
|
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 (
|
return (
|
||||||
<ManageFeedbackMemberContext.Provider
|
<ManageFeedbackMemberContext.Provider
|
||||||
value={{
|
value={{
|
||||||
feedback,
|
feedback,
|
||||||
showEditDialog,
|
|
||||||
handleEditDialog,
|
|
||||||
showAddDialog,
|
|
||||||
handleAddDialog,
|
|
||||||
showDeleteDialog,
|
|
||||||
handleDeleteDialog,
|
|
||||||
showDetailDialog,
|
showDetailDialog,
|
||||||
handleDetailDialog,
|
handleDetailDialog,
|
||||||
selectedfeedback,
|
selectedFeedback,
|
||||||
getfeedbackLists
|
showEditDialog,
|
||||||
|
handleEditDialog,
|
||||||
|
getfeedbackLists,
|
||||||
|
refreshData
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Toaster expand visibleToasts={9} duration={3000} />
|
<Toaster expand visibleToasts={9} duration={3000} />
|
||||||
|
|
||||||
{/* Feedback Detail Modal Component */}
|
|
||||||
<FeedbackDetail
|
|
||||||
showDialog={showDetailDialog}
|
|
||||||
handleDialog={(show) => handleDetailDialog(show, show ? selectedfeedback : null)}
|
|
||||||
feedbackId={selectedfeedback}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DataGridProvider
|
<DataGridProvider
|
||||||
columns={columns}
|
columns={columns}
|
||||||
pagination={{ size: 10 }}
|
pagination={{ size: 10 }}
|
||||||
// toolbar={<ListToolbar />}
|
|
||||||
layout={{ card: true }}
|
layout={{ card: true }}
|
||||||
sorting={[{ id: 'created_at', desc: true }]}
|
sorting={[{ id: 'feedbacks_created_at', desc: true }]}
|
||||||
serverSide={true}
|
serverSide={true}
|
||||||
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
|
onFetchData={({ pageIndex, pageSize, sorting, columnFilters }) =>
|
||||||
getfeedbackLists(pageIndex, pageSize, sorting, columnFilters)
|
getfeedbackLists(pageIndex, pageSize, sorting, columnFilters)
|
||||||
@ -219,4 +221,4 @@ const ManageFeedbackMemberProvider = ({ children }: { children: React.ReactNode
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { ManageFeedbackMemberContext, ManageFeedbackMemberProvider };
|
export { ManageFeedbackMemberContext, ManageFeedbackMemberProvider };
|
||||||
export type { feedbackProps };
|
export type { feedbackProps };
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { ManageFeedbackMemberContext } from './ManageFeedbackMemberContext';
|
import { ManageFeedbackMemberContext } from './ManageFeedbackMemberContext';
|
||||||
|
|
||||||
const useManageProviderContext = () => {
|
const useManageFeedbackContext = () => {
|
||||||
const context = useContext(ManageFeedbackMemberContext);
|
const context = useContext(ManageFeedbackMemberContext);
|
||||||
|
|
||||||
if (!context) throw new Error('useManageProviderContext must be used within AuthProvider');
|
if (!context) throw new Error('useManageProviderContext must be used within AuthProvider');
|
||||||
@ -9,4 +9,4 @@ const useManageProviderContext = () => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { useManageProviderContext };
|
export { useManageFeedbackContext };
|
||||||
|
|||||||
Reference in New Issue
Block a user