Fix QC Kak Inno 8 Des

Fix QC Kak Ayu 8 Des
Fix QC Pak Jun 8 Des
This commit is contained in:
2025-12-09 17:27:23 +08:00
parent 9dbe172165
commit ac2fc1a705
44 changed files with 712 additions and 477 deletions

View File

@@ -25,25 +25,27 @@ const textHeading = {
des: "Layanan adalah fitur yang membantu warga desa mengakses berbagai kebutuhan administrasi, informasi, dan bantuan secara cepat, mudah, dan transparan. Dengan fitur ini, semua layanan desa ada dalam genggaman Anda!",
};
const HEIGHT = 720;
function Layanan() {
return (
<Stack pos={"relative"} bg={colors.grey[1]} gap={"xl"} py={"md"}>
<Container w={{ base: "100%", md: "80%" }} p={"md"}>
<Stack align="center" gap={"0"}>
<Stack pos="relative" bg={colors.grey[1]} gap="xl" py="md">
<Container w={{ base: "100%", md: "80%" }} p="md">
<Stack align="center" gap="0">
<Text
fw={"bold"}
fw="bold"
c={colors["blue-button"]}
fz={{ base: "1.8rem", md: "3.4rem" }}
>
{textHeading.title}
</Text>
<Text ta={"center"} fz={{ base: "1rem", md: "1.3rem" }}>
<Text ta="center" fz={{ base: "1rem", md: "1.3rem" }}>
{textHeading.des}
</Text>
<Box p={"md"}>
<Box p="md">
<Button
component={Link}
href={"/darmasaba/desa/layanan"}
href="/darmasaba/desa/layanan"
variant="filled"
bg={colors["blue-button"]}
radius={100}
@@ -59,30 +61,26 @@ function Layanan() {
);
}
const height = 720;
function Slider() {
const state = useProxy(stateLayananDesa);
const [loading, setLoading] = useState(false);
const mobile = useMediaQuery("(max-width: 768px)", false);
const router = useTransitionRouter();
// Refs for smooth animation
const containerRef = useRef<HTMLDivElement>(null);
const scrollPositionRef = useRef(0);
const animationFrameRef = useRef<number>(0);
const scrollPosRef = useRef(0);
const animFrameRef = useRef<number>(0);
const isHoveredRef = useRef(false);
// Refs for drag functionality
const isDraggingRef = useRef(false);
const startXRef = useRef(0);
const scrollLeftRef = useRef(0);
const velocityRef = useRef(0);
const lastScrollTimeRef = useRef(0);
const lastScrollRef = useRef(0);
// Speed configuration
const normalSpeed = 1.0; // pixels per frame
const hoverSpeed = 0.3; // slower speed on hover
const SPEED_NORMAL = 1.0;
const SPEED_HOVER = 0.3;
const VELOCITY_DECAY = 0.95;
const SCROLL_THRESHOLD = 100;
useEffect(() => {
const loadData = async () => {
@@ -100,126 +98,118 @@ function Slider() {
const data = state.suratKeterangan.findMany.data || [];
// Duplicate slides for seamless infinite loop
// We need at least 3x the data for smooth infinite scrolling
const slidesData = [...data, ...data, ...data];
// Triple data untuk infinite loop (desktop only)
const slidesData = mobile ? data : [...data, ...data, ...data];
// Auto-scroll animation untuk desktop
useEffect(() => {
if (loading || !containerRef.current || slidesData.length === 0) return;
if (loading || !containerRef.current || data.length === 0 || mobile) return;
const container = containerRef.current;
const slideWidth = container.scrollWidth / slidesData.length;
const originalDataLength = data.length;
const originalLength = data.length;
// Start from the middle set of slides
scrollPositionRef.current = slideWidth * originalDataLength;
container.scrollLeft = scrollPositionRef.current;
// Start dari middle set
scrollPosRef.current = slideWidth * originalLength;
container.scrollLeft = scrollPosRef.current;
const animate = () => {
if (!containerRef.current) return;
const container = containerRef.current;
const slideWidth = container.scrollWidth / slidesData.length;
const timeSinceScroll = Date.now() - lastScrollRef.current;
const isUserScrolling = timeSinceScroll < SCROLL_THRESHOLD;
// Check if user recently scrolled manually
const timeSinceLastScroll = Date.now() - lastScrollTimeRef.current;
const isUserScrolling = timeSinceLastScroll < 100;
// Only auto-scroll if user is not actively scrolling or dragging
if (!isDraggingRef.current && !isUserScrolling) {
const currentSpeed = isHoveredRef.current ? hoverSpeed : normalSpeed;
scrollPositionRef.current += currentSpeed;
const speed = isHoveredRef.current ? SPEED_HOVER : SPEED_NORMAL;
scrollPosRef.current += speed;
// Reset position for infinite loop
if (scrollPositionRef.current >= slideWidth * (originalDataLength * 2)) {
scrollPositionRef.current -= slideWidth * originalDataLength;
// Reset untuk infinite loop
if (scrollPosRef.current >= slideWidth * (originalLength * 2)) {
scrollPosRef.current -= slideWidth * originalLength;
}
if (scrollPosRef.current <= 0) {
scrollPosRef.current += slideWidth * originalLength;
}
if (scrollPositionRef.current <= 0) {
scrollPositionRef.current += slideWidth * originalDataLength;
}
container.scrollLeft = scrollPositionRef.current;
container.scrollLeft = scrollPosRef.current;
} else {
// Sync scroll position when user is scrolling
scrollPositionRef.current = container.scrollLeft;
scrollPosRef.current = container.scrollLeft;
// Apply momentum/velocity for smooth drag release
// Momentum untuk drag release
if (!isDraggingRef.current && Math.abs(velocityRef.current) > 0.1) {
scrollPositionRef.current += velocityRef.current;
velocityRef.current *= 0.95; // Decay velocity
container.scrollLeft = scrollPositionRef.current;
scrollPosRef.current += velocityRef.current;
velocityRef.current *= VELOCITY_DECAY;
container.scrollLeft = scrollPosRef.current;
}
}
animationFrameRef.current = requestAnimationFrame(animate);
animFrameRef.current = requestAnimationFrame(animate);
};
animationFrameRef.current = requestAnimationFrame(animate);
animFrameRef.current = requestAnimationFrame(animate);
return () => {
if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
if (animFrameRef.current) {
cancelAnimationFrame(animFrameRef.current);
}
};
}, [loading, slidesData.length, data.length, mobile]);
}, [loading, data.length, mobile]);
const handleMouseEnter = () => {
isHoveredRef.current = true;
if (!mobile) isHoveredRef.current = true;
};
const handleMouseLeave = () => {
isHoveredRef.current = false;
isDraggingRef.current = false;
if (!mobile) {
isHoveredRef.current = false;
isDraggingRef.current = false;
}
};
// Mouse drag handlers
const handleMouseDown = (e: React.MouseEvent) => {
if (!containerRef.current) return;
if (!containerRef.current || mobile) return;
isDraggingRef.current = true;
startXRef.current = e.pageX - containerRef.current.offsetLeft;
scrollLeftRef.current = containerRef.current.scrollLeft;
velocityRef.current = 0;
containerRef.current.style.cursor = 'grabbing';
};
const handleMouseMove = (e: React.MouseEvent) => {
if (!isDraggingRef.current || !containerRef.current) return;
if (!isDraggingRef.current || !containerRef.current || mobile) return;
e.preventDefault();
const x = e.pageX - containerRef.current.offsetLeft;
const walk = (x - startXRef.current) * 2; // Multiply for faster scroll
const walk = (x - startXRef.current) * 2;
const newScrollLeft = scrollLeftRef.current - walk;
// Calculate velocity for momentum
velocityRef.current = containerRef.current.scrollLeft - newScrollLeft;
containerRef.current.scrollLeft = newScrollLeft;
scrollPositionRef.current = newScrollLeft;
lastScrollTimeRef.current = Date.now();
scrollPosRef.current = newScrollLeft;
lastScrollRef.current = Date.now();
};
const handleMouseUp = () => {
if (!containerRef.current) return;
if (!containerRef.current || mobile) return;
isDraggingRef.current = false;
containerRef.current.style.cursor = 'grab';
};
// Wheel scroll handler
const handleWheel = (e: React.WheelEvent) => {
if (!containerRef.current) return;
if (!containerRef.current || mobile) return;
e.preventDefault();
containerRef.current.scrollLeft += e.deltaY;
scrollPositionRef.current = containerRef.current.scrollLeft;
lastScrollTimeRef.current = Date.now();
scrollPosRef.current = containerRef.current.scrollLeft;
lastScrollRef.current = Date.now();
};
if (loading) {
return <Skeleton height={height} />;
return <Skeleton height={HEIGHT} />;
}
if (data.length === 0) {
@@ -242,9 +232,13 @@ function Slider() {
onMouseUp={handleMouseUp}
onWheel={handleWheel}
style={{
overflow: "hidden",
cursor: "grab",
overflowX: mobile ? "auto" : "hidden",
overflowY: "hidden",
cursor: mobile ? "default" : "grab",
userSelect: "none",
WebkitOverflowScrolling: "touch",
scrollbarWidth: "none",
msOverflowStyle: "none",
}}
>
<Box
@@ -259,13 +253,13 @@ function Slider() {
<Box
key={`${item.id}-${index}`}
style={{
flex: `0 0 ${mobile ? "100%" : "calc(33.333% - 1rem)"}`,
minWidth: mobile ? "100%" : "calc(33.333% - 1rem)",
flex: `0 0 ${mobile ? "90%" : "calc(33.333% - 1rem)"}`,
minWidth: mobile ? "90%" : "calc(33.333% - 1rem)",
}}
>
<Paper
h={height}
pos={"relative"}
h={HEIGHT}
pos="relative"
style={{
backgroundImage: `url(${item.image?.link})`,
backgroundSize: "cover",
@@ -280,23 +274,23 @@ function Slider() {
borderRadius: 8,
zIndex: 0,
}}
pos={"absolute"}
w={"100%"}
h={"100%"}
pos="absolute"
w="100%"
h="100%"
bg={colors.trans.dark[2]}
/>
<Stack
justify="space-between"
h={"100%"}
h="100%"
gap={0}
p={"lg"}
pos={"relative"}
p="lg"
pos="relative"
>
<Box p={"lg"}>
<Box p="lg">
<Text
fw={"bold"}
c={"white"}
size={"3.5rem"}
fw="bold"
c="white"
fz={{base: "xl", md: "3.5rem"}}
style={{
textAlign: "center",
}}
@@ -310,7 +304,7 @@ function Slider() {
router.push(`/darmasaba/desa/layanan/${item.id}`)
}
px={46}
radius={"100"}
radius="100"
size="md"
bg={colors["blue-button"]}
>