This commit is contained in:
rizky 2024-09-05 00:50:03 -07:00
parent 7c3d24255b
commit 69200c0ae5
5 changed files with 540 additions and 353 deletions

View File

@ -4,393 +4,464 @@ import React, { useCallback, useContext } from "react";
import { BG_COLOR, TEXT_COLOR } from "../../constants"; import { BG_COLOR, TEXT_COLOR } from "../../constants";
import DatepickerContext from "../../contexts/DatepickerContext"; import DatepickerContext from "../../contexts/DatepickerContext";
import { formatDate, nextMonth, previousMonth, classNames as cn } from "../../helpers"; import {
formatDate,
nextMonth,
previousMonth,
classNames as cn,
} from "../../helpers";
import { Period } from "../../types"; import { Period } from "../../types";
dayjs.extend(isBetween); dayjs.extend(isBetween);
interface Props { interface Props {
calendarData: { calendarData: {
date: dayjs.Dayjs; date: dayjs.Dayjs;
days: { days: {
previous: number[]; previous: number[];
current: number[]; current: number[];
next: number[]; next: number[];
};
}; };
onClickPreviousDays: (day: number) => void; };
onClickDay: (day: number) => void; onClickPreviousDays: (day: number) => void;
onClickNextDays: (day: number) => void; onClickDay: (day: number) => void;
onClickNextDays: (day: number) => void;
onIcon?: (day: number, date: Date) => any;
} }
const Days: React.FC<Props> = ({ const Days: React.FC<Props> = ({
calendarData, calendarData,
onClickPreviousDays, onClickPreviousDays,
onClickDay, onClickDay,
onClickNextDays onClickNextDays,
onIcon,
}) => { }) => {
// Contexts // Contexts
const { const {
primaryColor, primaryColor,
period, period,
changePeriod, changePeriod,
dayHover, dayHover,
changeDayHover, changeDayHover,
minDate, minDate,
maxDate, maxDate,
disabledDates disabledDates,
} = useContext(DatepickerContext); } = useContext(DatepickerContext);
// Functions // Functions
const currentDateClass = useCallback( const currentDateClass = useCallback(
(item: number) => { (item: number) => {
const itemDate = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${ const itemDate = `${calendarData.date.year()}-${
item >= 10 ? item : "0" + item calendarData.date.month() + 1
}`; }-${item >= 10 ? item : "0" + item}`;
if (formatDate(dayjs()) === formatDate(dayjs(itemDate))) if (formatDate(dayjs()) === formatDate(dayjs(itemDate)))
return TEXT_COLOR["500"][primaryColor as keyof (typeof TEXT_COLOR)["500"]]; return TEXT_COLOR["500"][
return ""; primaryColor as keyof (typeof TEXT_COLOR)["500"]
}, ];
[calendarData.date, primaryColor] return "";
); },
[calendarData.date, primaryColor]
);
const activeDateData = useCallback( const activeDateData = useCallback(
(day: number) => { (day: number) => {
const fullDay = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${day}`; const fullDay = `${calendarData.date.year()}-${
let className = ""; calendarData.date.month() + 1
}-${day}`;
let className = "";
if (dayjs(fullDay).isSame(period.start) && dayjs(fullDay).isSame(period.end)) { if (
className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium rounded-full`; dayjs(fullDay).isSame(period.start) &&
} else if (dayjs(fullDay).isSame(period.start)) { dayjs(fullDay).isSame(period.end)
className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium ${ ) {
dayjs(fullDay).isSame(dayHover) && !period.end className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium rounded-full`;
? "c-rounded-full" } else if (dayjs(fullDay).isSame(period.start)) {
: "c-rounded-l-full" className = ` ${
}`; BG_COLOR["500"][primaryColor]
} else if (dayjs(fullDay).isSame(period.end)) { } c-text-white c-font-medium ${
className = ` ${BG_COLOR["500"][primaryColor]} c-text-white c-font-medium ${ dayjs(fullDay).isSame(dayHover) && !period.end
dayjs(fullDay).isSame(dayHover) && !period.start ? "c-rounded-full"
? "c-rounded-full" : "c-rounded-l-full"
: "c-rounded-r-full" }`;
}`; } else if (dayjs(fullDay).isSame(period.end)) {
} className = ` ${
BG_COLOR["500"][primaryColor]
} c-text-white c-font-medium ${
dayjs(fullDay).isSame(dayHover) && !period.start
? "c-rounded-full"
: "c-rounded-r-full"
}`;
}
return { return {
active: dayjs(fullDay).isSame(period.start) || dayjs(fullDay).isSame(period.end), active:
className: className dayjs(fullDay).isSame(period.start) ||
}; dayjs(fullDay).isSame(period.end),
}, className: className,
[calendarData.date, dayHover, period.end, period.start, primaryColor] };
); },
[calendarData.date, dayHover, period.end, period.start, primaryColor]
);
const hoverClassByDay = useCallback( const hoverClassByDay = useCallback(
(day: number) => { (day: number) => {
let className = currentDateClass(day); let className = currentDateClass(day);
const fullDay = `${calendarData.date.year()}-${calendarData.date.month() + 1}-${ const fullDay = `${calendarData.date.year()}-${
day >= 10 ? day : "0" + day calendarData.date.month() + 1
}`; }-${day >= 10 ? day : "0" + day}`;
if (period.start && period.end) { if (period.start && period.end) {
if (dayjs(fullDay).isBetween(period.start, period.end, "day", "[)")) { if (dayjs(fullDay).isBetween(period.start, period.end, "day", "[)")) {
return ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( return ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass(
day day
)} dark:bg-white/10`; )} dark:bg-white/10`;
} }
} }
if (!dayHover) { if (!dayHover) {
return className; return className;
} }
if (period.start && dayjs(fullDay).isBetween(period.start, dayHover, "day", "[)")) { if (
className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( period.start &&
day dayjs(fullDay).isBetween(period.start, dayHover, "day", "[)")
)} dark:bg-white/10`; ) {
} className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass(
day
)} dark:bg-white/10`;
}
if (period.end && dayjs(fullDay).isBetween(dayHover, period.end, "day", "[)")) { if (
className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass( period.end &&
day dayjs(fullDay).isBetween(dayHover, period.end, "day", "[)")
)} dark:bg-white/10`; ) {
} className = ` ${BG_COLOR["100"][primaryColor]} ${currentDateClass(
day
)} dark:bg-white/10`;
}
if (dayHover === fullDay) { if (dayHover === fullDay) {
const bgColor = BG_COLOR["500"][primaryColor]; const bgColor = BG_COLOR["500"][primaryColor];
className = ` transition-all duration-500 text-white font-medium ${bgColor} ${ className = ` transition-all duration-500 text-white font-medium ${bgColor} ${
period.start ? "rounded-r-full" : "rounded-l-full" period.start ? "rounded-r-full" : "rounded-l-full"
}`; }`;
} }
return className; return className;
}, },
[calendarData.date, currentDateClass, dayHover, period.end, period.start, primaryColor] [
); calendarData.date,
currentDateClass,
dayHover,
period.end,
period.start,
primaryColor,
]
);
const isDateTooEarly = useCallback( const isDateTooEarly = useCallback(
(day: number, type: "current" | "previous" | "next") => { (day: number, type: "current" | "previous" | "next") => {
if (!minDate) { if (!minDate) {
return false; return false;
} }
const object = { const object = {
previous: previousMonth(calendarData.date), previous: previousMonth(calendarData.date),
current: calendarData.date, current: calendarData.date,
next: nextMonth(calendarData.date) next: nextMonth(calendarData.date),
}; };
const newDate = object[type as keyof typeof object]; const newDate = object[type as keyof typeof object];
const formattedDate = newDate.set("date", day); const formattedDate = newDate.set("date", day);
return dayjs(formattedDate).isSame(dayjs(minDate), "day") return dayjs(formattedDate).isSame(dayjs(minDate), "day")
? false ? false
: dayjs(formattedDate).isBefore(dayjs(minDate)); : dayjs(formattedDate).isBefore(dayjs(minDate));
}, },
[calendarData.date, minDate] [calendarData.date, minDate]
); );
const isDateTooLate = useCallback( const isDateTooLate = useCallback(
(day: number, type: "current" | "previous" | "next") => { (day: number, type: "current" | "previous" | "next") => {
if (!maxDate) { if (!maxDate) {
return false; return false;
} }
const object = { const object = {
previous: previousMonth(calendarData.date), previous: previousMonth(calendarData.date),
current: calendarData.date, current: calendarData.date,
next: nextMonth(calendarData.date) next: nextMonth(calendarData.date),
}; };
const newDate = object[type as keyof typeof object]; const newDate = object[type as keyof typeof object];
const formattedDate = newDate.set("date", day); const formattedDate = newDate.set("date", day);
return dayjs(formattedDate).isSame(dayjs(maxDate), "day") return dayjs(formattedDate).isSame(dayjs(maxDate), "day")
? false ? false
: dayjs(formattedDate).isAfter(dayjs(maxDate)); : dayjs(formattedDate).isAfter(dayjs(maxDate));
}, },
[calendarData.date, maxDate] [calendarData.date, maxDate]
); );
const isDateDisabled = useCallback( const isDateDisabled = useCallback(
(day: number, type: "current" | "previous" | "next") => { (day: number, type: "current" | "previous" | "next") => {
if (isDateTooEarly(day, type) || isDateTooLate(day, type)) { if (isDateTooEarly(day, type) || isDateTooLate(day, type)) {
return true; return true;
} }
const object = { const object = {
previous: previousMonth(calendarData.date), previous: previousMonth(calendarData.date),
current: calendarData.date, current: calendarData.date,
next: nextMonth(calendarData.date) next: nextMonth(calendarData.date),
}; };
const newDate = object[type as keyof typeof object]; const newDate = object[type as keyof typeof object];
const formattedDate = `${newDate.year()}-${newDate.month() + 1}-${ const formattedDate = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day day >= 10 ? day : "0" + day
}`; }`;
if (!disabledDates || (Array.isArray(disabledDates) && !disabledDates.length)) { if (
return false; !disabledDates ||
} (Array.isArray(disabledDates) && !disabledDates.length)
) {
return false;
}
let matchingCount = 0; let matchingCount = 0;
disabledDates?.forEach(dateRange => { disabledDates?.forEach((dateRange) => {
if ( if (
dayjs(formattedDate).isAfter(dateRange.startDate) && dayjs(formattedDate).isAfter(dateRange.startDate) &&
dayjs(formattedDate).isBefore(dateRange.endDate) dayjs(formattedDate).isBefore(dateRange.endDate)
) { ) {
matchingCount++; matchingCount++;
} }
if ( if (
dayjs(formattedDate).isSame(dateRange.startDate) || dayjs(formattedDate).isSame(dateRange.startDate) ||
dayjs(formattedDate).isSame(dateRange.endDate) dayjs(formattedDate).isSame(dateRange.endDate)
) { ) {
matchingCount++; matchingCount++;
} }
});
return matchingCount > 0;
},
[calendarData.date, isDateTooEarly, isDateTooLate, disabledDates]
);
const buttonClass = useCallback(
(day: number, type: "current" | "next" | "previous") => {
const baseClass =
"c-flex c-items-center c-justify-center c-w-12 c-h-12 lg:c-w-10 lg:c-h-10 c-relative";
if (type === "current") {
return cn(
baseClass,
!activeDateData(day).active
? hoverClassByDay(day)
: activeDateData(day).className,
isDateDisabled(day, type) && "c-text-gray-400 c-cursor-not-allowed"
);
}
return cn(
baseClass,
isDateDisabled(day, type) && "c-cursor-not-allowed",
"c-text-gray-400"
);
},
[activeDateData, hoverClassByDay, isDateDisabled]
);
const checkIfHoverPeriodContainsDisabledPeriod = useCallback(
(hoverPeriod: Period) => {
if (!Array.isArray(disabledDates)) {
return false;
}
for (let i = 0; i < disabledDates.length; i++) {
if (
dayjs(hoverPeriod.start).isBefore(disabledDates[i].startDate) &&
dayjs(hoverPeriod.end).isAfter(disabledDates[i].endDate)
) {
return true;
}
}
return false;
},
[disabledDates]
);
const getMetaData = useCallback(() => {
return {
previous: previousMonth(calendarData.date),
current: calendarData.date,
next: nextMonth(calendarData.date),
};
}, [calendarData.date]);
const hoverDay = useCallback(
(day: number, type: string) => {
const object = getMetaData();
const newDate = object[type as keyof typeof object];
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
if (period.start && !period.end) {
const hoverPeriod = { ...period, end: newHover };
if (dayjs(newHover).isBefore(dayjs(period.start))) {
hoverPeriod.start = newHover;
hoverPeriod.end = period.start;
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
changePeriod({
start: null,
end: period.start,
}); });
return matchingCount > 0; }
}, }
[calendarData.date, isDateTooEarly, isDateTooLate, disabledDates] if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
); changeDayHover(newHover);
}
}
const buttonClass = useCallback( if (!period.start && period.end) {
(day: number, type: "current" | "next" | "previous") => { const hoverPeriod = { ...period, start: newHover };
const baseClass = "c-flex c-items-center c-justify-center c-w-12 c-h-12 lg:c-w-10 lg:c-h-10"; if (dayjs(newHover).isAfter(dayjs(period.end))) {
if (type === "current") { hoverPeriod.start = period.end;
return cn( hoverPeriod.end = newHover;
baseClass, if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
!activeDateData(day).active changePeriod({
? hoverClassByDay(day) start: period.end,
: activeDateData(day).className, end: null,
isDateDisabled(day, type) && "c-text-gray-400 c-cursor-not-allowed" });
); }
} }
return cn(baseClass, isDateDisabled(day, type) && "c-cursor-not-allowed", "c-text-gray-400"); if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
}, changeDayHover(newHover);
[activeDateData, hoverClassByDay, isDateDisabled] }
); }
},
[
changeDayHover,
changePeriod,
checkIfHoverPeriodContainsDisabledPeriod,
getMetaData,
period,
]
);
const checkIfHoverPeriodContainsDisabledPeriod = useCallback( const handleClickDay = useCallback(
(hoverPeriod: Period) => { (day: number, type: "previous" | "current" | "next") => {
if (!Array.isArray(disabledDates)) { function continueClick() {
return false; if (type === "previous") {
} onClickPreviousDays(day);
for (let i = 0; i < disabledDates.length; i++) { }
if (
dayjs(hoverPeriod.start).isBefore(disabledDates[i].startDate) &&
dayjs(hoverPeriod.end).isAfter(disabledDates[i].endDate)
) {
return true;
}
}
return false;
},
[disabledDates]
);
const getMetaData = useCallback(() => { if (type === "current") {
return { onClickDay(day);
previous: previousMonth(calendarData.date), }
current: calendarData.date,
next: nextMonth(calendarData.date)
};
}, [calendarData.date]);
const hoverDay = useCallback( if (type === "next") {
(day: number, type: string) => { onClickNextDays(day);
const object = getMetaData(); }
const newDate = object[type as keyof typeof object]; }
const newHover = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
if (period.start && !period.end) { if (disabledDates?.length) {
const hoverPeriod = { ...period, end: newHover }; const object = getMetaData();
if (dayjs(newHover).isBefore(dayjs(period.start))) { const newDate = object[type as keyof typeof object];
hoverPeriod.start = newHover; const clickDay = `${newDate.year()}-${newDate.month() + 1}-${
hoverPeriod.end = period.start; day >= 10 ? day : "0" + day
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { }`;
changePeriod({
start: null,
end: period.start
});
}
}
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) {
changeDayHover(newHover);
}
}
if (!period.start && period.end) { if (period.start && !period.end) {
const hoverPeriod = { ...period, start: newHover }; dayjs(clickDay).isSame(dayHover) && continueClick();
if (dayjs(newHover).isAfter(dayjs(period.end))) { } else if (!period.start && period.end) {
hoverPeriod.start = period.end; dayjs(clickDay).isSame(dayHover) && continueClick();
hoverPeriod.end = newHover; } else {
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { continueClick();
changePeriod({ }
start: period.end, } else {
end: null continueClick();
}); }
} },
} [
if (!checkIfHoverPeriodContainsDisabledPeriod(hoverPeriod)) { dayHover,
changeDayHover(newHover); disabledDates?.length,
} getMetaData,
} onClickDay,
}, onClickNextDays,
[ onClickPreviousDays,
changeDayHover, period.end,
changePeriod, period.start,
checkIfHoverPeriodContainsDisabledPeriod, ]
getMetaData, );
period const load_marker = (day: number, type: string) => {
] let fullDay = `${calendarData.date.year()}-${
); calendarData.date.month() + 1
}-${day >= 10 ? day : "0" + day}`;
if (type === "previous") {
const newDate = previousMonth(calendarData.date);
fullDay = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
}
if (type === "next") {
const newDate = nextMonth(calendarData.date);
fullDay = `${newDate.year()}-${newDate.month() + 1}-${
day >= 10 ? day : "0" + day
}`;
}
const res = new Date(fullDay);
return typeof onIcon === "function" ? onIcon(day, res) : null;
};
return (
<div className="c-grid c-grid-cols-7 c-gap-y-0.5 c-my-1">
{calendarData.days.previous.map((item, index) => (
<button
type="button"
key={index}
disabled={isDateDisabled(item, "previous")}
className={`${buttonClass(item, "previous")}`}
onClick={() => handleClickDay(item, "previous")}
onMouseOver={() => {
hoverDay(item, "previous");
}}
>
<span className="c-relative">
{item}
<span className="c-relative">{load_marker(item, "previous")}</span>
</span>
</button>
))}
const handleClickDay = useCallback( {calendarData.days.current.map((item, index) => (
(day: number, type: "previous" | "current" | "next") => { <button
function continueClick() { type="button"
if (type === "previous") { key={index}
onClickPreviousDays(day); disabled={isDateDisabled(item, "current")}
} className={cx(
`${buttonClass(item, "current")}`,
item === 1 && "highlight"
)}
onClick={() => handleClickDay(item, "current")}
onMouseOver={() => {
hoverDay(item, "current");
}}
>
<span className="c-relative">
{item}
<span className="c-relative">{load_marker(item, "current")}</span>
</span>
</button>
))}
if (type === "current") { {calendarData.days.next.map((item, index) => (
onClickDay(day); <button
} type="button"
key={index}
if (type === "next") { disabled={isDateDisabled(item, "next")}
onClickNextDays(day); className={`${buttonClass(item, "next")}`}
} onClick={() => handleClickDay(item, "next")}
} onMouseOver={() => {
hoverDay(item, "next");
if (disabledDates?.length) { }}
const object = getMetaData(); >
const newDate = object[type as keyof typeof object]; <span className="c-relative">
const clickDay = `${newDate.year()}-${newDate.month() + 1}-${ {item}
day >= 10 ? day : "0" + day <span className="c-relative">{load_marker(item, "next")}</span>
}`; </span>
</button>
if (period.start && !period.end) { ))}
dayjs(clickDay).isSame(dayHover) && continueClick(); </div>
} else if (!period.start && period.end) { );
dayjs(clickDay).isSame(dayHover) && continueClick();
} else {
continueClick();
}
} else {
continueClick();
}
},
[
dayHover,
disabledDates?.length,
getMetaData,
onClickDay,
onClickNextDays,
onClickPreviousDays,
period.end,
period.start
]
);
return (
<div className="c-grid c-grid-cols-7 c-gap-y-0.5 c-my-1">
{calendarData.days.previous.map((item, index) => (
<button
type="button"
key={index}
disabled={isDateDisabled(item, "previous")}
className={`${buttonClass(item, "previous")}`}
onClick={() => handleClickDay(item, "previous")}
onMouseOver={() => {
hoverDay(item, "previous");
}}
>
{item}
</button>
))}
{calendarData.days.current.map((item, index) => (
<button
type="button"
key={index}
disabled={isDateDisabled(item, "current")}
className={cx(`${buttonClass(item, "current")}`, item === 1 && "highlight")}
onClick={() => handleClickDay(item, "current")}
onMouseOver={() => {
hoverDay(item, "current");
}}
>
{item}
</button>
))}
{calendarData.days.next.map((item, index) => (
<button
type="button"
key={index}
disabled={isDateDisabled(item, "next")}
className={`${buttonClass(item, "next")}`}
onClick={() => handleClickDay(item, "next")}
onMouseOver={() => {
hoverDay(item, "next");
}}
>
{item}
</button>
))}
</div>
);
}; };
export default Days; export default Days;

View File

@ -44,6 +44,7 @@ interface Props {
changeMonth: (month: number) => void; changeMonth: (month: number) => void;
changeYear: (year: number) => void; changeYear: (year: number) => void;
mode?: "monthly" | "daily"; mode?: "monthly" | "daily";
onMark?: (day: number, date: Date) => any;
} }
const Calendar: React.FC<Props> = ({ const Calendar: React.FC<Props> = ({
@ -54,6 +55,7 @@ const Calendar: React.FC<Props> = ({
onClickNext, onClickNext,
changeMonth, changeMonth,
changeYear, changeYear,
onMark,
mode = "daily", mode = "daily",
}) => { }) => {
// Contexts // Contexts
@ -366,6 +368,21 @@ const Calendar: React.FC<Props> = ({
onClickPreviousDays={clickPreviousDays} onClickPreviousDays={clickPreviousDays}
onClickDay={clickDay} onClickDay={clickDay}
onClickNextDays={clickNextDays} onClickNextDays={clickNextDays}
onIcon={(day, date) => {
if(typeof onMark === "function"){
return onMark(day, date)
}
return <></>
if (new Date().getDate() === day)
return (
<div className="c-absolute c-inset-y-0 c-left-0 -c-translate-y-1/2 -c-translate-x-1/2">
<div className="c-w-full c-h-full c-flex c-flex-row c-items-center c-justif-center c-px-0.5">
!
</div>
</div>
);
return <></>
}}
/> />
</> </>
)} )}

View File

@ -82,7 +82,10 @@ export interface DatepickerType {
disabledDates?: DateRangeType[] | null; disabledDates?: DateRangeType[] | null;
startWeekOn?: string | null; startWeekOn?: string | null;
popoverDirection?: PopoverDirectionType; popoverDirection?: PopoverDirectionType;
mode?: "daily" | "monthly" mode?: "daily" | "monthly";
onMark?: (day: number, date: Date) => any;
onLoad?: () => Promise<void>
} }
export type ColorKeys = (typeof COLORS)[number]; // "blue" | "orange" export type ColorKeys = (typeof COLORS)[number]; // "blue" | "orange"

18
comps/custom/QrLabel.tsx Normal file
View File

@ -0,0 +1,18 @@
import { FC } from "react";
import QRCode from 'react-qr-code';
export const QrLabel: FC<{
value: string,
bgcolor: string,
fgcolor: string,
size: number
}> = ({ value, bgcolor, fgcolor, size }) => {
return (
<div>
<QRCode value={value} size={size}
bgColor={bgcolor}
fgColor={fgcolor}
level={"L"} />
</div>
)
}

78
comps/custom/QrReader.tsx Normal file
View File

@ -0,0 +1,78 @@
import { useEffect, useRef, useState } from "react";
import QrScanner from "qr-scanner";
export const QrReader = () => {
const scanner = useRef<QrScanner>();
const videoEl = useRef<HTMLVideoElement>(null);
const qrBoxEl = useRef<HTMLDivElement>(null);
const [qrOn, setQrOn] = useState<boolean>(true);
const [scannedResult, setScannedResult] = useState<string | undefined>("");
// Success
const onScanSuccess = (result: QrScanner.ScanResult) => {
console.log(result);
setScannedResult(result?.data);
};
// Fail
const onScanFail = (err: string | Error) => {
console.log(err);
};
useEffect(() => {
if (videoEl?.current && !scanner.current) {
scanner.current = new QrScanner(videoEl?.current, onScanSuccess, {
onDecodeError: onScanFail,
preferredCamera: "environment",
highlightScanRegion: true,
highlightCodeOutline: true,
overlay: qrBoxEl?.current || undefined,
});
scanner?.current
?.start()
.then(() => setQrOn(true))
.catch((err) => {
if (err) setQrOn(false);
});
}
return () => {
if (!videoEl?.current) {
scanner?.current?.stop();
}
};
}, []);
useEffect(() => {
if (!qrOn)
alert(
"Camera is blocked or not accessible. Please allow camera in your browser permissions and Reload."
);
}, [qrOn]);
return (
<div className="qr-reader">
{/* QR */}
<video ref={videoEl}></video>
<div ref={qrBoxEl} className="qr-box">
</div>
{/* Show Data Result if scan is success */}
{scannedResult && (
<p
style={{
position: "absolute",
top: 0,
left: 0,
zIndex: 99999,
color: "white",
}}
>
Scanned Result: {scannedResult}
</p>
)}
</div>
);
};