Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Platform] Data download enhancement #2550

Open
wants to merge 2 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/platform/src/common/components/Calendar/Calendar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ const Calendar = ({
const CalendarSectionComponent = ({ month, onNextMonth, onPrevMonth }) => (
<div
className={`${
showTwoCalendars ? 'px-6 pt-5 pb-6' : 'px-2 pt-2 pb-5'
showTwoCalendars ? 'px-6 pt-5 pb-5' : 'px-2 pt-2'
} flex flex-col`}
>
<CalendarHeader
Expand Down
97 changes: 73 additions & 24 deletions src/platform/src/common/components/Calendar/DatePicker.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// DatePicker.js
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { format } from 'date-fns';
import { usePopper } from 'react-popper';
import { Transition } from '@headlessui/react';
Expand All @@ -8,15 +7,23 @@ import CalendarIcon from '@/icons/Analytics/calendarIcon';
import TabButtons from '../Button/TabButtons';

/**
* DatePicker component integrates the Calendar and manages its visibility.
* DatePicker component that integrates Calendar with react-popper.
* It manages its open/close state and renders the calendar in a popper with an arrow.
*/
const DatePicker = ({ customPopperStyle, alignment, onChange }) => {
const DatePicker = ({
customPopperStyle = {},
alignment = 'left',
onChange,
}) => {
const [isOpen, setIsOpen] = useState(false);
const [referenceElement, setReferenceElement] = useState(null);
const [popperElement, setPopperElement] = useState(null);
const [arrowElement, setArrowElement] = useState(null);

const [selectedDate, setSelectedDate] = useState({ start: null, end: null });
const popperRef = useRef(null);

// Configure react-popper
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: alignment === 'right' ? 'bottom-end' : 'bottom-start',
modifiers: [
Expand All @@ -38,41 +45,64 @@ const DatePicker = ({ customPopperStyle, alignment, onChange }) => {
},
{ name: 'computeStyles', options: { adaptive: false } },
{ name: 'eventListeners', options: { scroll: true, resize: true } },
{ name: 'arrow', options: { padding: 8 } },
{
name: 'arrow',
options: {
element: arrowElement, // attach arrow element
padding: 8,
},
},
],
});

const handleToggle = () => {
/**
* Toggles the calendar's open/close state.
*/
const toggleOpen = useCallback(() => {
setIsOpen((prev) => !prev);
};
}, []);

const handleValueChange = (newValue) => {
setSelectedDate(newValue);
onChange(newValue);
};
/**
* Called whenever the user selects a date range in the Calendar.
*/
const handleValueChange = useCallback(
(newValue) => {
setSelectedDate(newValue);
onChange?.(newValue);
},
[onChange],
);

const handleClickOutside = (event) => {
if (
popperRef.current &&
!popperRef.current.contains(event.target) &&
!referenceElement.contains(event.target)
) {
setIsOpen(false);
}
};
/**
* Closes the popper when clicking outside of it.
*/
const handleClickOutside = useCallback(
(event) => {
if (
popperRef.current &&
!popperRef.current.contains(event.target) &&
referenceElement &&
!referenceElement.contains(event.target)
) {
setIsOpen(false);
}
},
[referenceElement],
);

// Attach/detach outside click handler
useEffect(() => {
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
} else {
document.removeEventListener('mousedown', handleClickOutside);
}

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, referenceElement]);
}, [isOpen, handleClickOutside]);

// Format the selected date range for display
const formattedStartDate = selectedDate.start
? format(selectedDate.start, 'MMM d, yyyy')
: '';
Expand All @@ -86,19 +116,22 @@ const DatePicker = ({ customPopperStyle, alignment, onChange }) => {

return (
<div className="relative">
{/* The button that toggles the calendar */}
<TabButtons
Icon={<CalendarIcon />}
btnText={btnText}
tabButtonClass="w-full"
dropdown
onClick={handleToggle}
onClick={toggleOpen}
id="datePicker"
type="button"
btnStyle="w-full bg-white border-gray-750 px-4 py-2"
tabRef={setReferenceElement}
aria-haspopup="dialog"
aria-expanded={isOpen}
/>

{/* Transition for the popper (calendar container) */}
<Transition
show={isOpen}
enter="ease-out duration-300"
Expand All @@ -107,7 +140,6 @@ const DatePicker = ({ customPopperStyle, alignment, onChange }) => {
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
className="absolute z-50"
>
<div
ref={(node) => {
Expand All @@ -116,7 +148,24 @@ const DatePicker = ({ customPopperStyle, alignment, onChange }) => {
}}
style={{ ...styles.popper, ...customPopperStyle }}
{...attributes.popper}
className="z-50"
>
{/* The arrow element for popper */}
<div
ref={setArrowElement}
style={{
...styles.arrow,
width: 0,
height: 0,
borderLeft: '6px solid transparent',
borderRight: '6px solid transparent',
borderBottom: '6px solid white',
position: 'absolute',
}}
{...attributes.arrow}
/>
{/* Calendar container with reduced height */}

<Calendar
showTwoCalendars={false}
handleValueChange={handleValueChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,17 @@ import CustomDropdown from '../../../Dropdowns/CustomDropdown';
import DatePicker from '../../../Calendar/DatePicker';

/**
* Formats the name by replacing underscores and hyphens with spaces and adjusting case.
* Formats a string by replacing underscores/hyphens with spaces and adjusting its case.
*/
const formatName = (name, textFormat = 'lowercase') => {
if (typeof name !== 'string' || !name) return name;
const formatted = name.replace(/[_-]/g, ' ');
return textFormat === 'uppercase' ? formatted.toUpperCase() : formatted;
};

/**
* Defines the rules for formatting the field value based on the field id.
* Removes hyphens and formats in uppercase for display
* Retains hyphens in the stored value
*/
const FIELD_FORMAT_RULES = {
organization: {
display: (value) => formatName(value.replace(/[_-]/g, ' '), 'uppercase'),
display: (value) => formatName(value, 'uppercase'),
store: (value) => value,
},
default: {
Expand All @@ -38,7 +33,7 @@ const formatFieldValue = (value, fieldId, textFormat, display = false) => {

/**
* CustomFields Component
* Renders different types of input fields based on props.
* Renders different types of input fields based on the props.
*/
const CustomFields = ({
field = false,
Expand All @@ -53,13 +48,10 @@ const CustomFields = ({
defaultOption,
textFormat = 'lowercase',
}) => {
const [selectedOption, setSelectedOption] = useState(
defaultOption || (options.length > 0 ? options[0] : { id: '', name: '' }),
);
const initialOption =
defaultOption || (options.length > 0 ? options[0] : { id: '', name: '' });
const [selectedOption, setSelectedOption] = useState(initialOption);

/**
* Handles the selection of an option.
*/
const handleSelect = useCallback(
(option) => {
const formattedOption = {
Expand All @@ -73,26 +65,23 @@ const CustomFields = ({
);

return (
<div className="w-full h-auto flex flex-col gap-2 justify-start">
<label className="w-[280px] h-auto p-0 m-0 text-[#7A7F87]">{title}</label>

<div className="w-full flex flex-col gap-2">
<label className="w-[280px] text-[#7A7F87]">{title}</label>
{field ? (
<input
className="bg-transparent text-[16px] font-medium leading-6 p-0 m-0 w-full h-auto border-none"
type="text"
name={id}
className="bg-transparent text-[16px] font-medium leading-6 w-full border-none p-0 m-0"
value={formatFieldValue(selectedOption.name, id, textFormat, true)}
onChange={(e) =>
handleSelect({ ...selectedOption, name: e.target.value })
}
type="text"
name={id}
disabled={!edit}
/>
) : useCalendar ? (
<DatePicker
customPopperStyle={{ left: '-7px' }}
onChange={(date) => {
handleSelect({ name: date });
}}
onChange={(date) => handleSelect({ name: date })}
/>
) : (
<CustomDropdown
Expand Down Expand Up @@ -121,9 +110,7 @@ const CustomFields = ({
<span className="flex items-center space-x-2">
<span>{formatName(option.name, textFormat)}</span>
</span>
{selectedOption.id === option.id && (
<CheckIcon fill={'#145FFF'} />
)}
{selectedOption.id === option.id && <CheckIcon fill="#145FFF" />}
</span>
))}
</CustomDropdown>
Expand Down
Loading