update params customer_phone on module notification
This commit is contained in:
@ -26,9 +26,10 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { useManageNotificationContext } from '../hooks/useManageNotificationContext';
|
import { useManageNotificationContext } from '../hooks/useManageNotificationContext';
|
||||||
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
import { doSaveLogActivity } from '@/actions/GlobalActions';
|
||||||
import { CustomerProps } from '@/pages/master/provider/blocks/AddDialog';
|
import { CustomerProps } from '@/pages/master/provider/blocks/AddDialog';
|
||||||
import { Check, ChevronDown } from 'lucide-react';
|
import { Check, ChevronDown, X } from 'lucide-react';
|
||||||
import { initialStateNotification, selectedNotification, validateFormNotification } from './Types';
|
import { initialStateNotification, selectedNotification, validateFormNotification } from './Types';
|
||||||
import { useDebounce } from './useDebounce';
|
import { useDebounce } from './useDebounce';
|
||||||
|
import Papa from 'papaparse';
|
||||||
|
|
||||||
const API_URL_CUSTOMER = apiConfig.service_customer;
|
const API_URL_CUSTOMER = apiConfig.service_customer;
|
||||||
const API_URL_NOTIFICATION = apiConfig.service_notification;
|
const API_URL_NOTIFICATION = apiConfig.service_notification;
|
||||||
@ -42,16 +43,23 @@ const AddDialog = () => {
|
|||||||
const [errors, setErrors] = useState<Record<string, string>>({});
|
const [errors, setErrors] = useState<Record<string, string>>({});
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const debouncedSearchQuery = useDebounce(searchQuery, 500);
|
const debouncedSearchQuery = useDebounce(searchQuery, 500);
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const [formField, setFormField] = useState<selectedNotification>(initialStateNotification);
|
const [formField, setFormField] = useState<selectedNotification>(initialStateNotification);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
const [customers, setCustomers] = useState<CustomerProps[]>([]);
|
||||||
|
const [uploadedFileName, setUploadedFileName] = useState<string>('');
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setFormField({ ...initialStateNotification });
|
setFormField({ ...initialStateNotification });
|
||||||
setIsSubmitting(false);
|
setIsSubmitting(false);
|
||||||
setAlert({ show: false, message: '' });
|
setAlert({ show: false, message: '' });
|
||||||
setErrors({});
|
setErrors({});
|
||||||
|
setUploadedFileName('');
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
@ -79,7 +87,6 @@ const AddDialog = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await PostData(`${API_URL_NOTIFICATION}/send`, formField);
|
const response = await PostData(`${API_URL_NOTIFICATION}/send`, formField);
|
||||||
// console.log('send response :', response);
|
|
||||||
|
|
||||||
if (response?.status) {
|
if (response?.status) {
|
||||||
handleAddDialog(false);
|
handleAddDialog(false);
|
||||||
@ -148,6 +155,90 @@ const AddDialog = () => {
|
|||||||
}
|
}
|
||||||
}, [showAddDialog]);
|
}, [showAddDialog]);
|
||||||
|
|
||||||
|
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
// Validasi tipe file
|
||||||
|
if (!file.name.endsWith('.csv')) {
|
||||||
|
toast.error('Please upload a CSV file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setUploadedFileName(file.name);
|
||||||
|
|
||||||
|
Papa.parse(file, {
|
||||||
|
header: true,
|
||||||
|
skipEmptyLines: true,
|
||||||
|
dynamicTyping: false,
|
||||||
|
complete: (results) => {
|
||||||
|
try {
|
||||||
|
const phoneNumbers: string[] = [];
|
||||||
|
|
||||||
|
// Extract phone numbers from CSV
|
||||||
|
results.data.forEach((row: any) => {
|
||||||
|
// Cari kolom yang berisi nomor telepon (bisa MSISDN, phone, atau nama kolom lain)
|
||||||
|
const phone = row.MSISDN || row.msisdn || row.phone || row.Phone || row.PHONE;
|
||||||
|
|
||||||
|
if (phone) {
|
||||||
|
// Bersihkan nomor telepon dari karakter non-digit
|
||||||
|
const cleanPhone = String(phone).replace(/\D/g, '');
|
||||||
|
|
||||||
|
if (cleanPhone && cleanPhone.length >= 10) {
|
||||||
|
phoneNumbers.push(cleanPhone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (phoneNumbers.length === 0) {
|
||||||
|
toast.error('No valid phone numbers found in CSV. Please check column name (MSISDN)');
|
||||||
|
setUploadedFileName('');
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update form field dengan phone numbers
|
||||||
|
setFormField((prev) => ({
|
||||||
|
...prev,
|
||||||
|
customer_phone: phoneNumbers
|
||||||
|
}));
|
||||||
|
|
||||||
|
toast.success(`${phoneNumbers.length} phone numbers extracted successfully`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing CSV:', error);
|
||||||
|
toast.error('Failed to parse CSV file');
|
||||||
|
setUploadedFileName('');
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error('Error reading CSV:', error);
|
||||||
|
toast.error('Error reading CSV file');
|
||||||
|
setUploadedFileName('');
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFile = () => {
|
||||||
|
setFormField((prev) => ({
|
||||||
|
...prev,
|
||||||
|
customer_phone: []
|
||||||
|
}));
|
||||||
|
setUploadedFileName('');
|
||||||
|
if (fileInputRef.current) {
|
||||||
|
fileInputRef.current.value = '';
|
||||||
|
}
|
||||||
|
toast.info('Phone numbers cleared');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={showAddDialog} onOpenChange={handleAddDialog}>
|
<Dialog open={showAddDialog} onOpenChange={handleAddDialog}>
|
||||||
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
|
<DialogContent className="container-fixed max-w-[768px] flex flex-col p-5 overflow-hidden">
|
||||||
@ -358,6 +449,7 @@ const AddDialog = () => {
|
|||||||
checked={formField.via === 'whatsapp'}
|
checked={formField.via === 'whatsapp'}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="accent-blue-600"
|
className="accent-blue-600"
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
<span>WhatsApp</span>
|
<span>WhatsApp</span>
|
||||||
</label>
|
</label>
|
||||||
@ -372,6 +464,38 @@ const AddDialog = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||||
|
<label className="form-label flex items-center gap-1 max-w-56">
|
||||||
|
Upload MSISDN CSV
|
||||||
|
</label>
|
||||||
|
<div className="flex-1 flex flex-col gap-2">
|
||||||
|
<Input
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
accept=".csv"
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
className="input col-span-6"
|
||||||
|
/>
|
||||||
|
{uploadedFileName && formField.customer_phone.length > 0 && (
|
||||||
|
<div className="flex items-center gap-2 text-sm text-green-600 bg-green-50 px-3 py-2 rounded-md">
|
||||||
|
<Check className="w-4 h-4" />
|
||||||
|
<span>
|
||||||
|
{uploadedFileName} - {formField.customer_phone.length} phone numbers
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleRemoveFile}
|
||||||
|
className="ml-auto text-red-500 hover:text-red-700"
|
||||||
|
>
|
||||||
|
<X className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
<div className="flex items-baseline flex-wrap lg:flex-nowrap gap-2.5">
|
||||||
<label className="form-label flex items-center gap-1 max-w-56">
|
<label className="form-label flex items-center gap-1 max-w-56">
|
||||||
@ -381,7 +505,7 @@ const AddDialog = () => {
|
|||||||
className="input col-span-6"
|
className="input col-span-6"
|
||||||
name="content"
|
name="content"
|
||||||
placeholder="Enter OTP (5 digits)"
|
placeholder="Enter OTP (5 digits)"
|
||||||
value={formField.content}
|
// value={formField.content}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
if (/^\d{0,5}$/.test(value)) {
|
if (/^\d{0,5}$/.test(value)) {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export interface selectedNotification {
|
|||||||
via: string;
|
via: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
customer_phone: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialStateNotification: selectedNotification = {
|
export const initialStateNotification: selectedNotification = {
|
||||||
@ -15,7 +16,8 @@ export const initialStateNotification: selectedNotification = {
|
|||||||
type: '',
|
type: '',
|
||||||
via: '',
|
via: '',
|
||||||
subject: '',
|
subject: '',
|
||||||
content: ''
|
content: '',
|
||||||
|
customer_phone: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validateFormNotification = (
|
export const validateFormNotification = (
|
||||||
@ -47,8 +49,8 @@ export const validateFormNotification = (
|
|||||||
// Validasi khusus content
|
// Validasi khusus content
|
||||||
if (formField.via === 'whatsapp') {
|
if (formField.via === 'whatsapp') {
|
||||||
if (!formField.content || !/^\d{5}$/.test(formField.content)) {
|
if (!formField.content || !/^\d{5}$/.test(formField.content)) {
|
||||||
newErrors.content = 'Content harus berupa 5 digit angka (OTP)';
|
newErrors.content = 'Content must be a 5-digit number (OTP)';
|
||||||
toast.error('Content harus berupa 5 digit angka (OTP)');
|
toast.error('Content must be a 5-digit number (OTP)');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user