%PDF- <> %âãÏÓ endobj 2 0 obj <> endobj 3 0 obj <>/ExtGState<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/Annots[ 28 0 R 29 0 R] /MediaBox[ 0 0 595.5 842.25] /Contents 4 0 R/Group<>/Tabs/S>> endobj ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<>endobj 2 0 obj<> endobj 2 0 obj<>endobj 2 0 obj<>es 3 0 R>> endobj 2 0 obj<> ox[ 0.000000 0.000000 609.600000 935.600000]/Fi endobj 3 0 obj<> endobj 7 1 obj<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/Subtype/Form>> stream
/* eslint-disable react-hooks/exhaustive-deps */ import React, { useState, useEffect, useRef } from "react"; import FileSaver from 'file-saver'; import BudgetService from "../../services/BudgetService"; import { AntdPaging, AntdSelect, AntdDatepicker } from "../../components"; import util from "../../util"; import { MONTH_NAMES } from "../../constants"; import { Input, InputNumber, Button, message, Modal, Card, Table, Row, Col } from 'antd'; import { ExclamationCircleOutlined, } from '@ant-design/icons'; const { confirm } = Modal; const $ = window.$; export default function BudgetList() { const [result, setResult] = useState({ data: [], page: {}, total: { amount: 0, gst: 0, payable_amount: 0 } }); const [loading, setLoading] = useState(false); const [mediums, setMediums] = useState([]); const [channels, setChannels] = useState([]); const formRef = useRef(); const sdataRef = useRef({ p: 1, ps: 25 }); const cols = [ { title: 'Month', dataIndex: 'month_name', }, { title: 'From Date', dataIndex: 'from_date', render: (text) => <div>{util.getDate(text, 'DD MMM YYYY')}</div> }, { title: 'To Date', dataIndex: 'to_date', render: (text) => <div>{util.getDate(text, 'DD MMM YYYY')}</div> }, { title: "Medium", dataIndex: "medium" }, { title: "Channel", dataIndex: "channel" }, { title: "Particular", dataIndex: "particular" }, { title: "Amount", dataIndex: "amount", align: "right" }, { title: "GST", dataIndex: "gst", align: "right", render: (text) => <div>{text}%</div> }, { title: "Payable Amount", dataIndex: "payable_amount", align: "right" }, { title: 'Created By', dataIndex: 'created_by_name', render: (text, row) => ( <div> {text} <div className="fs11 text-secondary"> @{util.getDate(row.created, 'DD MMM YYYY - hh:mm A')} </div> </div> ) }, { title: '', dataIndex: 'id', width: "86px", render: (id, row) => ( <div className="text-center"> <Button.Group size="small"> <Button type="default" onClick={() => formRef.current.open(row)}> <i className="fa fa-edit"></i> </Button> <Button type="default" onClick={() => deleteRecord(id)}> <i className="fa fa-times-circle text-danger"></i> </Button> </Button.Group> </div> ) } ]; const list = (p, ps) => { setLoading(true); sdataRef.current.p = p || 1; sdataRef.current.ps = ps || sdataRef.current.ps; BudgetService.list(sdataRef.current).then(({ data }) => { data.result.data.forEach((v, i) => { v.key = i; }); setResult(data.result); }).catch(e => { message.error(e.message); }).finally(() => { setLoading(false); }) } const downloadCsv = () => { let header = {}; cols.forEach(v => { if (v.title) { header[v.dataIndex] = v.title.toUpperCase(); } }); header.created = 'CREATED ON'; setLoading(true); BudgetService.all(sdataRef.current).then(({ data }) => { let records = data.result.data; records.forEach(v => { v.gst = v.gst + '%'; }) records.push({}); records.push({ from_date: 'Total Amount', to_date: data.result.total.amount }); records.push({ from_date: 'Total GST', to_date: data.result.total.gst }); records.push({ from_date: 'Total Payable Amount', to_date: data.result.total.payable_amount }); let blob = util.convertToCSVBlob(records, header); FileSaver.saveAs(blob, 'budget.csv'); }).catch(e => { message.error(e.message); }).finally(() => { setLoading(false); }) } const deleteRecord = (id) => { message.destroy(); confirm({ title: `Are you sure to delete this budget?`, icon: <ExclamationCircleOutlined />, content: '', okText: 'Yes', okType: 'danger', cancelText: 'No', onOk() { util.showLoader(); BudgetService.delete(id).then(({ data }) => { message.success(data.message || 'Deleted'); list(); }).catch(e => { message.error(e.message); }).finally(() => { util.hideLoader(); }) }, onCancel() { }, }) } useEffect(() => { list(); BudgetService.allMediums({ status: 1 }).then(res => { setMediums([...res.data.result.data]) }); BudgetService.allChannels({ status: 1 }).then(res => { setChannels([...res.data.result.data]) }); return () => { message.destroy() } }, []); const wh = $(window).height(); return ( <div> <Card size="small"> <div className="d-flex align-items-center justify-content-between"> <div> <SearchForm dataRef={sdataRef} onSearch={list} mediums={mediums} channels={channels} /> </div> <div> <Button type="primary" onClick={() => formRef.current.open()}>Add New</Button> </div> </div> </Card> <div className="pt10"> {result.data.length > 0 && <div className="d-flex align-items-center justify-content-between mb8"> <div> Showing {result.page.start + 1} - {result.page.start + result.page.total} of {result.page.total_records} records. </div> <div> Total Amount: <strong>{result.total.amount.toFixed(2)}</strong> | Total GST: <strong>{result.total.gst.toFixed(2)}</strong> | Total Payable Amount: <strong>{result.total.payable_amount.toFixed(2)}</strong> </div> <div> <Button type="dashed" size="small" onClick={downloadCsv}>Download CSV</Button> </div> </div> } <Card size="small" bodyStyle={{ padding: 0 }}> <Table size="small" bordered={false} dataSource={result.data} columns={cols} loading={loading} scroll={{ y: wh - 270 }} pagination={false} className="stripped" /> <div className="paging-box"> <AntdPaging onChange={list} total={result.page.total_records} current={result.page.cur_page} pageSize={sdataRef.current.ps} showSizeChanger /> </div> </Card> </div> <AddForm refOb={formRef} callback={list} pageno={sdataRef.current.p} mediums={mediums} channels={channels} /> </div> ) } const SearchForm = (props) => { let { dataRef, onSearch } = props; let [data, setData] = useState({ ...dataRef.current }); const [mediums, setMediums] = useState([]); const [channels, setChannels] = useState([]); const handleChange = (v, k) => { data[k] = v; setData({ ...data }); } useEffect(() => { dataRef.current = { ...data }; }, [data]); useEffect(() => { setData({ ...data, p: dataRef.current.p, ps: dataRef.current.ps }); }, [dataRef.current.p, dataRef.current.ps]); useEffect(() => { setMediums([...props.mediums]); }, [props.mediums]); useEffect(() => { setChannels([...props.channels]); }, [props.channels]); return ( <form onSubmit={e => e.preventDefault()} autoComplete="off" spellCheck="false"> <div className="d-flex align-items-center"> <div className="w200 mr5"> <AntdSelect showSearch allowClear placeholder="Medium (All)" options={mediums} value={data.medium_id} onChange={v => { data.channel_id = null; handleChange(v, 'medium_id'); }} /> </div> <div className="w200 mr5"> <AntdSelect showSearch allowClear placeholder="Channel (All)" options={channels.filter(v => v.medium_id === data.medium_id)} value={data.channel_id} onChange={v => { handleChange(v, 'channel_id'); }} /> </div> <div className="mr5"> <Input placeholder="Search" allowClear value={data.k} onChange={e => handleChange(e.target.value, 'k')} /> </div> <div> <Button type="primary" onClick={() => onSearch()}>Search</Button> </div> </div> </form> ) } const AddForm = (props) => { let { callback, pageno, refOb, mediums, channels } = props; const [showModal, setShowModal] = useState(false); let [data, setData] = useState({}); const handleChange = (v, k) => { data[k] = v; setData({ ...data }); } const handleOk = () => { message.destroy(); util.showLoader("Saving..."); BudgetService.save(data).then(({ data }) => { message.success(data.message || 'Saved'); callback(data.id ? pageno : 1); setShowModal(false); }).catch(e => { message.error(e.message); }).finally(() => { util.hideLoader(); }) } const handleCancel = () => { setShowModal(false); } refOb.current = { ...refOb.current, open: (dtl) => { setData(dtl ? { ...dtl } : {}); setShowModal(true); } } return ( <Modal title={`${data.id ? 'Edit' : 'Add'} Budget`} open={showModal} okText="Save" onOk={handleOk} onCancel={handleCancel} destroyOnClose maskClosable={false} width={500} style={{ top: 20 }} > <form onSubmit={e => { e.preventDefault(); handleOk() }} autoComplete="off" spellCheck="false"> <div className="form-group"> <Row gutter={[16, 16]}> <Col span={8}> <label className="req">Month</label> <AntdSelect showSearch allowClear placeholder="Select" options={MONTH_NAMES.map((m, i) => ({ value: i + 1, label: m }))} value={data.month} onChange={v => { handleChange(v, 'month'); }} /> </Col> <Col span={8}> <label className="req">From Date</label> <AntdDatepicker onChange={dt => handleChange(dt, 'from_date')} value={data.from_date} /> </Col> <Col span={8}> <label className="req">To Date</label> <AntdDatepicker onChange={dt => handleChange(dt, 'to_date')} value={data.to_date} /> </Col> </Row> </div> <div className="form-group"> <label className="req">Medium</label> <AntdSelect showSearch allowClear placeholder="Select" options={mediums} value={data.medium_id} onChange={v => { data.channel_id = null; handleChange(v, 'medium_id'); }} /> </div> <div className="form-group"> <label className="req">Channel</label> <AntdSelect showSearch allowClear placeholder="Select" options={channels.filter(v => v.medium_id === data.medium_id)} value={data.channel_id} onChange={v => { handleChange(v, 'channel_id'); }} /> </div> <div className="form-group"> <label className="req">Particular</label> <Input value={data.particular || ''} onChange={e => handleChange(e.target.value, 'particular')} /> </div> <div className="form-group"> <label className="req">Amount</label> <InputNumber style={{ width: '100%' }} value={data.amount || ''} onChange={v => { data.payable_amount = util.nv(v) + util.nv(v) * util.nv(data.gst) / 100; handleChange(v, 'amount') }} /> </div> <div className="form-group"> <label className="req">GST (%)</label> <InputNumber style={{ width: '100%' }} value={data.gst || ''} onChange={v => { data.payable_amount = util.nv(data.amount) + util.nv(data.amount) * util.nv(v) / 100; handleChange(v, 'gst') }} /> </div> <div className="form-group"> <label className="req">Payable Amount</label> <InputNumber value={data.payable_amount || ''} onChange={v => handleChange(v, 'payable_amount')} style={{ width: '100%' }} /> </div> </form> </Modal> ) }