color picker

This commit is contained in:
2025-10-24 11:46:58 +07:00
parent dd874cbe9c
commit 50d040953f
2 changed files with 104 additions and 116 deletions

View File

@@ -9,7 +9,7 @@ import {
Switch, Switch,
Row, Row,
Col, Col,
Radio, ColorPicker,
} from 'antd'; } from 'antd';
import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif'; import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
import { validateRun } from '../../../../Utils/validate'; import { validateRun } from '../../../../Utils/validate';
@@ -18,18 +18,6 @@ import { createStatus, updateStatus } from '../../../../api/master-status';
const { Text } = Typography; const { Text } = Typography;
const { TextArea } = Input; const { TextArea } = Input;
// Daftar 8 warna yang tersedia
const COLOR_OPTIONS = [
{ label: 'Merah', value: '#EF4444', hex: '#EF4444' },
{ label: 'Biru', value: '#3B82F6', hex: '#3B82F6' },
{ label: 'Hijau', value: '#10B981', hex: '#10B981' },
{ label: 'Kuning', value: '#F59E0B', hex: '#F59E0B' },
{ label: 'Ungu', value: '#8B5CF6', hex: '#8B5CF6' },
{ label: 'Pink', value: '#EC4899', hex: '#EC4899' },
{ label: 'Orange', value: '#F97316', hex: '#F97316' },
{ label: 'Teal', value: '#14B8A6', hex: '#14B8A6' },
];
const DetailStatus = (props) => { const DetailStatus = (props) => {
const [confirmLoading, setConfirmLoading] = useState(false); const [confirmLoading, setConfirmLoading] = useState(false);
@@ -57,8 +45,8 @@ const DetailStatus = (props) => {
setFormData({ ...formData, is_active: checked }); setFormData({ ...formData, is_active: checked });
}; };
const handleColorChange = (e) => { const handleColorChange = (color, hex) => {
setFormData({ ...formData, status_color: e.target.value }); setFormData({ ...formData, status_color: hex });
}; };
const handleCancel = () => { const handleCancel = () => {
@@ -213,38 +201,33 @@ const DetailStatus = (props) => {
<Text strong>Status Color</Text> <Text strong>Status Color</Text>
<Text style={{ color: 'red' }}> *</Text> <Text style={{ color: 'red' }}> *</Text>
<div style={{ marginTop: '8px' }}> <div style={{ marginTop: '8px' }}>
<Radio.Group <ColorPicker
value={formData.status_color || '#000000'}
onChange={handleColorChange} onChange={handleColorChange}
value={formData.status_color}
disabled={props.readOnly} disabled={props.readOnly}
> showText={(color) => `color hex: ${color.toHexString()}`}
<Row gutter={[8, 8]}> allowClear={false}
{COLOR_OPTIONS.map((color) => ( format="hex"
<Col span={12} key={color.value}> size="large"
<Radio value={color.value} style={{ width: '100%' }}> style={{ width: '100%' }}
<div presets={[
style={{ {
display: 'flex', label: 'Recommended',
alignItems: 'center', colors: [
gap: '8px', '#EF4444', // Merah
}} '#3B82F6', // Biru
> '#10B981', // Hijau
<div '#F59E0B', // Kuning
style={{ '#8B5CF6', // Ungu
width: '20px', '#EC4899', // Pink
height: '20px', '#F97316', // Orange
backgroundColor: color.hex, '#14B8A6', // Teal
borderRadius: '4px', '#6B7280', // Gray
border: '1px solid #d9d9d9', '#000000', // Black
}} ],
},
]}
/> />
<span>{color.label}</span>
</div>
</Radio>
</Col>
))}
</Row>
</Radio.Group>
</div> </div>
</div> </div>
<div style={{ marginBottom: 12 }}> <div style={{ marginBottom: 12 }}>

View File

@@ -1,5 +1,5 @@
import React, { memo, useState, useEffect } from 'react'; import React, { memo, useState, useEffect } from 'react';
import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input } from 'antd'; import { Space, Tag, ConfigProvider, Button, Row, Col, Card, Input, Modal } from 'antd';
import { import {
PlusOutlined, PlusOutlined,
EditOutlined, EditOutlined,
@@ -8,6 +8,7 @@ import {
SearchOutlined, SearchOutlined,
CheckOutlined, CheckOutlined,
CloseOutlined, CloseOutlined,
ClockCircleOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
@@ -49,13 +50,7 @@ const getRoleColor = (role_name, role_level) => {
return 'default'; return 'default';
}; };
const columns = ( const columns = (showPreviewModal, showEditModal, showDeleteDialog, showApprovalModal) => [
showPreviewModal,
showEditModal,
showDeleteDialog,
showApproveDialog,
showRejectDialog
) => [
{ {
title: 'ID', title: 'ID',
dataIndex: 'user_id', dataIndex: 'user_id',
@@ -116,32 +111,22 @@ const columns = (
render: (_, record) => { render: (_, record) => {
// is_approve: 0 = Rejected, 1 = Pending, 2 = Approved // is_approve: 0 = Rejected, 1 = Pending, 2 = Approved
if (record.is_approve === 1 || record.is_approve === '1') { if (record.is_approve === 1 || record.is_approve === '1') {
// Pending - show both Approve and Reject buttons // Pending - show single Pending button
return ( return (
<Space size="small" direction="vertical">
<Button <Button
type="primary" type="default"
size="small" size="small"
icon={<CheckOutlined />} icon={<ClockCircleOutlined />}
onClick={() => showApproveDialog(record)} onClick={() => showApprovalModal(record)}
style={{ style={{
backgroundColor: '#52c41a', backgroundColor: '#faad14',
borderColor: '#52c41a', borderColor: '#faad14',
color: 'white',
width: '100%', width: '100%',
}} }}
> >
Approve Pending
</Button> </Button>
<Button
danger
size="small"
icon={<CloseOutlined />}
onClick={() => showRejectDialog(record)}
style={{ width: '100%' }}
>
Reject
</Button>
</Space>
); );
} else if (record.is_approve === 0 || record.is_approve === '0') { } else if (record.is_approve === 0 || record.is_approve === '0') {
// Rejected // Rejected
@@ -233,6 +218,8 @@ const columns = (
const ListUser = memo(function ListUser(props) { const ListUser = memo(function ListUser(props) {
const [showFilter, setShowFilter] = useState(false); const [showFilter, setShowFilter] = useState(false);
const [trigerFilter, setTrigerFilter] = useState(false); const [trigerFilter, setTrigerFilter] = useState(false);
const [approvalModalVisible, setApprovalModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
const defaultFilter = { criteria: '' }; const defaultFilter = { criteria: '' };
const [formDataFilter, setFormDataFilter] = useState(defaultFilter); const [formDataFilter, setFormDataFilter] = useState(defaultFilter);
@@ -285,44 +272,30 @@ const ListUser = memo(function ListUser(props) {
props.setActionMode('add'); props.setActionMode('add');
}; };
const showApproveDialog = (param) => { const showApprovalModal = (param) => {
Swal.fire({ setSelectedUser(param);
icon: 'question', setApprovalModalVisible(true);
title: 'Konfirmasi Approve User',
text: 'Apakah anda yakin approve user "' + param.user_fullname + '" ?',
showCancelButton: true,
cancelButtonColor: '#d33',
cancelButtonText: 'Batal',
confirmButtonColor: '#23A55A',
confirmButtonText: 'Approve',
reverseButtons: true,
}).then((result) => {
if (result.isConfirmed) {
handleApprove(param.user_id);
} else if (result.dismiss) {
props.setSelectedData(null);
}
});
}; };
const showRejectDialog = (param) => { const handleModalApprove = () => {
Swal.fire({ if (selectedUser) {
icon: 'warning', handleApprove(selectedUser.user_id);
title: 'Konfirmasi Reject User', setApprovalModalVisible(false);
text: 'Apakah anda yakin reject user "' + param.user_fullname + '" ?', setSelectedUser(null);
showCancelButton: true,
cancelButtonColor: '#23A55A',
cancelButtonText: 'Batal',
confirmButtonColor: '#d33',
confirmButtonText: 'Reject',
reverseButtons: true,
}).then((result) => {
if (result.isConfirmed) {
handleReject(param.user_id);
} else if (result.dismiss) {
props.setSelectedData(null);
} }
}); };
const handleModalReject = () => {
if (selectedUser) {
handleReject(selectedUser.user_id);
setApprovalModalVisible(false);
setSelectedUser(null);
}
};
const handleModalCancel = () => {
setApprovalModalVisible(false);
setSelectedUser(null);
}; };
const showDeleteDialog = (param) => { const showDeleteDialog = (param) => {
@@ -470,14 +443,46 @@ const ListUser = memo(function ListUser(props) {
showPreviewModal, showPreviewModal,
showEditModal, showEditModal,
showDeleteDialog, showDeleteDialog,
showApproveDialog, showApprovalModal
showRejectDialog
)} )}
triger={trigerFilter} triger={trigerFilter}
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
{/* Approval Modal */}
<Modal
title="Konfirmasi Persetujuan User"
open={approvalModalVisible}
onCancel={handleModalCancel}
footer={[
<Button key="reject" danger onClick={handleModalReject}>
Reject User
</Button>,
<Button
key="approve"
type="primary"
style={{ backgroundColor: '#23A55A', borderColor: '#23A55A' }}
onClick={handleModalApprove}
>
Approve User
</Button>,
]}
width={400}
>
<div style={{ textAlign: 'center', padding: '20px 0' }}>
<ClockCircleOutlined
style={{ fontSize: '48px', color: '#faad14', marginBottom: '16px' }}
/>
<p style={{ fontSize: '16px', margin: '16px 0' }}>
User: <strong>{selectedUser?.user_fullname}</strong>
</p>
<p style={{ color: '#666', margin: '8px 0' }}>
Apakah Anda ingin approve atau reject user ini?
</p>
</div>
</Modal>
</React.Fragment> </React.Fragment>
); );
}); });