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

Bug where two calendars are rendered #525

Open
lukecharle opened this issue Oct 19, 2024 · 2 comments
Open

Bug where two calendars are rendered #525

lukecharle opened this issue Oct 19, 2024 · 2 comments

Comments

@lukecharle
Copy link

I'm having an issue where I keep getting at least two calendars being painted and it should be just the one, I've used the below code, trying to ensure it deletes any other instances but it isn't working.

Screenshot 2024-10-19 at 20 56 45
import { BlockStack, Button, InlineStack, Text } from '@shopify/polaris';
import { ChevronLeftIcon, ChevronRightIcon } from '@shopify/polaris-icons';
import CalHeatmap from 'cal-heatmap';
import CalendarLabel from 'cal-heatmap/plugins/CalendarLabel';
import LegendLite from 'cal-heatmap/plugins/LegendLite';
import Tooltip from 'cal-heatmap/plugins/Tooltip';
import { format } from 'date-fns';
import { useCallback, useEffect, useRef, useState } from 'react';

// Define constants at the top of the file, outside the component
const CELL_WIDTH = 11;
const CELL_HEIGHT = 11;
const CELL_RADIUS = 2;
const CELL_GUTTER = 4;
const CELLS_PER_MONTH = 4;
const DOMAIN_GUTTER = 4;

export default function StockCalendar({ chartData }) {
  const calRef = useRef(null);
  const calInstanceRef = useRef(null);
  const containerRef = useRef(null);

  const [range, setRange] = useState(8);

  const calculateRange = useCallback(() => {
    if (containerRef.current) {
      const containerWidth = containerRef.current.offsetWidth;

      // Calculate the width of one month (range) including the domain
      const monthWidth =
        CELL_WIDTH * CELLS_PER_MONTH + CELL_GUTTER * (CELLS_PER_MONTH - 1) + DOMAIN_GUTTER;

      // Calculate how many full months can fit
      const fullMonths = Math.floor(containerWidth / monthWidth);

      // Calculate the remaining width
      const remainingWidth = containerWidth - fullMonths * monthWidth;

      // Calculate the fraction of the next month that can fit
      const fractionOfNextMonth = remainingWidth / monthWidth;

      // If we can fit at least 50% of the next month, include it
      const newRange = fractionOfNextMonth >= 0.5 ? fullMonths + 1 : fullMonths;

      // Subtract two from the calculated range, but ensure at least 1 month is shown
      const finalRange = Math.max(1, newRange - 2);

      setRange(finalRange);
    }
  }, []);

  const initializeCalendar = useCallback(() => {
    if (calRef.current && !calInstanceRef.current && chartData.length > 0) {
      const cal = new CalHeatmap();
      cal.paint(
        {
          itemSelector: calRef.current,
          range: range,
          data: {
            source: chartData.map((item) => ({
              date: new Date(item.key).toISOString().split('T')[0],
              inventoryQuantity: item.value
            })),
            type: 'json',
            x: 'date',
            y: 'inventoryQuantity'
          },
          date: {
            start: new Date(new Date().getFullYear() - 1, 8, 21), // September 21st of the previous year
            min: new Date(new Date().getFullYear() - 1, 8, 21),
            max: new Date(new Date().getFullYear(), 8, 20) // September 20th of the current year
          },
          scale: {
            color: {
              type: 'threshold',
              range: ['#f47560', '#ede84e', '#2CB67D'],
              domain: [1, 11]
            }
          },
          domain: {
            type: 'month',
            gutter: DOMAIN_GUTTER,
            label: { text: 'MMM', textAlign: 'start', position: 'top' }
          },
          subDomain: {
            type: 'ghDay',
            radius: CELL_RADIUS,
            width: CELL_WIDTH,
            height: CELL_HEIGHT,
            gutter: CELL_GUTTER
          }
        },
        [
          [
            Tooltip,
            {
              text: function (date, value, dayjsDate) {
                let stockStatus = 'Out of stock';
                if (value > 0 && value <= 10) {
                  stockStatus = 'Low in stock';
                } else if (value > 10) {
                  stockStatus = 'In stock';
                }
                return `${stockStatus} (${value || 0} items) on ${dayjsDate.format('dddd, MMMM D, YYYY')}`;
              }
            }
          ],
          [
            LegendLite,
            {
              itemSelector: '#day-legend',
              radius: 2,
              width: 11,
              height: 11,
              gutter: 4
            }
          ],
          [
            CalendarLabel,
            {
              width: 30,
              textAlign: 'start',
              text: () =>
                Array.from({ length: 7 }, (_, i) =>
                  i % 2 === 0 ? '' : format(new Date(2023, 0, i + 1), 'EEE')
                ),
              padding: [25, 0, 0, 0]
            }
          ]
        ]
      );
      calInstanceRef.current = cal;
      console.log('New CalHeatmap instance created');
    }
  }, [chartData, range]);

  useEffect(() => {
    console.log('StockCalendar effect running');
    calculateRange();
    initializeCalendar();

    const resizeObserver = new ResizeObserver(() => {
      calculateRange();
    });

    if (containerRef.current) {
      resizeObserver.observe(containerRef.current);
    }

    // Cleanup function
    return () => {
      if (calInstanceRef.current) {
        console.log('Destroying CalHeatmap instance');
        calInstanceRef.current.destroy();
        calInstanceRef.current = null;
      }
      resizeObserver.disconnect();
    };
  }, [initializeCalendar, calculateRange]);

  const handlePrevious = () => calInstanceRef.current && calInstanceRef.current.previous();
  const handleNext = () => calInstanceRef.current && calInstanceRef.current.next();

  return (
    <div className="StockCalendar" ref={containerRef}>
      <BlockStack gap={400}>
        <div ref={calRef}></div>

        <InlineStack align="space-between" blockAlign="center">
          <InlineStack gap={200} blockAlign="center">
            <Button
              icon={ChevronLeftIcon}
              onClick={handlePrevious}
              disabled={!calInstanceRef.current}
              accessibilityLabel="Previous"
            />
            <Button
              icon={ChevronRightIcon}
              onClick={handleNext}
              disabled={!calInstanceRef.current}
              accessibilityLabel="Next"
            />
          </InlineStack>

          <InlineStack gap={200} align="end" blockAlign="center">
            <Text as="span" variant="bodyMd" tone="subdued">
              Out of stock
            </Text>
            <div id="day-legend" style={{ display: 'inline-block' }}></div>

            <Text as="span" variant="bodyMd" tone="subdued">
              In stock
            </Text>
          </InlineStack>
        </InlineStack>
      </BlockStack>
    </div>
  );
}
@baoziv587
Copy link

same

@baoziv587
Copy link

for now, a monkey patch to hidden it

.ch-container:nth-of-type(2){
    display: none;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants