From 334bd08e60a0c97e660615555a086b6232a2aaf6 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 10:46:19 +0700
Subject: [PATCH 01/29] feat: integrate Daily Checklist Dashboard to API
integration
---
.../components/pages/dashboard/Dashboard.tsx | 606 +++++-------------
1 file changed, 163 insertions(+), 443 deletions(-)
diff --git a/src/figma-make/components/pages/dashboard/Dashboard.tsx b/src/figma-make/components/pages/dashboard/Dashboard.tsx
index 5d866ffc..f6d12d79 100644
--- a/src/figma-make/components/pages/dashboard/Dashboard.tsx
+++ b/src/figma-make/components/pages/dashboard/Dashboard.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useState, useEffect } from 'react';
+import { useState } from 'react';
import {
Card,
CardContent,
@@ -15,7 +15,6 @@ import {
SelectTrigger,
SelectValue,
} from '@/figma-make/components/base/select';
-import { Input } from '@/figma-make/components/base/input';
import { Badge } from '@/figma-make/components/base/badge';
import {
Calendar as CalendarIcon,
@@ -35,53 +34,17 @@ import {
ResponsiveContainer,
Cell,
} from 'recharts';
-import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase';
import { toast } from 'sonner';
-
-interface EmployeePerformance {
- employee_id: string;
- employee_name: string;
- kandang_id: string;
- kandang_name: string;
- total_activities_in_category: number; // Total aktivitas di kategori
- completed_activities: number; // Aktivitas yang sudah di-check
- completion_rate: number;
- last_activity_date: string | null;
- color: string; // Color based on kandang
-}
-
-interface Kandang {
- id: string;
- name: string;
-}
-
-interface Category {
- id: string;
- name: string;
-}
-
-interface ChecklistKandang {
- id: string;
- date: string;
- kandang_id: string;
- category: string;
- kandang: {
- id: string;
- name: string;
- } | null;
-}
-
-interface AssignmentEmployee {
- id: string;
- task_id: string;
- employee_id: string;
- checked: boolean;
- updated_at: string;
- employee: {
- id: string;
- name: string;
- } | null;
-}
+import useSWR from 'swr';
+import { BaseApiResponse } from '@/types/api/api-general';
+import { DailyChecklistSummary } from '@/types/api/daily-checklist/daily-checklist';
+import { AxiosError } from 'axios';
+import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
+import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
+import { KandangApi } from '@/services/api/master-data';
+import { useSelect } from '@/components/input/SelectInput';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { formatDate } from '@/lib/helper';
const KANDANG_COLORS = [
'#0069e0', // Blue (primary)
@@ -102,312 +65,65 @@ const CATEGORY_LABELS: { [key: string]: string } = {
};
export function Dashboard() {
- const [loading, setLoading] = useState(false);
- const [employeePerformance, setEmployeePerformance] = useState<
- EmployeePerformance[]
- >([]);
-
- // Master data
- const [kandangList, setKandangList] = useState([]);
- const [categoryList, setCategoryList] = useState([]);
-
// Filters
const [dateFrom, setDateFrom] = useState('');
const [dateTo, setDateTo] = useState('');
const [kandangFilter, setKandangFilter] = useState('ALL');
const [categoryFilter, setCategoryFilter] = useState('ALL');
- // Color mapping for kandang
- const [kandangColorMap, setKandangColorMap] = useState<{
- [key: string]: string;
- }>({});
-
- useEffect(() => {
- fetchMasterData();
- }, []);
-
- useEffect(() => {
- // Only fetch when date filters are set
- if (dateFrom && dateTo) {
- fetchEmployeePerformance();
- } else {
- setEmployeePerformance([]);
+ const {
+ data: summaryResponse,
+ isLoading: isLoadingSummary,
+ mutate: refreshSummary,
+ } = useSWR<
+ BaseApiResponse,
+ AxiosError,
+ SWRHttpKey
+ >(
+ dateFrom && dateTo
+ ? `${DailyChecklistApi.basePath}/summary?date_from=${dateFrom}&date_to=${dateTo}&kandang_id=${kandangFilter === 'ALL' ? '' : kandangFilter}&category=${categoryFilter === 'ALL' ? '' : categoryFilter}`
+ : '',
+ httpClientFetcher,
+ {
+ keepPreviousData: true,
}
- }, [dateFrom, dateTo, kandangFilter, categoryFilter]);
+ );
- const fetchMasterData = async () => {
- if (!isSupabaseConfigured()) return;
-
- try {
- // Fetch kandang
- const { data: kandangData, error: kandangError } = await supabase
- .from('kandang')
- .select('id, name')
- .order('name', { ascending: true });
-
- if (kandangError) {
- console.error('Error fetching kandang:', kandangError);
- } else {
- setKandangList(kandangData || []);
-
- // Create color mapping
- const colorMap: { [key: string]: string } = {};
- (kandangData || []).forEach((k, index) => {
- colorMap[k.id] = KANDANG_COLORS[index % KANDANG_COLORS.length];
- });
- setKandangColorMap(colorMap);
- }
-
- // Set categories from CATEGORY_LABELS (hardcoded list)
- const categories: Category[] = Object.keys(CATEGORY_LABELS).map((id) => ({
- id,
- name: CATEGORY_LABELS[id],
- }));
- setCategoryList(categories);
- } catch (error) {
- console.error('Error fetching master data:', error);
- }
- };
-
- const fetchEmployeePerformance = async () => {
- if (!isSupabaseConfigured() || !dateFrom || !dateTo) {
- return;
- }
-
- try {
- setLoading(true);
-
- // Step 1: Get all checklists in date range + filters
- let checklistQuery = supabase
- .from('daily_checklists')
- .select(
- `
- id,
- date,
- kandang_id,
- category,
- kandang:kandang_id (
- id,
- name
- )
- `
- )
- .gte('date', dateFrom)
- .lte('date', dateTo);
-
- if (kandangFilter !== 'ALL') {
- checklistQuery = checklistQuery.eq('kandang_id', kandangFilter);
- }
-
- if (categoryFilter !== 'ALL') {
- checklistQuery = checklistQuery.eq('category', categoryFilter);
- }
-
- const { data: checklists, error: checklistError } = await checklistQuery;
-
- if (checklistError) {
- console.error('Error fetching checklists:', checklistError);
- toast.error('Gagal memuat data checklist');
- return;
- }
-
- if (!checklists || checklists.length === 0) {
- setEmployeePerformance([]);
- return;
- }
-
- const checklistsData = checklists as unknown as ChecklistKandang[];
-
- // Step 2: Get all tasks from these checklists
- const checklistIds = checklistsData.map((c) => c.id);
- const { data: tasks, error: tasksError } = await supabase
- .from('daily_checklist_activity_tasks')
- .select('id, checklist_id')
- .in('checklist_id', checklistIds);
-
- if (tasksError) {
- console.error('Error fetching tasks:', tasksError);
- return;
- }
-
- if (!tasks || tasks.length === 0) {
- setEmployeePerformance([]);
- return;
- }
-
- const taskIds = tasks.map((t) => t.id);
-
- // Step 3: Get all assignments for these tasks
- const { data: assignments, error: assignmentsError } = await supabase
- .from('daily_checklist_activity_task_assignments')
- .select(
- `
- id,
- task_id,
- employee_id,
- checked,
- updated_at,
- employee:employee_id (
- id,
- name
- )
- `
- )
- .in('task_id', taskIds);
-
- if (assignmentsError) {
- console.error('Error fetching assignments:', assignmentsError);
- return;
- }
-
- if (!assignments || assignments.length === 0) {
- setEmployeePerformance([]);
- return;
- }
-
- const assignmentsData = assignments as unknown as AssignmentEmployee[];
-
- // Step 4: Calculate total activities in selected category (if filtered)
- let totalActivitiesInCategory = 0;
-
- if (categoryFilter !== 'ALL') {
- // Get total activities from master data for this category
- const { data: phases } = await supabase
- .from('phases')
- .select('id')
- .eq('category_id', categoryFilter);
-
- if (phases && phases.length > 0) {
- const phaseIds = phases.map((p) => p.id);
- const { count } = await supabase
- .from('activities')
- .select('*', { count: 'exact', head: true })
- .in('phase_id', phaseIds);
-
- totalActivitiesInCategory = count || 0;
- }
- }
-
- // Step 5: Group by employee and calculate performance
- const employeeMap = new Map<
- string,
- {
- employee_id: string;
- employee_name: string;
- kandang_id: string;
- kandang_name: string;
- completed_count: number;
- total_count: number;
- last_activity_date: string | null;
- }
- >();
-
- assignmentsData.forEach((assignment) => {
- const task = tasks.find((t) => t.id === assignment.task_id);
- if (!task) return;
-
- const checklist = checklistsData.find(
- (c) => c.id === task.checklist_id
- );
- if (!checklist) return;
-
- const employeeId = assignment.employee_id;
- const employeeName = assignment.employee?.name || 'Unknown';
- const kandangId = checklist.kandang_id;
- const kandangName = checklist.kandang?.name || 'Unknown';
-
- if (!employeeMap.has(employeeId)) {
- employeeMap.set(employeeId, {
- employee_id: employeeId,
- employee_name: employeeName,
- kandang_id: kandangId,
- kandang_name: kandangName,
- completed_count: 0,
- total_count: 0,
- last_activity_date: null,
- });
- }
-
- const empData = employeeMap.get(employeeId)!;
- empData.total_count += 1;
-
- if (assignment.checked) {
- empData.completed_count += 1;
- }
-
- // Update last activity date
- if (assignment.updated_at) {
- if (
- !empData.last_activity_date ||
- assignment.updated_at > empData.last_activity_date
- ) {
- empData.last_activity_date = assignment.updated_at;
- }
- }
- });
-
- // Step 6: Convert to array and add calculated fields
- const performanceData: EmployeePerformance[] = Array.from(
- employeeMap.values()
- ).map((emp) => {
- // Use total activities in category if category is selected, otherwise use employee's assigned count
- const totalActivities =
- categoryFilter !== 'ALL' && totalActivitiesInCategory > 0
- ? totalActivitiesInCategory
- : emp.total_count;
-
- return {
- employee_id: emp.employee_id,
- employee_name: emp.employee_name,
- kandang_id: emp.kandang_id,
- kandang_name: emp.kandang_name,
- total_activities_in_category: totalActivities,
- completed_activities: emp.completed_count,
- completion_rate:
- totalActivities > 0
- ? Math.round((emp.completed_count / totalActivities) * 100)
- : 0,
- last_activity_date: emp.last_activity_date,
- color: kandangColorMap[emp.kandang_id] || '#0069e0',
- };
- });
-
- // Sort by employee name
- performanceData.sort((a, b) =>
- a.employee_name.localeCompare(b.employee_name)
- );
-
- setEmployeePerformance(performanceData);
- } catch (error) {
- console.error('Error fetching employee performance:', error);
- toast.error('Terjadi kesalahan saat memuat data');
- } finally {
- setLoading(false);
- }
- };
-
- const formatDate = (dateString: string | null) => {
- if (!dateString) return '-';
- const date = new Date(dateString);
- return date.toLocaleDateString('id-ID', {
- day: '2-digit',
- month: 'short',
- year: 'numeric',
+ const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
+ useSelect(KandangApi.basePath, 'id', 'name', 'search', {
+ page: '1',
+ limit: '100',
});
- };
- const hasFilters = dateFrom && dateTo;
+ const kandangColorMap: { [key: string]: string } = {};
+ (kandangOptions || []).forEach((k, index) => {
+ kandangColorMap[k.value] = KANDANG_COLORS[index % KANDANG_COLORS.length];
+ });
- // Prepare chart data
- const chartData = employeePerformance.map((emp) => ({
+ const employeePerformance = isResponseSuccess(summaryResponse)
+ ? summaryResponse.data?.tracking_abk.map((abk) => {
+ return {
+ ...abk,
+ color: kandangColorMap[abk.kandang_id] || '#0069e0',
+ };
+ })
+ : [];
+
+ const chartData = employeePerformance?.map((emp) => ({
name: emp.employee_name,
- completed: emp.completed_activities,
- remaining: emp.total_activities_in_category - emp.completed_activities,
- total: emp.total_activities_in_category,
+ completed: emp.activity_done,
+ remaining: emp.activity_left,
+ total: emp.total_activity,
color: emp.color,
kandang: emp.kandang_name,
}));
+ const hasFilters = dateFrom && dateTo;
+
+ if (summaryResponse && isResponseError(summaryResponse)) {
+ toast.error('Gagal memuat data: ' + summaryResponse.message);
+ }
+
return (
@@ -457,9 +173,12 @@ export function Dashboard() {
Semua Kandang
- {kandangList.map((kandang) => (
-
- {kandang.name}
+ {kandangOptions.map((kandang) => (
+
+ {kandang.label}
))}
@@ -482,9 +201,9 @@ export function Dashboard() {
Semua Kategori
- {categoryList.map((category) => (
-
- {category.name}
+ {Object.keys(CATEGORY_LABELS).map((category) => (
+
+ {CATEGORY_LABELS[category]}
))}
@@ -523,11 +242,11 @@ export function Dashboard() {
melihat performance ABK.
- ) : loading ? (
+ ) : isLoadingSummary ? (
Memuat data...
- ) : employeePerformance.length === 0 ? (
+ ) : employeePerformance && employeePerformance.length === 0 ? (
@@ -582,7 +301,7 @@ export function Dashboard() {
fill='#10B981'
radius={[0, 0, 0, 0]}
>
- {chartData.map((entry, index) => (
+ {chartData?.map((entry, index) => (
- {chartData.map((entry, index) => (
+ {chartData?.map((entry, index) => (
{/* Employee Tracking Table */}
- {hasFilters && employeePerformance.length > 0 && (
-
-
- Tracking ABK
-
- Detail performance masing-masing ABK
-
-
-
-
-
-
-
-
- Nama ABK
-
-
- Kandang
-
-
- Total Aktivitas
-
-
- Aktivitas Selesai
-
-
- Aktivitas Tersisa
-
-
- Completion Rate
-
-
- Last Activity
-
-
-
-
- {employeePerformance.map((emp, index) => (
-
-
- {emp.employee_name}
-
-
-
- {emp.kandang_name}
-
-
-
- {emp.total_activities_in_category}
-
-
- {emp.completed_activities}
-
-
- {emp.total_activities_in_category -
- emp.completed_activities}
-
-
-
-
-
- {emp.completion_rate}%
-
-
-
-
- {formatDate(emp.last_activity_date)}
-
+ {hasFilters &&
+ employeePerformance &&
+ employeePerformance.length > 0 && (
+
+
+ Tracking ABK
+
+ Detail performance masing-masing ABK
+
+
+
+
+
+
+
+
+ Nama ABK
+
+
+ Kandang
+
+
+ Total Aktivitas
+
+
+ Aktivitas Selesai
+
+
+ Aktivitas Tersisa
+
+
+ Completion Rate
+
+
+ Last Activity
+
- ))}
-
-
-
-
-
- )}
+
+
+ {employeePerformance?.map((emp, index) => (
+
+
+ {emp.employee_name}
+
+
+
+ {emp.kandang_name}
+
+
+
+ {emp.total_activity}
+
+
+ {emp.activity_done}
+
+
+ {emp.activity_left}
+
+
+
+
+
+ {emp.completion_rate}%
+
+
+
+
+ {formatDate(emp.last_activity, 'DD MMM YYYY')}
+
+
+ ))}
+
+
+
+
+
+ )}
| |
);
From a8c12d0c929842a65e33fd1ae9ad55e0ae88aac5 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 10:47:00 +0700
Subject: [PATCH 02/29] feat(FE): create type for daily checklist dashboard
---
.../api/daily-checklist/daily-checklist.d.ts | 26 +++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/src/types/api/daily-checklist/daily-checklist.d.ts b/src/types/api/daily-checklist/daily-checklist.d.ts
index 9f01ae1f..b88a34aa 100644
--- a/src/types/api/daily-checklist/daily-checklist.d.ts
+++ b/src/types/api/daily-checklist/daily-checklist.d.ts
@@ -54,3 +54,29 @@ export type CreateDailyChecklistPayload = {
category: string;
status: string;
};
+
+export type PerformanceOverviewItem = {
+ employee_id: number;
+ employee_name: string;
+ total_activity: number;
+ activity_done: number;
+ activity_left: number;
+ kandang: Pick;
+};
+
+export type TrackingAbkItem = {
+ employee_id: number;
+ employee_name: string;
+ kandang_id: number;
+ kandang_name: string;
+ total_activity: number;
+ activity_done: number;
+ activity_left: number;
+ completion_rate: number;
+ last_activity: string;
+};
+
+export type DailyChecklistSummary = {
+ performance_overview: PerformanceOverviewItem[];
+ tracking_abk: TrackingAbkItem[];
+};
From f46a0610f57477bb681e41f95931e42d087e05ea Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 12:55:31 +0700
Subject: [PATCH 03/29] feat: integrate Daily Checklist Report to API
---
.../reports/DailyChecklistReportsContent.tsx | 923 ++++++++----------
1 file changed, 432 insertions(+), 491 deletions(-)
diff --git a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
index 71156f15..7d6383a0 100644
--- a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
+++ b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
@@ -1,13 +1,9 @@
'use client';
-import { useState, useEffect } from 'react';
-import { Eye, Download, Search } from 'lucide-react';
+import { useMemo } from 'react';
import { Card, CardContent } from '@/figma-make/components/base/card';
-import { Button } from '@/figma-make/components/base/button';
import { Badge } from '@/figma-make/components/base/badge';
-import { Input } from '@/figma-make/components/base/input';
import { Label } from '@/figma-make/components/base/label';
-import { DateRangePicker } from '@/figma-make/components/base/date-range-picker';
import {
Select,
SelectContent,
@@ -16,357 +12,287 @@ import {
SelectValue,
} from '@/figma-make/components/base/select';
import { toast } from 'sonner';
-import { supabase, isSupabaseConfigured } from '@/figma-make/lib/supabase';
import { useRouter } from 'next/navigation';
+import { useSelect } from '@/components/input/SelectInput';
+import { AreaApi, KandangApi, LocationApi } from '@/services/api/master-data';
+import useSWR from 'swr';
+import { BaseApiResponse } from '@/types/api/api-general';
+import { DailyChecklistReport } from '@/types/api/daily-checklist/daily-checklist';
+import { AxiosError } from 'axios';
+import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
+import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
+import Table from '@/components/Table';
+import { isResponseSuccess } from '@/lib/api-helper';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
+import { cn } from '@/lib/helper';
+import { ColumnDef } from '@tanstack/react-table';
+import { report } from 'process';
+import { PhaseApi } from '@/services/api/daily-checklist/phase';
+import { EmployeeApi } from '@/services/api/daily-checklist/employee';
-interface SubmissionReportItem {
- checklist_id: string;
- date: string;
- kandang_id: string;
- kandang_name: string;
- category: string;
- status: string;
- progress_percent: number;
- total_phases: number;
- total_activities: number;
- total_employees: number;
- updated_at: string;
-}
-
-interface Kandang {
- id: string;
- name: string;
-}
-
-interface ReportQueryResult {
- id: string;
- date: string;
- kandang_id: string;
- category: string;
- status: string;
- updated_at: string;
- kandang: {
- id: string;
- name: string;
- } | null;
-}
-
-const STATUS_OPTIONS = [
- { value: 'ALL', label: 'Semua Status' },
- { value: 'DRAFT', label: 'Draft' },
- { value: 'SUBMITTED', label: 'Submitted' },
- { value: 'APPROVED', label: 'Approved' },
- { value: 'REJECTED', label: 'Rejected' },
+const MONTH_OPTIONS = [
+ { value: '1', label: 'Januari' },
+ { value: '2', label: 'Februari' },
+ { value: '3', label: 'Maret' },
+ { value: '4', label: 'April' },
+ { value: '5', label: 'Mei' },
+ { value: '6', label: 'Juni' },
+ { value: '7', label: 'Juli' },
+ { value: '8', label: 'Agustus' },
+ { value: '9', label: 'September' },
+ { value: '10', label: 'Oktober' },
+ { value: '11', label: 'November' },
+ { value: '12', label: 'Desember' },
];
-const CATEGORY_LABELS: { [key: string]: string } = {
- pullet_open: 'Pullet Open',
- pullet_close: 'Pullet Close',
- produksi_open: 'Produksi Open',
- produksi_close: 'Produksi Close',
-};
+const YEAR_OPTIONS = [
+ { value: '2027', label: '2027' },
+ { value: '2026', label: '2026' },
+ { value: '2025', label: '2025' },
+ { value: '2024', label: '2024' },
+ { value: '2023', label: '2023' },
+ { value: '2022', label: '2022' },
+ { value: '2021', label: '2021' },
+ { value: '2020', label: '2020' },
+];
+
+// const CATEGORY_LABELS: { [key: string]: string } = {
+// pullet_open: 'Pullet Open',
+// pullet_close: 'Pullet Close',
+// produksi_open: 'Produksi Open',
+// produksi_close: 'Produksi Close',
+// };
export function DailyChecklistReportsContent() {
const router = useRouter();
- const [loading, setLoading] = useState(true);
- // Report State
- const [reportList, setReportList] = useState([]);
- const [filteredReportList, setFilteredReportList] = useState<
- SubmissionReportItem[]
- >([]);
+ const currentMonth = useMemo(() => new Date().getMonth() + 1, []);
+ const currentYear = useMemo(() => new Date().getFullYear(), []);
- // Master data
- const [kandangList, setKandangList] = useState([]);
+ const {
+ state: tableFilterState,
+ updateFilter,
+ setPage,
+ setPageSize,
+ toQueryString: getTableFilterQueryString,
+ } = useTableFilter({
+ initial: {
+ bulan: currentMonth.toString(),
+ tahun: currentYear.toString(),
+ area_id: '',
+ location_id: '',
+ kandang_id: '',
+ employee_id: '',
+ phase_id: '',
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ bulan: 'bulan',
+ tahun: 'tahun',
+ area_id: 'area_id',
+ location_id: 'location_id',
+ kandang_id: 'kandang_id',
+ employee_id: 'employee_id',
+ phase_id: 'phase_id',
+ },
+ });
- // Filters
- const [statusFilter, setStatusFilter] = useState('ALL');
- const [kandangFilter, setKandangFilter] = useState('ALL');
- const [searchText, setSearchText] = useState('');
- const [dateFrom, setDateFrom] = useState('');
- const [dateTo, setDateTo] = useState('');
-
- useEffect(() => {
- fetchKandangList();
- fetchReports();
- }, []);
-
- useEffect(() => {
- applyFilters();
- }, [reportList, statusFilter, kandangFilter, searchText, dateFrom, dateTo]);
-
- const fetchKandangList = async () => {
- if (!isSupabaseConfigured()) return;
-
- try {
- const { data, error } = await supabase
- .from('kandang')
- .select('id, name')
- .order('name', { ascending: true });
-
- if (error) {
- console.error('Error fetching kandang:', error);
- return;
- }
-
- setKandangList(data || []);
- } catch (error) {
- console.error('Error fetching kandang:', error);
+ const {
+ data: reportResponse,
+ isLoading: isLoadingReport,
+ mutate: refreshReport,
+ } = useSWR<
+ BaseApiResponse,
+ AxiosError,
+ SWRHttpKey
+ >(
+ `${DailyChecklistApi.basePath}/report${getTableFilterQueryString()}`,
+ httpClientFetcher,
+ {
+ keepPreviousData: true,
}
- };
+ );
- const fetchReports = async () => {
- if (!isSupabaseConfigured()) {
- console.warn('Supabase not configured');
- setLoading(false);
- return;
+ const { options: areaOptions, isLoadingOptions: isLoadingAreas } = useSelect(
+ AreaApi.basePath,
+ 'id',
+ 'name',
+ 'search',
+ {
+ page: '1',
+ limit: '100',
}
+ );
- try {
- setLoading(true);
-
- // Fetch checklists directly from daily_checklists table
- const { data: checklists, error } = await supabase
- .from('daily_checklists')
- .select(
- `
- id,
- date,
- kandang_id,
- category,
- status,
- updated_at,
- kandang:kandang_id (
- id,
- name
- )
- `
- )
- .order('date', { ascending: false })
- .order('updated_at', { ascending: false });
-
- if (error) {
- console.error('Error fetching reports:', error);
- toast.error('Gagal memuat data reports');
- return;
- }
-
- // Enrich data with calculations
- const enrichedData = await Promise.all(
- ((checklists as unknown as ReportQueryResult[]) || [])
- .filter((checklist) => checklist.id)
- .map(async (checklist) => {
- // Count phases
- const { count: phaseCount } = await supabase
- .from('daily_checklist_phases')
- .select('*', { count: 'exact', head: true })
- .eq('checklist_id', checklist.id);
-
- // Count activities (tasks)
- const { count: activityCount } = await supabase
- .from('daily_checklist_activity_tasks')
- .select('*', { count: 'exact', head: true })
- .eq('checklist_id', checklist.id);
-
- // Count unique employees
- const { data: tasks } = await supabase
- .from('daily_checklist_activity_tasks')
- .select('id')
- .eq('checklist_id', checklist.id);
-
- const taskIds = (tasks || []).map((t) => t.id);
- let uniqueEmployees = new Set();
-
- if (taskIds.length > 0) {
- const { data: assignments } = await supabase
- .from('daily_checklist_activity_task_assignments')
- .select('employee_id')
- .in('task_id', taskIds);
-
- uniqueEmployees = new Set(
- (assignments || []).map((a) => a.employee_id)
- );
- }
-
- // ✅ Calculate progress based on phase coverage
- const { count: totalPhasesInMaster } = await supabase
- .from('phases')
- .select('*', { count: 'exact', head: true })
- .eq('category_id', checklist.category);
-
- const { data: checklistTasks } = await supabase
- .from('daily_checklist_activity_tasks')
- .select('id, phase_id')
- .eq('checklist_id', checklist.id);
-
- const checklistTaskIds = (checklistTasks || []).map((t) => t.id);
- const uniquePhasesWithChecked = new Set();
-
- if (checklistTaskIds.length > 0) {
- const { data: checkedAssignments } = await supabase
- .from('daily_checklist_activity_task_assignments')
- .select('task_id')
- .in('task_id', checklistTaskIds)
- .eq('checked', true);
-
- if (checkedAssignments && checkedAssignments.length > 0) {
- const checkedTaskIds = new Set(
- checkedAssignments.map((a) => a.task_id)
- );
- checklistTasks?.forEach((task) => {
- if (checkedTaskIds.has(task.id)) {
- uniquePhasesWithChecked.add(task.phase_id);
- }
- });
- }
- }
-
- const phasesWithCheckedCount = uniquePhasesWithChecked.size;
- const progressPercent =
- totalPhasesInMaster && totalPhasesInMaster > 0
- ? Math.round(
- (phasesWithCheckedCount / totalPhasesInMaster) * 100
- )
- : 0;
-
- return {
- checklist_id: checklist.id,
- date: checklist.date,
- kandang_id: checklist.kandang_id,
- kandang_name: checklist.kandang?.name || '-',
- category: checklist.category,
- status: checklist.status,
- progress_percent: progressPercent,
- total_phases: phaseCount || 0,
- total_activities: activityCount || 0,
- total_employees: uniqueEmployees.size,
- updated_at: checklist.updated_at,
- };
- })
- );
-
- setReportList(enrichedData);
- } catch (error) {
- console.error('Error fetching reports:', error);
- toast.error('Terjadi kesalahan');
- } finally {
- setLoading(false);
- }
- };
-
- const applyFilters = () => {
- let filtered = [...reportList];
-
- if (statusFilter && statusFilter !== 'ALL') {
- filtered = filtered.filter((item) => item.status === statusFilter);
- }
-
- if (kandangFilter && kandangFilter !== 'ALL') {
- filtered = filtered.filter((item) => item.kandang_id === kandangFilter);
- }
-
- if (searchText) {
- filtered = filtered.filter(
- (item) =>
- item.kandang_name.toLowerCase().includes(searchText.toLowerCase()) ||
- item.category.toLowerCase().includes(searchText.toLowerCase()) ||
- (CATEGORY_LABELS[item.category] || '')
- .toLowerCase()
- .includes(searchText.toLowerCase())
- );
- }
-
- if (dateFrom) {
- filtered = filtered.filter(
- (item) => new Date(item.date) >= new Date(dateFrom)
- );
- }
- if (dateTo) {
- filtered = filtered.filter(
- (item) => new Date(item.date) <= new Date(dateTo)
- );
- }
-
- setFilteredReportList(filtered);
- };
-
- const getStatusBadge = (status: string) => {
- switch (status) {
- case 'DRAFT':
- return (
-
- Draft
-
- );
- case 'SUBMITTED':
- return (
-
- Submitted
-
- );
- case 'APPROVED':
- return (
-
- Approved
-
- );
- case 'REJECTED':
- return (
-
- Rejected
-
- );
- default:
- return (
-
- {status}
-
- );
- }
- };
-
- const formatDate = (dateString: string) => {
- const date = new Date(dateString);
- return date.toLocaleDateString('id-ID', {
- day: '2-digit',
- month: 'short',
- year: 'numeric',
+ const { options: locationOptions, isLoadingOptions: isLoadingLocations } =
+ useSelect(LocationApi.basePath, 'id', 'name', 'search', {
+ page: '1',
+ limit: '100',
+ area_id: tableFilterState.area_id,
});
- };
- const formatDateTime = (dateString: string) => {
- const date = new Date(dateString);
- return date.toLocaleString('id-ID', {
- day: '2-digit',
- month: 'short',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
+ const { options: kandangOptions, isLoadingOptions: isLoadingKandangs } =
+ useSelect(KandangApi.basePath, 'id', 'name', 'search', {
+ page: '1',
+ limit: '100',
+ area_id: tableFilterState.area_id,
+ location_id: tableFilterState.location_id,
});
+
+ const { options: phaseOptions, isLoadingOptions: isLoadingPhases } =
+ useSelect(PhaseApi.basePath, 'id', 'name', 'search', {
+ page: '1',
+ limit: '100',
+ });
+
+ const { options: employeeOptions, isLoadingOptions: isLoadingEmployees } =
+ useSelect(EmployeeApi.basePath, 'id', 'name', 'search', {
+ page: '1',
+ limit: '500',
+ kandang_id: tableFilterState.kandang_id,
+ });
+
+ const currentMonthMaxDay = new Date(
+ Number(tableFilterState.tahun),
+ Number(tableFilterState.bulan),
+ 0
+ ).getDate();
+
+ const reportDateColumns: ColumnDef[] = [];
+
+ if (isResponseSuccess(reportResponse) && reportResponse.data) {
+ for (let dateNumber = 1; dateNumber <= currentMonthMaxDay; dateNumber++) {
+ reportDateColumns.push({
+ accessorKey: `daily_activities[${dateNumber}]`,
+ header: `${dateNumber}`,
+ enableSorting: false,
+ cell: ({ row }) => row.original.daily_activities[dateNumber],
+ });
+ }
+ }
+
+ const reportColumns: ColumnDef[] = [
+ {
+ accessorKey: 'area',
+ header: 'Area',
+ enableSorting: false,
+ cell: ({ row }) => row.original.area.name,
+ },
+ {
+ accessorKey: 'farm',
+ header: 'Farm',
+ enableSorting: false,
+ cell: ({ row }) => row.original.farm.name,
+ },
+ {
+ accessorKey: 'kandang',
+ header: 'Kandang',
+ enableSorting: false,
+ cell: ({ row }) => row.original.kandang.name,
+ },
+ {
+ accessorKey: 'abk',
+ header: 'ABK',
+ enableSorting: false,
+ cell: ({ row }) => (
+ {row.original.abk.name}
+ ),
+ },
+ {
+ accessorKey: 'phase',
+ header: 'Phase',
+ enableSorting: false,
+ cell: ({ row }) => row.original.phase,
+ },
+ {
+ header: `Tanggal - ${MONTH_OPTIONS[Number(tableFilterState.bulan) - 1].label} - ${tableFilterState.tahun}`,
+ columns: reportDateColumns,
+ },
+ {
+ accessorKey: 'summary.total_checklist',
+ header: 'Total Checklist',
+ enableSorting: false,
+ },
+ {
+ accessorKey: 'summary.jumlah_hari_efektif',
+ header: 'Jumlah Hari Efektif',
+ enableSorting: false,
+ },
+ {
+ accessorKey: 'summary.abk_percentage',
+ header: 'ABK (%)',
+ enableSorting: false,
+ },
+ {
+ accessorKey: 'summary.kandang_percentage',
+ header: 'Kandang (%)',
+ enableSorting: false,
+ },
+ {
+ header: 'Kategori',
+ columns: [
+ {
+ accessorKey: 'summary.kategori.kurang',
+ header: 'Kurang',
+ enableSorting: false,
+ cell: ({ row }) => (
+
+ {row.original.summary.kategori.kurang}
+
+ ),
+ },
+ {
+ accessorKey: 'summary.kategori.cukup',
+ header: 'Cukup',
+ enableSorting: false,
+ cell: ({ row }) => row.original.summary.kategori.cukup,
+ },
+ {
+ accessorKey: 'summary.kategori.baik',
+ header: 'Baik',
+ enableSorting: false,
+ cell: ({ row }) => (
+
+ {row.original.summary.kategori.baik}
+
+ ),
+ },
+ ],
+ },
+ ];
+
+ // const exportToCSV = () => {
+ // toast.info('Export CSV akan segera tersedia');
+ // };
+
+ const monthChangeHandler = (value: string) => updateFilter('bulan', value);
+ const yearChangeHandler = (value: string) => updateFilter('tahun', value);
+
+ const areaChangeHandler = (value: string) => {
+ updateFilter('area_id', value === 'ALL' ? '' : value);
+ updateFilter('location_id', '');
+ updateFilter('kandang_id', '');
+ updateFilter('employee_id', '');
};
- const handleViewDetail = (checklistId: string) => {
- // Navigate to detail page (same as List Daily Checklist)
- router.push(`/list-daily-checklist/detail?checklistId=${checklistId}`);
+ const locationChangeHandler = (value: string) => {
+ updateFilter('location_id', value === 'ALL' ? '' : value);
+ updateFilter('kandang_id', '');
+ updateFilter('employee_id', '');
};
- const exportToCSV = () => {
- toast.info('Export CSV akan segera tersedia');
+ const kandangChangeHandler = (value: string) => {
+ updateFilter('kandang_id', value === 'ALL' ? '' : value);
+ updateFilter('employee_id', '');
+ };
+
+ const phaseChangeHandler = (value: string) => {
+ updateFilter('phase_id', value);
+ };
+
+ const employeeChangeHandler = (value: string) => {
+ updateFilter('employee_id', value === 'ALL' ? '' : value);
};
return (
@@ -380,13 +306,13 @@ export function DailyChecklistReportsContent() {
Laporan lengkap checklist harian
-
Export CSV
-
+ */}
{/* Main Card */}
@@ -395,22 +321,100 @@ export function DailyChecklistReportsContent() {
{/* Filters Section */}
-
Periode Tanggal
-
- {
- setDateFrom(from);
- setDateTo(to);
- }}
- />
-
+
Bulan
+
+
+
+
+
+ {MONTH_OPTIONS.map((bulan) => (
+
+ {bulan.label}
+
+ ))}
+
+
+
+
+ Tahun
+
+
+
+
+
+ {YEAR_OPTIONS.map((tahun) => (
+
+ {tahun.label}
+
+ ))}
+
+
+
+
+ Area
+
+
+
+
+
+ Semua Area
+ {areaOptions.map((area) => (
+
+ {area.label}
+
+ ))}
+
+
+
+
+ Lokasi
+
+
+
+
+
+ Semua Lokasi
+ {locationOptions.map((location) => (
+
+ {location.label}
+
+ ))}
+
+
-
Kandang
-
+
Semua Kandang
- {kandangList.map((kandang) => (
-
- {kandang.name}
+ {kandangOptions.map((kandang) => (
+
+ {kandang.label}
))}
-
- Status
-
+ Phase
+
-
+
- {STATUS_OPTIONS.map((option) => (
-
- {option.label}
+ Semua Phase
+ {phaseOptions.map((phase) => (
+
+ {phase.label}
))}
-
-
Cari
-
- setSearchText(e.target.value)}
- className='border-gray-200 pl-9'
- />
-
-
+
ABK
+
+
+
+
+
+ Semua ABK
+ {employeeOptions.map((employee) => (
+
+ {employee.label}
+
+ ))}
+
+
{/* Reports Table */}
- {loading ? (
-
- Memuat data...
-
- ) : filteredReportList.length > 0 ? (
-
-
-
-
-
- Tanggal
-
-
- Kandang
-
-
- Kategori
-
-
- Status
-
-
- Phase
-
-
- Aktivitas
-
-
- ABK
-
-
- Progress
-
-
- Updated At
-
-
- Aksi
-
-
-
-
- {filteredReportList.map((item, index) => (
-
-
- {formatDate(item.date)}
-
-
- {item.kandang_name}
-
-
- {CATEGORY_LABELS[item.category] || item.category}
-
-
- {getStatusBadge(item.status)}
-
-
- {item.total_phases}
-
-
- {item.total_activities}
-
-
- {item.total_employees}
-
-
-
-
-
- {item.progress_percent}%
-
-
-
-
- {formatDateTime(item.updated_at)}
-
-
-
-
- handleViewDetail(item.checklist_id)
- }
- className='border-gray-200 text-gray-700 hover:bg-gray-50'
- >
-
- Detail
-
-
-
-
- ))}
-
-
-
- ) : (
-
- {searchText ||
- dateFrom ||
- dateTo ||
- statusFilter !== 'ALL' ||
- kandangFilter !== 'ALL'
- ? 'Tidak ada data yang sesuai dengan filter'
- : 'Belum ada data checklist'}
-
- )}
+
+ data={
+ isResponseSuccess(reportResponse)
+ ? reportResponse.data || []
+ : []
+ }
+ columns={reportColumns}
+ isLoading={isLoadingReport}
+ pageSize={tableFilterState.pageSize}
+ onPageSizeChange={setPageSize}
+ rowOptions={[10, 20, 50, 100]}
+ page={
+ isResponseSuccess(reportResponse)
+ ? reportResponse?.meta?.page
+ : 0
+ }
+ totalItems={
+ isResponseSuccess(reportResponse)
+ ? reportResponse?.meta?.total_results
+ : 0
+ }
+ onPageChange={setPage}
+ className={{
+ containerClassName: cn({
+ 'w-full mb-20':
+ isResponseSuccess(reportResponse) &&
+ reportResponse?.data?.length === 0,
+ }),
+ tableWrapperClassName:
+ 'overflow-x-auto border border-solid border-base-content/10 rounded-none',
+ headerRowClassName: 'bg-gray-50/50',
+ headerColumnClassName:
+ 'text-left py-3.5 px-6 text-sm font-semibold text-gray-700 border-x border-base-content/10',
+ bodyColumnClassName:
+ 'px-4 py-3 text-base-content border-x border-base-content/10',
+ paginationClassName: 'px-4',
+ }}
+ />
From aec5c8997988a1503ece029dcb1c90d1810560c1 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 12:56:42 +0700
Subject: [PATCH 04/29] feat: create Daily Checklist Report type
---
.../api/daily-checklist/daily-checklist.d.ts | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/src/types/api/daily-checklist/daily-checklist.d.ts b/src/types/api/daily-checklist/daily-checklist.d.ts
index b88a34aa..017b16b6 100644
--- a/src/types/api/daily-checklist/daily-checklist.d.ts
+++ b/src/types/api/daily-checklist/daily-checklist.d.ts
@@ -2,6 +2,9 @@ import { BaseMetadata } from '@/types/api/api-general';
import { BaseKandang } from '@/types/api/master-data/kandang';
import { Phase } from '@/types/api/daily-checklist/phase';
import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity';
+import { BaseArea } from '@/types/api/master-data/area';
+import { BaseLocation } from '@/types/api/master-data/location';
+import { BaseEmployee } from '@/types/api/master-data/employee';
export type BaseDailyChecklist = {
id: number;
@@ -80,3 +83,23 @@ export type DailyChecklistSummary = {
performance_overview: PerformanceOverviewItem[];
tracking_abk: TrackingAbkItem[];
};
+
+export type DailyChecklistReport = {
+ area: Pick;
+ farm: Pick;
+ kandang: Pick;
+ abk: Pick;
+ phase: string;
+ daily_activities: Record;
+ summary: {
+ total_checklist: number;
+ jumlah_hari_efektif: number;
+ abk_percentage: number;
+ kandang_percentage: number;
+ kategori: {
+ kurang: number;
+ cukup: number;
+ baik: number;
+ };
+ };
+};
From f84fcb78b8698aec0cc08a0e740bce9eaeaf17ae Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 13:04:46 +0700
Subject: [PATCH 05/29] chore: delete supabase related code
---
src/figma-make/lib/info.tsx | 6 -
src/figma-make/lib/supabase.ts | 339 ---------------------------------
2 files changed, 345 deletions(-)
delete mode 100644 src/figma-make/lib/info.tsx
delete mode 100644 src/figma-make/lib/supabase.ts
diff --git a/src/figma-make/lib/info.tsx b/src/figma-make/lib/info.tsx
deleted file mode 100644
index 3127f912..00000000
--- a/src/figma-make/lib/info.tsx
+++ /dev/null
@@ -1,6 +0,0 @@
-// TODO: delete this file later
-
-/* AUTOGENERATED FILE - DO NOT EDIT CONTENTS */
-
-export const projectId = 'xxx';
-export const publicAnonKey = 'xxx';
diff --git a/src/figma-make/lib/supabase.ts b/src/figma-make/lib/supabase.ts
deleted file mode 100644
index 0b693389..00000000
--- a/src/figma-make/lib/supabase.ts
+++ /dev/null
@@ -1,339 +0,0 @@
-import { createClient, SupabaseClient } from '@supabase/supabase-js';
-import { projectId, publicAnonKey } from '@/figma-make/lib/info';
-
-// ============================================
-// 🔍 SUPABASE ENVIRONMENT DEBUG CHECK
-// ============================================
-
-/**
- * Get environment variable from multiple sources
- * Checks in order: __ENV__, window.__ENV__, process.env, import.meta.env
- */
-function getEnv(key: string): string | undefined {
- let value: string | undefined;
- let source: string | undefined;
-
- // Check globalThis.__ENV__
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- if ((globalThis as any).__ENV__?.[key]) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- value = (globalThis as any).__ENV__[key];
- source = 'globalThis.__ENV__';
- }
- // Check window.__ENV__
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- else if (typeof window !== 'undefined' && (window as any).__ENV__?.[key]) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- value = (window as any).__ENV__[key];
- source = 'window.__ENV__';
- }
- // Check process.env
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- else if ((globalThis as any).process?.env?.[key]) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- value = (globalThis as any).process.env[key];
- source = 'process.env';
- }
- // Check import.meta.env (if available)
- else if (
- typeof import.meta !== 'undefined' &&
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (import.meta as any)?.env?.[key]
- ) {
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- value = (import.meta as any).env[key];
- source = 'import.meta.env';
- }
-
- if (value && source) {
- console.log(`✅ ${key} loaded from: ${source}`);
- }
-
- return value;
-}
-
-// Try to read from environment variables first
-let supabaseUrl = getEnv('VITE_SUPABASE_URL');
-let supabaseAnonKey = getEnv('VITE_SUPABASE_ANON_KEY');
-
-// Fallback to Figma Make autogenerated credentials
-if (!supabaseUrl || !supabaseAnonKey) {
- console.log(
- '📋 Using Figma Make autogenerated Supabase credentials from /utils/supabase/info.tsx'
- );
- supabaseUrl = `https://${projectId}.supabase.co`;
- supabaseAnonKey = publicAnonKey;
-}
-
-// Helper function to mask sensitive data
-const maskString = (str: string | undefined): string => {
- if (!str) return 'undefined';
- if (str.length <= 20) return str.substring(0, 10) + '...';
- return str.substring(0, 20) + '...' + `(${str.length - 20} chars masked)`;
-};
-
-// Debug logging
-console.group('🔍 Supabase Environment Check');
-console.log('projectId (from info.tsx):', projectId);
-console.log('SUPABASE_URL present?', !!supabaseUrl);
-console.log('SUPABASE_KEY present?', !!supabaseAnonKey);
-console.log('SUPABASE_URL value:', maskString(supabaseUrl));
-console.log('SUPABASE_KEY value:', maskString(supabaseAnonKey));
-console.groupEnd();
-
-// Check if Supabase is configured
-export const isSupabaseConfigured = () => {
- return !!(supabaseUrl && supabaseAnonKey);
-};
-
-// Create Supabase client or throw error
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-let supabase: SupabaseClient;
-
-if (isSupabaseConfigured()) {
- console.log('✅ Creating real Supabase client...');
- supabase = createClient(supabaseUrl!, supabaseAnonKey!);
- console.log('✅ Supabase client created successfully!');
-} else {
- const errorMessage = `
-❌ SUPABASE CONFIGURATION ERROR ❌
-
-Missing required environment variables:
-- VITE_SUPABASE_URL: ${!!supabaseUrl ? '✅ Present' : '❌ Missing'}
-- VITE_SUPABASE_ANON_KEY: ${!!supabaseAnonKey ? '✅ Present' : '❌ Missing'}
-
-Please set Supabase environment variables in:
-→ Figma Make Supabase integration settings
-→ Deployment settings/environment configuration
-
-The app checked the following sources:
-- globalThis.__ENV__
-- window.__ENV__
-- process.env
-- import.meta.env
-
-None of these sources contained the required variables.
- `.trim();
-
- console.error(errorMessage);
- throw new Error(errorMessage);
-}
-
-export { supabase };
-
-// Database types
-export interface Database {
- public: {
- Tables: {
- kandang: {
- Row: {
- id: string;
- name: string;
- created_at?: string;
- };
- Insert: {
- id?: string;
- name: string;
- created_at?: string;
- };
- Update: {
- id?: string;
- name?: string;
- created_at?: string;
- };
- };
- employees: {
- Row: {
- id: string;
- name: string;
- kandang_id: string;
- is_active: boolean;
- created_at?: string;
- };
- Insert: {
- id?: string;
- name: string;
- kandang_id: string;
- is_active?: boolean;
- created_at?: string;
- };
- Update: {
- id?: string;
- name?: string;
- kandang_id?: string;
- is_active?: boolean;
- created_at?: string;
- };
- };
- phases: {
- Row: {
- id: string;
- name: string;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- name: string;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- name?: string;
- created_at?: string;
- updated_at?: string;
- };
- };
- phase_activities: {
- Row: {
- id: string;
- phase_id: string;
- name: string;
- description?: string;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- phase_id: string;
- name: string;
- description?: string;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- phase_id?: string;
- name?: string;
- description?: string;
- created_at?: string;
- updated_at?: string;
- };
- };
- checklists: {
- Row: {
- id: string;
- name: string;
- description?: string;
- phase_id: string;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- name: string;
- description?: string;
- phase_id: string;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- name?: string;
- description?: string;
- phase_id?: string;
- created_at?: string;
- updated_at?: string;
- };
- };
- daily_checklists: {
- Row: {
- id: string;
- date: string;
- kandang_id: string;
- checklist_id: string;
- category: string;
- status: string;
- name?: string;
- total_score?: number;
- document_path?: string;
- reject_reason?: string;
- created_by: string;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- date: string;
- kandang_id: string;
- checklist_id: string;
- category: string;
- status?: string;
- name?: string;
- total_score?: number;
- document_path?: string;
- reject_reason?: string;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- date?: string;
- kandang_id?: string;
- checklist_id?: string;
- category?: string;
- status?: string;
- name?: string;
- total_score?: number;
- document_path?: string;
- reject_reason?: string;
- created_at?: string;
- updated_at?: string;
- };
- };
- daily_checklist_tasks: {
- Row: {
- id: string;
- checklist_id: string;
- activity_id: string;
- notes?: string;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- checklist_id: string;
- activity_id: string;
- notes?: string;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- checklist_id?: string;
- activity_id?: string;
- notes?: string;
- created_at?: string;
- updated_at?: string;
- };
- };
- task_assignees: {
- Row: {
- id: string;
- task_id: string;
- employee_id: string;
- is_completed: boolean;
- created_at?: string;
- updated_at?: string;
- };
- Insert: {
- id?: string;
- task_id: string;
- employee_id: string;
- is_completed?: boolean;
- created_at?: string;
- updated_at?: string;
- };
- Update: {
- id?: string;
- task_id?: string;
- employee_id?: string;
- is_completed?: boolean;
- created_at?: string;
- updated_at?: string;
- };
- };
- };
- };
-}
From 536b1c5b01a00ea6cab54c9cdc6e9e091a3c9620 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 13:05:16 +0700
Subject: [PATCH 06/29] chore: uninstall supabase
---
package-lock.json | 152 ++--------------------------------------------
package.json | 1 -
2 files changed, 6 insertions(+), 147 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index a20c8c4d..38844543 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,6 @@
"version": "0.1.0",
"dependencies": {
"@react-pdf/renderer": "^4.3.1",
- "@supabase/supabase-js": "^2.89.0",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.12.2",
@@ -3951,86 +3950,6 @@
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
"license": "MIT"
},
- "node_modules/@supabase/auth-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.89.0.tgz",
- "integrity": "sha512-wiWZdz8WMad8LQdJMWYDZ2SJtZP5MwMqzQq3ehtW2ngiI3UTgbKiFrvMUUS3KADiVlk4LiGfODB2mrYx7w2f8w==",
- "license": "MIT",
- "dependencies": {
- "tslib": "2.8.1"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@supabase/functions-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.89.0.tgz",
- "integrity": "sha512-XEueaC5gMe5NufNYfBh9kPwJlP5M2f+Ogr8rvhmRDAZNHgY6mI35RCkYDijd92pMcNM7g8pUUJov93UGUnqfyw==",
- "license": "MIT",
- "dependencies": {
- "tslib": "2.8.1"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@supabase/postgrest-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.89.0.tgz",
- "integrity": "sha512-/b0fKrxV9i7RNOEXMno/I1862RsYhuUo+Q6m6z3ar1f4ulTMXnDfv0y4YYxK2POcgrOXQOgKYQx1eArybyNvtg==",
- "license": "MIT",
- "dependencies": {
- "tslib": "2.8.1"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@supabase/realtime-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.89.0.tgz",
- "integrity": "sha512-aMOvfDb2a52u6PX6jrrjvACHXGV3zsOlWRzZsTIOAJa0hOVvRp01AwC1+nLTGUzxzezejrYeCX+KnnM1xHdl+w==",
- "license": "MIT",
- "dependencies": {
- "@types/phoenix": "^1.6.6",
- "@types/ws": "^8.18.1",
- "tslib": "2.8.1",
- "ws": "^8.18.2"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@supabase/storage-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.89.0.tgz",
- "integrity": "sha512-6zKcXofk/M/4Eato7iqpRh+B+vnxeiTumCIP+Tz26xEqIiywzD9JxHq+udRrDuv6hXE+pmetvJd8n5wcf4MFRQ==",
- "license": "MIT",
- "dependencies": {
- "iceberg-js": "^0.8.1",
- "tslib": "2.8.1"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
- "node_modules/@supabase/supabase-js": {
- "version": "2.89.0",
- "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.89.0.tgz",
- "integrity": "sha512-KlaRwSfFA0fD73PYVMHj5/iXFtQGCcX7PSx0FdQwYEEw9b2wqM7GxadY+5YwcmuEhalmjFB/YvqaoNVF+sWUlg==",
- "license": "MIT",
- "dependencies": {
- "@supabase/auth-js": "2.89.0",
- "@supabase/functions-js": "2.89.0",
- "@supabase/postgrest-js": "2.89.0",
- "@supabase/realtime-js": "2.89.0",
- "@supabase/storage-js": "2.89.0"
- },
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -4471,6 +4390,7 @@
"version": "20.19.23",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.23.tgz",
"integrity": "sha512-yIdlVVVHXpmqRhtyovZAcSy0MiPcYWGkoO4CGe/+jpP0hmNuihm4XhHbADpK++MsiLHP5MVlv+bcgdF99kSiFQ==",
+ "dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@@ -4488,12 +4408,6 @@
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
"license": "MIT"
},
- "node_modules/@types/phoenix": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
- "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
- "license": "MIT"
- },
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
@@ -4506,7 +4420,6 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz",
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -4517,7 +4430,6 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -4544,15 +4456,6 @@
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
- "node_modules/@types/ws": {
- "version": "8.18.1",
- "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
- "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.2.tgz",
@@ -4599,7 +4502,6 @@
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
@@ -5123,7 +5025,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -5829,8 +5730,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/d3-array": {
"version": "3.2.4",
@@ -6206,8 +6106,7 @@
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
@@ -6468,7 +6367,6 @@
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -6642,7 +6540,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -7519,15 +7416,6 @@
"integrity": "sha512-fXHXcGFTXOvZTSkPJuGOQf5Lv5T/R2itiiCVPg9LxAje5D00O0pP83yJShFq5V89Ly//Gt6acj7z8pbBr34stw==",
"license": "ISC"
},
- "node_modules/iceberg-js": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
- "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
- "license": "MIT",
- "engines": {
- "node": ">=20.0.0"
- }
- },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -8160,7 +8048,6 @@
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.4.tgz",
"integrity": "sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/runtime": "^7.28.4",
"fast-png": "^6.2.0",
@@ -9380,7 +9267,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9411,7 +9297,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -9479,8 +9364,7 @@
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/react-number-format": {
"version": "5.4.4",
@@ -9497,7 +9381,6 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
@@ -9666,8 +9549,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
@@ -10533,7 +10415,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -10701,7 +10582,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10733,6 +10613,7 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
"license": "MIT"
},
"node_modules/unicode-properties": {
@@ -11064,27 +10945,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/ws": {
- "version": "8.18.3",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
- "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/xlsx": {
"version": "0.20.3",
"resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
diff --git a/package.json b/package.json
index aa90e44a..3a775db2 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,6 @@
},
"dependencies": {
"@react-pdf/renderer": "^4.3.1",
- "@supabase/supabase-js": "^2.89.0",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.12.2",
From 8afd8c63826289c67df79e3e52ac333a74d0c1bd Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:41:34 +0700
Subject: [PATCH 07/29] feat: create master daily checklist configuration page
---
.../master-data/configuration/page.tsx | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 src/app/daily-checklist/master-data/configuration/page.tsx
diff --git a/src/app/daily-checklist/master-data/configuration/page.tsx b/src/app/daily-checklist/master-data/configuration/page.tsx
new file mode 100644
index 00000000..7b55c2ea
--- /dev/null
+++ b/src/app/daily-checklist/master-data/configuration/page.tsx
@@ -0,0 +1,11 @@
+import { MasterConfigurationContent } from '@/figma-make/components/pages/master-data/configuration/MasterConfigurationContent';
+
+const MasterConfigurationPage = () => {
+ return (
+
+ );
+};
+
+export default MasterConfigurationPage;
From 404019f181eac1307ce7b45c82756336147babdf Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:41:46 +0700
Subject: [PATCH 08/29] feat: add Konfigurasi menu
---
src/config/constant.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/config/constant.ts b/src/config/constant.ts
index f7f2255e..e4409748 100644
--- a/src/config/constant.ts
+++ b/src/config/constant.ts
@@ -60,6 +60,12 @@ export const MAIN_DRAWER_LINKS: SidebarMenuItem[] = [
// TODO: add permission
// permission: ['lti.daily_checklist.list'],
},
+ {
+ text: 'Konfigurasi',
+ link: '/daily-checklist/master-data/configuration',
+ // TODO: add permission
+ // permission: ['lti.daily_checklist.list'],
+ },
],
},
],
From c099314bdb65f4596a9e0f62ea25e3e02980fa38 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:42:02 +0700
Subject: [PATCH 09/29] feat: add route permission for daily checklist master
configuration
---
src/config/route-permission.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/config/route-permission.ts b/src/config/route-permission.ts
index ca720f28..9ebc566b 100644
--- a/src/config/route-permission.ts
+++ b/src/config/route-permission.ts
@@ -20,6 +20,7 @@ export const ROUTE_PERMISSIONS: Record = {
'/daily-checklist/reports/': ['lti.dashboard.list'],
'/daily-checklist/master-data/employee/': ['lti.dashboard.list'],
'/daily-checklist/master-data/activity/': ['lti.dashboard.list'],
+ '/daily-checklist/master-data/configuration/': ['lti.dashboard.list'],
// Production
// Production - Project Flock
From dfac7f84ff7af96b5a5276600c7e77ade50ad037 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:42:14 +0700
Subject: [PATCH 10/29] feat: create MasterConfigurationContent component
---
.../MasterConfigurationContent.tsx | 564 ++++++++++++++++++
1 file changed, 564 insertions(+)
create mode 100644 src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx
diff --git a/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx b/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx
new file mode 100644
index 00000000..1358d6ba
--- /dev/null
+++ b/src/figma-make/components/pages/master-data/configuration/MasterConfigurationContent.tsx
@@ -0,0 +1,564 @@
+'use client';
+
+import { useState } from 'react';
+import { Plus, MoreVertical, Pencil, Trash2 } from 'lucide-react';
+import { Card, CardContent } from '@/figma-make/components/base/card';
+import { Button } from '@/figma-make/components/base/button';
+import { Label } from '@/figma-make/components/base/label';
+import { Input } from '@/figma-make/components/base/input';
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from '@/figma-make/components/base/dialog';
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from '@/figma-make/components/base/alert-dialog';
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from '@/figma-make/components/base/dropdown-menu';
+import { toast } from 'sonner';
+import useSWR from 'swr';
+import Table from '@/components/Table';
+import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
+import { cn, formatDate } from '@/lib/helper';
+import { useTableFilter } from '@/services/hooks/useTableFilter';
+import { ColumnDef } from '@tanstack/react-table';
+import { DailyChecklistConfiguration } from '@/types/api/daily-checklist/configuration';
+import { DailyChecklistConfigurationApi } from '@/services/api/daily-checklist/configuration';
+import { DatePicker } from '@/figma-make/components/base/date-picker';
+
+export function MasterConfigurationContent() {
+ const {
+ state: tableFilterState,
+ setPage,
+ setPageSize,
+ toQueryString: getTableFilterQueryString,
+ } = useTableFilter({
+ initial: {
+ search: '',
+ },
+ paramMap: {
+ page: 'page',
+ pageSize: 'limit',
+ },
+ });
+
+ const {
+ data: dailyChecklistConfigurations,
+ isLoading: isLoadingDailyChecklistConfigurations,
+ mutate: refreshDailyChecklistConfigurations,
+ } = useSWR(
+ `${DailyChecklistConfigurationApi.basePath}${getTableFilterQueryString()}`,
+ DailyChecklistConfigurationApi.getAllFetcher,
+ {
+ keepPreviousData: true,
+ }
+ );
+
+ const [showModal, setShowModal] = useState(false);
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
+ const [configurationToDelete, setConfigurationToDelete] = useState<
+ number | null
+ >(null);
+ const [loading, setLoading] = useState(false);
+ const [modalMode, setModalMode] = useState<'create' | 'edit'>('create');
+ const [configurationForm, setConfigurationForm] = useState({
+ id: 0,
+ date: '',
+ percentage_threshold_bad: '',
+ percentage_threshold_enough: '',
+ });
+
+ const configurationColumns: ColumnDef[] = [
+ {
+ id: 'date',
+ header: 'Tanggal',
+ accessorKey: 'date',
+ enableSorting: false,
+ cell: ({ row }) => formatDate(row.original.date, 'DD MMM YYYY'),
+ },
+ {
+ id: 'percentage_threshold_bad',
+ header: 'Threshold Bad',
+ accessorKey: 'percentage_threshold_bad',
+ enableSorting: false,
+ cell: ({ row }) => `${row.original.percentage_threshold_bad}%`,
+ },
+ {
+ id: 'percentage_threshold_enough',
+ header: 'Threshold Enough',
+ accessorKey: 'percentage_threshold_enough',
+ enableSorting: false,
+ cell: ({ row }) => `${row.original.percentage_threshold_enough}%`,
+ },
+ {
+ id: 'action',
+ header: 'Aksi',
+ accessorKey: 'action',
+ enableSorting: false,
+ cell: ({ row }) => (
+
+
+
+
+
+
+
+ handleEdit(row.original)}>
+
+ Edit
+
+ handleDeleteClick(row.original.id)}
+ className='text-red-600'
+ >
+
+ Hapus
+
+
+
+ ),
+ },
+ ];
+
+ const handleAdd = () => {
+ setModalMode('create');
+ setConfigurationForm({
+ id: 0,
+ date: '',
+ percentage_threshold_bad: '',
+ percentage_threshold_enough: '',
+ });
+ setShowModal(true);
+ };
+
+ const handleEdit = (configuration: DailyChecklistConfiguration) => {
+ setModalMode('edit');
+ setConfigurationForm({
+ id: configuration.id,
+ date: configuration.date,
+ percentage_threshold_bad: String(configuration.percentage_threshold_bad),
+ percentage_threshold_enough: String(
+ configuration.percentage_threshold_enough
+ ),
+ });
+ setShowModal(true);
+ };
+
+ const handleSave = async () => {
+ if (
+ !configurationForm.date.trim() ||
+ Number(configurationForm.percentage_threshold_bad) === 0 ||
+ Number(configurationForm.percentage_threshold_enough) === 0
+ ) {
+ toast.error('Tanggal dan persentase harus diisi');
+ return;
+ }
+
+ setLoading(true);
+
+ try {
+ if (modalMode === 'create') {
+ const createConfigurationResponse =
+ await DailyChecklistConfigurationApi.create({
+ date: formatDate(configurationForm.date, 'YYYY-MM-DD'),
+ percentage_threshold_bad: Number(
+ configurationForm.percentage_threshold_bad
+ ),
+ percentage_threshold_enough: Number(
+ configurationForm.percentage_threshold_enough
+ ),
+ });
+
+ if (isResponseError(createConfigurationResponse)) {
+ console.error(
+ 'Error creating configuration:',
+ createConfigurationResponse.message
+ );
+ toast.error('Gagal menambahkan konfigurasi');
+ return;
+ }
+
+ refreshDailyChecklistConfigurations();
+ toast.success('Konfigurasi berhasil ditambahkan');
+ } else {
+ const updateConfigurationResponse =
+ await DailyChecklistConfigurationApi.update(configurationForm.id, {
+ date: formatDate(configurationForm.date, 'YYYY-MM-DD'),
+ percentage_threshold_bad: Number(
+ configurationForm.percentage_threshold_bad
+ ),
+ percentage_threshold_enough: Number(
+ configurationForm.percentage_threshold_enough
+ ),
+ });
+
+ if (isResponseError(updateConfigurationResponse)) {
+ console.error(
+ 'Error updating configuration:',
+ updateConfigurationResponse.message
+ );
+ toast.error('Gagal mengubah konfigurasi');
+ return;
+ }
+
+ refreshDailyChecklistConfigurations();
+ toast.success('Konfigurasi berhasil diubah');
+ }
+
+ setShowModal(false);
+ setConfigurationForm({
+ id: 0,
+ date: '',
+ percentage_threshold_bad: '',
+ percentage_threshold_enough: '',
+ });
+ } catch (error) {
+ console.error('Error saving configuration:', error);
+ toast.error('Terjadi kesalahan saat menyimpan konfigurasi');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleDeleteClick = (configurationId: number) => {
+ setConfigurationToDelete(configurationId);
+ setShowDeleteConfirm(true);
+ };
+
+ const handleConfirmDelete = async () => {
+ if (!configurationToDelete) return;
+
+ setLoading(true);
+
+ try {
+ const deleteConfigurationResponse =
+ await DailyChecklistConfigurationApi.delete(configurationToDelete);
+
+ if (isResponseError(deleteConfigurationResponse)) {
+ console.error(
+ 'Error deleting configuration:',
+ deleteConfigurationResponse.message
+ );
+ toast.error('Gagal menghapus konfigurasi');
+ return;
+ }
+
+ refreshDailyChecklistConfigurations();
+ toast.success('Konfigurasi berhasil dihapus');
+ setShowDeleteConfirm(false);
+ setConfigurationToDelete(null);
+ } catch (error) {
+ console.error('Error deleting employee:', error);
+ toast.error('Terjadi kesalahan saat menghapus konfigurasi');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleExport = (format: string) => {
+ toast.success(`Data berhasil diekspor ke ${format}`);
+ };
+
+ if (isLoadingDailyChecklistConfigurations && !dailyChecklistConfigurations) {
+ return (
+
+
+
+
+ Master Konfigurasi
+
+
+ Master Data • Konfigurasi
+
+
+
+
+ Memuat data...
+
+
+
+
+ );
+ }
+
+ const formatDateForDisplay = (dateStr: string) => {
+ if (!dateStr) return 'Pilih tanggal';
+ const [year, month, day] = dateStr.split('-');
+ const date = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
+ return date.toLocaleDateString('id-ID', {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ };
+
+ return (
+
+
+ {/* Page Title */}
+
+
+ Master Konfigurasi
+
+
+ Master Data • Konfigurasi
+
+
+
+ {/* Main Card */}
+
+
+ {/* Single Toolbar Row */}
+
+
+
+
+ Tambah Konfigurasi
+
+
+
+
+ {/* Table */}
+
+ data={
+ isResponseSuccess(dailyChecklistConfigurations)
+ ? dailyChecklistConfigurations?.data
+ : []
+ }
+ columns={configurationColumns}
+ pageSize={tableFilterState.pageSize}
+ onPageSizeChange={setPageSize}
+ rowOptions={[10, 20, 50, 100]}
+ page={
+ isResponseSuccess(dailyChecklistConfigurations)
+ ? dailyChecklistConfigurations?.meta?.page
+ : 0
+ }
+ totalItems={
+ isResponseSuccess(dailyChecklistConfigurations)
+ ? dailyChecklistConfigurations?.meta?.total_results
+ : 0
+ }
+ onPageChange={setPage}
+ isLoading={isLoadingDailyChecklistConfigurations}
+ className={{
+ containerClassName: cn({
+ 'w-full mb-20':
+ isResponseSuccess(dailyChecklistConfigurations) &&
+ dailyChecklistConfigurations?.data?.length === 0,
+ }),
+ tableWrapperClassName:
+ 'overflow-x-auto border border-solid border-base-content/10 rounded-none',
+ headerRowClassName: 'bg-gray-50/50',
+ headerColumnClassName:
+ 'text-left py-3.5 px-6 text-sm font-semibold text-gray-700',
+ paginationClassName: 'px-4',
+ }}
+ />
+
+
+
+
+ {/* Add/Edit Modal */}
+
+
+
+
+ {modalMode === 'create'
+ ? 'Tambah Konfigurasi'
+ : 'Edit Konfigurasi'}
+
+
+ {modalMode === 'create'
+ ? 'Masukkan detail konfigurasi baru'
+ : 'Ubah detail konfigurasi'}
+
+
+
+
+
+ Tanggal Efektif *
+
+
+
+ setConfigurationForm({
+ ...configurationForm,
+ date: e,
+ })
+ }
+ disabled={loading}
+ placeholder='Pilih tanggal'
+ formatDisplay={formatDateForDisplay}
+ />
+
+
+
+
+
+ Threshold *
+
+
+
+
+
+
+
+
+
+
+ setShowModal(false)}
+ disabled={loading}
+ >
+ Batal
+
+
+ {loading ? 'Menyimpan...' : 'Simpan'}
+
+
+
+
+
+ {/* Delete Confirmation */}
+
+
+
+ Hapus konfigurasi?
+
+ Data konfigurasi akan dihapus secara permanen.
+
+
+
+ Batal
+
+ {loading ? 'Menghapus...' : 'Hapus'}
+
+
+
+
+
+ );
+}
From 25352659f33eac32f86050ece001531256cfb3dd Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:42:33 +0700
Subject: [PATCH 11/29] feat: create Daily Checklist Configuration Api Service
---
.../api/daily-checklist/configuration.ts | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 src/services/api/daily-checklist/configuration.ts
diff --git a/src/services/api/daily-checklist/configuration.ts b/src/services/api/daily-checklist/configuration.ts
new file mode 100644
index 00000000..27331aee
--- /dev/null
+++ b/src/services/api/daily-checklist/configuration.ts
@@ -0,0 +1,19 @@
+import { BaseApiService } from '@/services/api/base';
+import {
+ CreateDailyChecklistConfigurationPayload,
+ DailyChecklistConfiguration,
+ UpdateDailyChecklistConfigurationPayload,
+} from '@/types/api/daily-checklist/configuration';
+
+export class DailyChecklistConfigurationApiService extends BaseApiService<
+ DailyChecklistConfiguration,
+ CreateDailyChecklistConfigurationPayload,
+ UpdateDailyChecklistConfigurationPayload
+> {
+ constructor(basePath: string = '/master-data/config-checklists') {
+ super(basePath);
+ }
+}
+
+export const DailyChecklistConfigurationApi =
+ new DailyChecklistConfigurationApiService('/master-data/config-checklists');
From 1002c6c437f4a9f9c40b0fbfd74c7382aa8143e4 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 14:42:41 +0700
Subject: [PATCH 12/29] feat: create Daily Checklist Configuration type
---
.../api/daily-checklist/configuration.d.ts | 22 +++++++++++++++++++
1 file changed, 22 insertions(+)
create mode 100644 src/types/api/daily-checklist/configuration.d.ts
diff --git a/src/types/api/daily-checklist/configuration.d.ts b/src/types/api/daily-checklist/configuration.d.ts
new file mode 100644
index 00000000..7e94c69b
--- /dev/null
+++ b/src/types/api/daily-checklist/configuration.d.ts
@@ -0,0 +1,22 @@
+import { BaseMetadata } from '@/types/api/api-general';
+
+export type BaseConfiguration = {
+ id: number;
+ date: string;
+ percentage_threshold_bad: number;
+ percentage_threshold_enough: number;
+};
+
+export type DailyChecklistConfiguration = BaseMetadata & BaseConfiguration;
+
+export type CreateDailyChecklistConfigurationPayload = {
+ date: string;
+ percentage_threshold_bad: number;
+ percentage_threshold_enough: number;
+};
+
+export type UpdateDailyChecklistConfigurationPayload = {
+ date: string;
+ percentage_threshold_bad: number;
+ percentage_threshold_enough: number;
+};
From 9245285fe211acab85c0cafcd64780b828f32408 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:30:30 +0700
Subject: [PATCH 13/29] feat: add upload document in daily checklist
---
.../daily-checklist/DailyChecklistContent.tsx | 119 +++++++++++++++++-
1 file changed, 117 insertions(+), 2 deletions(-)
diff --git a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
index ae704e92..7bd0be83 100644
--- a/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
+++ b/src/figma-make/components/pages/daily-checklist/DailyChecklistContent.tsx
@@ -30,7 +30,7 @@ import { KandangApi } from '@/services/api/master-data';
import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import useSWR from 'swr';
-import { BaseApiResponse } from '@/types/api/api-general';
+import { BaseApiResponse, Document } from '@/types/api/api-general';
import { AxiosError } from 'axios';
import { httpClientFetcher, SWRHttpKey } from '@/services/http/client';
import { PhaseApi } from '@/services/api/daily-checklist/phase';
@@ -39,6 +39,9 @@ import { Employee } from '@/types/api/daily-checklist/employee';
import { PhaseActivityApi } from '@/services/api/daily-checklist/phase-activity';
import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity';
import DebouncedTextArea from '@/components/input/DebouncedTextArea';
+import DropFileInput from '@/components/input/DropFileInput';
+import Link from 'next/link';
+import { Icon } from '@iconify/react';
// Static categories
const CATEGORIES = [
@@ -148,6 +151,10 @@ export function DailyChecklistContent() {
const [loading, setLoading] = useState(false);
const [initialLoading, setInitialLoading] = useState(true);
+ const [existingDocuments, setExistingDocuments] = useState([]);
+ const [documents, setDocuments] = useState([]);
+ const [deletedDocumentIds, setDeletedDocumentIds] = useState([]);
+
// Format date for display
const formatDateForDisplay = (dateStr: string) => {
if (!dateStr) return 'Pilih tanggal';
@@ -340,6 +347,9 @@ export function DailyChecklistContent() {
return;
}
+ // set existing document
+ setExistingDocuments(existingDailyChecklist?.data.document_urls || []);
+
// Build assignments map
const assignmentMap: {
[taskId: string]: {
@@ -729,7 +739,11 @@ export function DailyChecklistContent() {
setLoading(true);
try {
- const submitRes = await DailyChecklistApi.submit(dailyChecklistId);
+ const submitRes = await DailyChecklistApi.submit(
+ dailyChecklistId,
+ documents,
+ deletedDocumentIds
+ );
if (isResponseError(submitRes)) {
console.error('Error submitting:', submitRes.message);
@@ -750,6 +764,19 @@ export function DailyChecklistContent() {
const handleSaveDraft = async () => {
if (!dailyChecklistId) return;
+ const uploadImageRes = await DailyChecklistApi.uploadImage(
+ Number(dailyChecklistId),
+ 'DRAFT',
+ documents,
+ deletedDocumentIds
+ );
+
+ if (isResponseError(uploadImageRes)) {
+ console.error('Error saving draft:', uploadImageRes.message);
+ toast.error('Gagal menyimpan draft');
+ return;
+ }
+
toast.success('Draft tersimpan otomatis');
};
@@ -1263,6 +1290,94 @@ export function DailyChecklistContent() {
)}
+ {dailyChecklistId &&
+ selectedPhaseIds.length > 0 &&
+ selectedEmployees.length > 0 && (
+ <>
+ {existingDocuments.length > 0 && (
+
+
+ Dokumen yang telah diupload
+
+ {existingDocuments.map(
+ (existingDocument, existingDocumentIdx) => (
+
+
+ {existingDocument.name}{' '}
+
+
+
+ {
+ setDeletedDocumentIds((prevIds) => [
+ ...prevIds,
+ existingDocument.id,
+ ]);
+
+ setExistingDocuments((prevExistingDocument) => {
+ const newExistingDocuments = [
+ ...prevExistingDocument,
+ ];
+ newExistingDocuments.splice(
+ existingDocumentIdx,
+ 1
+ );
+ return newExistingDocuments;
+ });
+ }}
+ className='p-1 rounded-full text-error focus-visible:text-error-content hover:text-error-content'
+ >
+
+
+
+ )
+ )}
+
+ )}
+
+ {
+ setDocuments(files);
+ }}
+ onDelete={(deletedFileIdx: number) => {
+ const newRequestDocuments = [...documents];
+
+ newRequestDocuments?.splice(deletedFileIdx, 1);
+
+ setDocuments(newRequestDocuments);
+ }}
+ className={{
+ wrapper: 'mt-6',
+ inputWrapper: 'flex items-center',
+ label: 'font-semibold text-gray-900',
+ }}
+ />
+ >
+ )}
+
{/* Action Buttons */}
{dailyChecklistId &&
selectedPhaseIds.length > 0 &&
From a24f51dad35a82e5e590008e99d3cea5df8887d4 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:30:44 +0700
Subject: [PATCH 14/29] feat: show existing document
---
.../detail/DetailDailyChecklistContent.tsx | 37 +++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
index 54e4c93f..a867c29d 100644
--- a/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
+++ b/src/figma-make/components/pages/list-daily-checklist/detail/DetailDailyChecklistContent.tsx
@@ -20,6 +20,9 @@ import { toast } from 'sonner';
import { useRouter, useSearchParams } from 'next/navigation';
import { DailyChecklistApi } from '@/services/api/daily-checklist/daily-checklist';
import { isResponseError } from '@/lib/api-helper';
+import Link from 'next/link';
+import { Icon } from '@iconify/react';
+import { Document } from '@/types/api/api-general';
interface ChecklistDetailRow {
checklist_id: string;
@@ -125,6 +128,7 @@ export function DetailDailyChecklistContent() {
const [employees, setEmployees] = useState<{ id: string; name: string }[]>(
[]
);
+ const [documents, setDocuments] = useState([]);
// Modals
const [showApproveModal, setShowApproveModal] = useState(false);
@@ -160,6 +164,8 @@ export function DetailDailyChecklistContent() {
const rawDetailChecklist = checklistDataRes?.data;
+ setDocuments(rawDetailChecklist?.document_urls || []);
+
const checklistData = {
id: rawDetailChecklist?.id,
date: rawDetailChecklist?.date,
@@ -842,6 +848,37 @@ export function DetailDailyChecklistContent() {
Tidak ada data aktivitas
)}
+
+ {documents.length > 0 && (
+
+
+ Dokumen yang telah diupload
+
+
+
+
+ )}
From 10e843aebf780c6b486a7d26d614b4aa7172125a Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:31:00 +0700
Subject: [PATCH 15/29] chore: refresh phase after modifying activity
---
.../pages/master-data/activity/MasterAktivitasContent.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx
index a5b8ac3d..36774ce2 100644
--- a/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx
+++ b/src/figma-make/components/pages/master-data/activity/MasterAktivitasContent.tsx
@@ -328,6 +328,7 @@ export function MasterAktivitasContent() {
return;
}
+ refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil ditambahkan');
} else {
@@ -349,6 +350,7 @@ export function MasterAktivitasContent() {
return;
}
+ refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil diubah');
}
@@ -387,6 +389,7 @@ export function MasterAktivitasContent() {
return;
}
+ refreshPhases();
refreshPhaseActivities();
toast.success('Aktivitas berhasil dihapus');
setShowActivityDeleteConfirm(false);
From 70eac011f37fc4d8942ded42393081c54c860933 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:31:07 +0700
Subject: [PATCH 16/29] chore: remove export button
---
.../employee/MasterEmployeeContent.tsx | 25 -------------------
1 file changed, 25 deletions(-)
diff --git a/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx b/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx
index c9562971..f8b67e7a 100644
--- a/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx
+++ b/src/figma-make/components/pages/master-data/employee/MasterEmployeeContent.tsx
@@ -283,10 +283,6 @@ export function MasterEmployeeContent() {
}
};
- const handleExport = (format: string) => {
- toast.success(`Data berhasil diekspor ke ${format}`);
- };
-
if (isLoadingEmployees && !employees) {
return (
@@ -390,27 +386,6 @@ export function MasterEmployeeContent() {
{/* RIGHT: Export + Add */}
-
-
-
-
- Export
-
-
-
-
- handleExport('CSV')}>
- Export CSV
-
- handleExport('Excel')}>
- Export Excel
-
-
-
-
Date: Mon, 12 Jan 2026 17:31:35 +0700
Subject: [PATCH 17/29] feat: add export to excel
---
.../pages/reports/DailyChecklistReportsContent.tsx | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
index 7d6383a0..61558d4f 100644
--- a/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
+++ b/src/figma-make/components/pages/reports/DailyChecklistReportsContent.tsx
@@ -29,6 +29,8 @@ import { ColumnDef } from '@tanstack/react-table';
import { report } from 'process';
import { PhaseApi } from '@/services/api/daily-checklist/phase';
import { EmployeeApi } from '@/services/api/daily-checklist/employee';
+import { Button } from '@/figma-make/components/base/button';
+import { Download } from 'lucide-react';
const MONTH_OPTIONS = [
{ value: '1', label: 'Januari' },
@@ -262,9 +264,11 @@ export function DailyChecklistReportsContent() {
},
];
- // const exportToCSV = () => {
- // toast.info('Export CSV akan segera tersedia');
- // };
+ const exportToCSV = () => {
+ DailyChecklistApi.exportDailyChecklistReportToExcel(
+ getTableFilterQueryString()
+ );
+ };
const monthChangeHandler = (value: string) => updateFilter('bulan', value);
const yearChangeHandler = (value: string) => updateFilter('tahun', value);
@@ -306,13 +310,13 @@ export function DailyChecklistReportsContent() {
Laporan lengkap checklist harian
- {/*
Export CSV
- */}
+
{/* Main Card */}
From a891608d13efe188aab52a34a65573a20bca5d89 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:31:55 +0700
Subject: [PATCH 18/29] feat: add uploadImage and
exportDailyChecklistReportToExcel method
---
.../api/daily-checklist/daily-checklist.ts | 155 ++++++++++++++++--
1 file changed, 141 insertions(+), 14 deletions(-)
diff --git a/src/services/api/daily-checklist/daily-checklist.ts b/src/services/api/daily-checklist/daily-checklist.ts
index 48b789a8..b8f72201 100644
--- a/src/services/api/daily-checklist/daily-checklist.ts
+++ b/src/services/api/daily-checklist/daily-checklist.ts
@@ -1,13 +1,18 @@
import axios from 'axios';
+import * as XLSX from 'xlsx';
import { BaseApiService } from '@/services/api/base';
-import { httpClient } from '@/services/http/client';
+import { httpClient, httpClientFetcher } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
import {
CreateDailyChecklistPayload,
DailyChecklist,
+ DailyChecklistReport,
DetailDailyChecklist,
} from '@/types/api/daily-checklist/daily-checklist';
+import { isResponseError } from '@/lib/api-helper';
+import { toast } from 'sonner';
+import { formatDate } from '@/lib/helper';
export class DailyChecklistApiService extends BaseApiService<
DailyChecklist,
@@ -134,15 +139,26 @@ export class DailyChecklistApiService extends BaseApiService<
}
}
- async submit(id: string) {
+ async submit(
+ id: string,
+ files: File[] = [],
+ deletedDocumentIds: number[] = []
+ ) {
try {
+ const formData = new FormData();
+
+ formData.append('status', 'SUBMITTED');
+
+ formData.append('reject_reason', '');
+
+ files.forEach((file) => formData.append(`documents`, file));
+
+ formData.append('deleted_document_ids', deletedDocumentIds.join(','));
+
const submitPath = `${this.basePath}/${id}`;
const submitRes = await httpClient(submitPath, {
method: 'PATCH',
- body: {
- status: 'SUBMITTED',
- reject_reason: '',
- },
+ body: formData,
});
return submitRes;
@@ -156,13 +172,16 @@ export class DailyChecklistApiService extends BaseApiService<
async approve(id: string) {
try {
+ const formData = new FormData();
+
+ formData.append('status', 'APPROVED');
+
+ formData.append('reject_reason', '');
+
const approvePath = `${this.basePath}/${id}`;
const approveRes = await httpClient(approvePath, {
method: 'PATCH',
- body: {
- status: 'APPROVED',
- reject_reason: '',
- },
+ body: formData,
});
return approveRes;
@@ -176,13 +195,16 @@ export class DailyChecklistApiService extends BaseApiService<
async reject(id: string, rejectReason: string) {
try {
+ const formData = new FormData();
+
+ formData.append('status', 'REJECTED');
+
+ formData.append('reject_reason', rejectReason);
+
const rejectPath = `${this.basePath}/${id}`;
const rejectRes = await httpClient(rejectPath, {
method: 'PATCH',
- body: {
- status: 'REJECTED',
- reject_reason: rejectReason,
- },
+ body: formData,
});
return rejectRes;
@@ -193,6 +215,111 @@ export class DailyChecklistApiService extends BaseApiService<
return undefined;
}
}
+
+ async uploadImage(
+ id: number,
+ status: string,
+ files: File[],
+ deletedDocumentIds: number[] = []
+ ) {
+ try {
+ const formData = new FormData();
+
+ formData.append('status', status);
+
+ files.forEach((file) => formData.append(`documents`, file));
+
+ formData.append('deleted_document_ids', deletedDocumentIds.join(','));
+
+ const uploadImagePath = `${this.basePath}/${id}`;
+ const uploadImageRes = await httpClient(
+ uploadImagePath,
+ {
+ method: 'PATCH',
+ body: formData,
+ }
+ );
+
+ return uploadImageRes;
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ return error.response?.data;
+ }
+ return undefined;
+ }
+ }
+
+ async exportDailyChecklistReportToExcel(initialQueryString: string) {
+ const params = new URLSearchParams(initialQueryString);
+
+ params.set('limit', '2000');
+
+ const queryString = `?${params.toString()}`;
+
+ try {
+ const dailyMarketingsReport = await httpClientFetcher<
+ BaseApiResponse
+ >(`${this.basePath}/report${queryString}`);
+
+ if (isResponseError(dailyMarketingsReport)) {
+ toast.error('Gagal melakukan export daily checklist! Coba lagi.');
+ return;
+ }
+
+ const currentMonthMaxDay = new Date(
+ Number(params.get('tahun')),
+ Number(params.get('bulan')),
+ 0
+ ).getDate();
+
+ const rows = dailyMarketingsReport.data;
+
+ const formattedRows = [];
+
+ for (let i = 0; i < rows.length; i++) {
+ const formattedData: Record = {
+ Area: rows[i].area.name,
+ Farm: rows[i].farm.name,
+ Kandang: rows[i].kandang.name,
+ ABK: rows[i].abk.name,
+ Phase: rows[i].phase,
+ };
+
+ // Add day
+ for (let j = 1; j <= currentMonthMaxDay; j++) {
+ formattedData[`Day ${j}`] = rows[i].daily_activities[`${j}`];
+ }
+
+ // add summary
+ formattedData['Total Checklist'] = rows[i].summary.total_checklist;
+ formattedData['Jumlah Hari Efektif'] =
+ rows[i].summary.jumlah_hari_efektif;
+ formattedData['ABK %'] = rows[i].summary.abk_percentage;
+ formattedData['Kandang %'] = rows[i].summary.kandang_percentage;
+ formattedData['Kategori Kurang'] = rows[i].summary.kategori.kurang;
+ formattedData['Kategori Cukup'] = rows[i].summary.kategori.cukup;
+ formattedData['Kategori Baik'] = rows[i].summary.kategori.baik;
+
+ formattedRows.push(formattedData);
+ }
+
+ const ws = XLSX.utils.json_to_sheet(formattedRows);
+ const wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(
+ wb,
+ ws,
+ `Daily Checklist ${params.get('tahun')}-${params.get('bulan')?.slice(0, 3)}`
+ );
+
+ // triggers download in browser
+ XLSX.writeFile(
+ wb,
+ `laporan-daily-checklist-${params.get('tahun')}-${params.get('bulan')}.xlsx`
+ );
+ } catch (error) {
+ toast.error('Gagal melakukan export daily checklist! Coba lagi.');
+ }
+ }
}
export const DailyChecklistApi = new DailyChecklistApiService(
From b39202111e0bad96f1222aacfa466ef938f8dbf8 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:32:07 +0700
Subject: [PATCH 19/29] feat: create Document type
---
src/types/api/api-general.d.ts | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/types/api/api-general.d.ts b/src/types/api/api-general.d.ts
index 5b9f04e3..d3deb616 100644
--- a/src/types/api/api-general.d.ts
+++ b/src/types/api/api-general.d.ts
@@ -116,3 +116,10 @@ export type BaseGroupedApproval = {
export type Approvals = BaseApiResponse;
export type GroupedApprovals = BaseApiResponse;
+
+export type Document = {
+ id: number;
+ name: string;
+ size: number;
+ url: string;
+};
From 4cbd83a6fabaeae9c36db53a488f2a07b6646db9 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Mon, 12 Jan 2026 17:32:34 +0700
Subject: [PATCH 20/29] feat: add document_urls property in
DetailDailyChecklist type
---
src/types/api/daily-checklist/daily-checklist.d.ts | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/types/api/daily-checklist/daily-checklist.d.ts b/src/types/api/daily-checklist/daily-checklist.d.ts
index 017b16b6..5e5a3fe8 100644
--- a/src/types/api/daily-checklist/daily-checklist.d.ts
+++ b/src/types/api/daily-checklist/daily-checklist.d.ts
@@ -1,4 +1,4 @@
-import { BaseMetadata } from '@/types/api/api-general';
+import { BaseMetadata, Document } from '@/types/api/api-general';
import { BaseKandang } from '@/types/api/master-data/kandang';
import { Phase } from '@/types/api/daily-checklist/phase';
import { PhaseActivity } from '@/types/api/daily-checklist/phase-activity';
@@ -49,6 +49,7 @@ export type DetailDailyChecklist = BaseDailyChecklist & {
id: number;
name: string;
}[];
+ document_urls: Document[];
};
export type CreateDailyChecklistPayload = {
From 7add41ea5a8e982f3845908bfb5e693f8e5b713a Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Mon, 12 Jan 2026 17:45:15 +0700
Subject: [PATCH 21/29] refactor(FE): Map approval 6 to green and export
FinanceApi
---
src/components/pages/expense/ExpenseStatusBadge.tsx | 4 ++++
src/services/api/report/finance-report.ts | 4 ++++
2 files changed, 8 insertions(+)
diff --git a/src/components/pages/expense/ExpenseStatusBadge.tsx b/src/components/pages/expense/ExpenseStatusBadge.tsx
index a70b6454..a7fcb3e9 100644
--- a/src/components/pages/expense/ExpenseStatusBadge.tsx
+++ b/src/components/pages/expense/ExpenseStatusBadge.tsx
@@ -39,6 +39,10 @@ const ExpenseStatusBadge = ({ approval }: ExpenseStatusBadgeProps) => {
case 5:
expenseStatusPillBadgeColor = 'green';
break;
+
+ case 6:
+ expenseStatusPillBadgeColor = 'green';
+ break;
}
if (isLatestApprovalRejected) {
diff --git a/src/services/api/report/finance-report.ts b/src/services/api/report/finance-report.ts
index 014b9fec..61480fb1 100644
--- a/src/services/api/report/finance-report.ts
+++ b/src/services/api/report/finance-report.ts
@@ -64,3 +64,7 @@ export class FinanceApiService extends BaseApiService<
}
export const FinanceApi = new FinanceApiService('reports');
+
+// export const FinanceApi = new FinanceApiService(
+// 'http://localhost:4010/api/reports/finance'
+// );
From e1c0701629d8d34a2371596612d08514e71e5630 Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Mon, 12 Jan 2026 21:39:22 +0700
Subject: [PATCH 22/29] refactor(FE): Adapt customer payment report types and
exports
---
.../finance/export/CustomerPaymentExportPDF.tsx | 9 ++++-----
.../finance/export/CustomerPaymentExportXLSX.tsx | 6 ++++--
.../pages/report/finance/tab/CustomerPaymentTab.tsx | 12 ++++++++----
src/services/api/report/finance-report.ts | 8 ++++----
src/types/api/report/customer-payment.d.ts | 13 ++++---------
5 files changed, 24 insertions(+), 24 deletions(-)
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
index e224d0f0..88c556de 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportPDF.tsx
@@ -161,10 +161,7 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
{customerReport.customer.name}
- {customerReport.customer_address || ''}
-
-
- NPWP: {customerReport.customer_npwp || '-'}
+ {customerReport.customer.address || ''}
{customerReport.summary && (
@@ -266,7 +263,9 @@ const createPDFDocument = (params: CustomerPaymentExportPDFParams) => {
- {formatNumber(item.aging)} hari
+
+ {item.aging_day ? formatNumber(item.aging_day) : '-'} hari
+
{item.reference || '-'}
diff --git a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
index 3cc4d67a..d51aa3b7 100644
--- a/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
+++ b/src/components/pages/report/finance/export/CustomerPaymentExportXLSX.tsx
@@ -30,9 +30,11 @@ export const generateCustomerPaymentExcel = (
'Tanggal Realisasi': item.realization_date
? formatDate(item.realization_date, 'DD MMM YYYY')
: '',
- Aging: formatNumber(item.aging || 0),
+ Aging: formatNumber(item.aging_day || 0),
Referensi: item.reference || '',
- 'Nomor Polisi': item.vehicle_plate || '',
+ 'Nomor Polisi': Array.isArray(item.vehicle_plate)
+ ? item.vehicle_plate.join(', ')
+ : '',
'Ekor/Qty': formatNumber(item.qty || 0),
'Berat (Kg)': formatNumber(item.weight || 0),
AVG: formatNumber(item.average_weight || 0),
diff --git a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
index f57f7335..1d8d1993 100644
--- a/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
+++ b/src/components/pages/report/finance/tab/CustomerPaymentTab.tsx
@@ -279,10 +279,14 @@ const CustomerPaymentTab = () => {
{
id: 'aging',
header: 'Aging',
- accessorKey: 'aging',
+ accessorKey: 'aging_day',
cell: (props) => {
- const value = props.row.original.aging;
- return {formatNumber(value)} hari
;
+ const value = props.row.original.aging_day;
+ return (
+
+ {value ? formatNumber(value) : '-'} hari
+
+ );
},
},
{
@@ -662,7 +666,7 @@ const CustomerPaymentTab = () => {
Date: Tue, 13 Jan 2026 08:42:07 +0700
Subject: [PATCH 23/29] feat(FE): Add 5MB file size validation for document
uploads
---
src/components/pages/expense/ExpenseRealizationContent.tsx | 7 +++++++
src/components/pages/expense/ExpenseRequestContent.tsx | 7 +++++++
.../pages/expense/form/ExpenseRealizationForm.tsx | 7 +++++++
3 files changed, 21 insertions(+)
diff --git a/src/components/pages/expense/ExpenseRealizationContent.tsx b/src/components/pages/expense/ExpenseRealizationContent.tsx
index ccd57ec3..ea4a0e8d 100644
--- a/src/components/pages/expense/ExpenseRealizationContent.tsx
+++ b/src/components/pages/expense/ExpenseRealizationContent.tsx
@@ -48,6 +48,13 @@ const ExpenseRealizationContent = ({
const realizationDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('documents', true);
+
+ const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024);
+ if (invalidFiles.length > 0) {
+ toast.error('Ukuran dokumen maksimal 5 MB!');
+ return;
+ }
+
formik.setFieldValue('documents', val);
};
diff --git a/src/components/pages/expense/ExpenseRequestContent.tsx b/src/components/pages/expense/ExpenseRequestContent.tsx
index 82c58341..a1ad4643 100644
--- a/src/components/pages/expense/ExpenseRequestContent.tsx
+++ b/src/components/pages/expense/ExpenseRequestContent.tsx
@@ -251,6 +251,13 @@ const ExpenseRequestContent = ({
const requestDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('documents', true);
+
+ const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024);
+ if (invalidFiles.length > 0) {
+ toast.error('Ukuran dokumen maksimal 5 MB!');
+ return;
+ }
+
formik.setFieldValue('documents', val);
};
diff --git a/src/components/pages/expense/form/ExpenseRealizationForm.tsx b/src/components/pages/expense/form/ExpenseRealizationForm.tsx
index 6526b1c1..e214e56f 100644
--- a/src/components/pages/expense/form/ExpenseRealizationForm.tsx
+++ b/src/components/pages/expense/form/ExpenseRealizationForm.tsx
@@ -223,6 +223,13 @@ const ExpenseRealizationForm = ({
const realizationDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('documents', true);
+
+ const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024);
+ if (invalidFiles.length > 0) {
+ toast.error('Ukuran dokumen maksimal 5 MB!');
+ return;
+ }
+
formik.setFieldValue('documents', val);
};
From d306fad40c1b198f7a0f71d081219e1e07792b52 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Tue, 13 Jan 2026 10:19:22 +0700
Subject: [PATCH 24/29] chore: use DailyMarketingReportResponse type
---
.../report/DailyMarketingReportContent.tsx | 22 ++++++++++++++-----
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/src/components/pages/report/DailyMarketingReportContent.tsx b/src/components/pages/report/DailyMarketingReportContent.tsx
index 1eba4ea3..3ddbd6cf 100644
--- a/src/components/pages/report/DailyMarketingReportContent.tsx
+++ b/src/components/pages/report/DailyMarketingReportContent.tsx
@@ -31,7 +31,10 @@ import { MarketingReportApi } from '@/services/api/report/marketing-report';
import { MARKETING_TYPE_OPTIONS } from '@/config/constant';
import { httpClient } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
-import { DailyMarketingReport } from '@/types/api/report/marketing';
+import {
+ DailyMarketingReport,
+ DailyMarketingReportResponse,
+} from '@/types/api/report/marketing';
import { isResponseError } from '@/lib/api-helper';
const DailyMarketingReportContent = () => {
@@ -191,9 +194,10 @@ const DailyMarketingReportContent = () => {
const queryString = `?${params.toString()}`;
try {
- const dailyMarketingsReport = await httpClient<
- BaseApiResponse
- >(`${MarketingReportApi.basePath}${queryString}`);
+ const dailyMarketingsReport =
+ await httpClient(
+ `${MarketingReportApi.basePath}${queryString}`
+ );
if (isResponseError(dailyMarketingsReport)) {
toast.error('Gagal melakukan export penjualan harian! Coba lagi.');
@@ -202,7 +206,10 @@ const DailyMarketingReportContent = () => {
const openPdf = async () => {
const dailyMarketingReportPdfBlob = await pdf(
-
+
).toBlob();
const dailyMarketingReportPdfUrl = URL.createObjectURL(
@@ -213,7 +220,10 @@ const DailyMarketingReportContent = () => {
const downloadPdf = async () => {
const blob = await pdf(
-
+
).toBlob();
const url = URL.createObjectURL(blob);
From 021df116004396d37ae8266680aab199ca4f4ec5 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Tue, 13 Jan 2026 10:20:23 +0700
Subject: [PATCH 25/29] chore: add total props in DailyMarketingReportPDFProps
and adjust data type
---
.../pages/report/DailyMarketingReportPDF.tsx | 32 +++++++++++++++----
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/src/components/pages/report/DailyMarketingReportPDF.tsx b/src/components/pages/report/DailyMarketingReportPDF.tsx
index 337892b3..86ee29bc 100644
--- a/src/components/pages/report/DailyMarketingReportPDF.tsx
+++ b/src/components/pages/report/DailyMarketingReportPDF.tsx
@@ -9,11 +9,15 @@ import {
View,
} from '@react-pdf/renderer';
-import { DailyMarketingReport } from '@/types/api/report/marketing';
+import {
+ DailyMarketingReport,
+ SalesSummary,
+} from '@/types/api/report/marketing';
import { formatCurrency, formatDate, formatNumber } from '@/lib/helper';
interface DailyMarketingReportPDFProps {
data?: DailyMarketingReport;
+ total?: SalesSummary;
}
const DailyMarketingReportPDFStyle = StyleSheet.create({
@@ -267,9 +271,12 @@ const DailyMarketingReportPDFStyle = StyleSheet.create({
},
});
-const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
- const rows = data?.rows || [];
- const summary = data?.summary;
+const DailyMarketingReportPDF = ({
+ data,
+ total,
+}: DailyMarketingReportPDFProps) => {
+ const rows = data || [];
+ const summary = total;
return (
@@ -409,7 +416,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
- {formatDate(row.do_date, 'DD/MM/YYYY')}
+ {formatDate(row.realization_date, 'DD/MM/YYYY')}
@@ -429,7 +436,7 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
- {row.sales}
+ {row.sales.name}
@@ -518,6 +525,19 @@ const DailyMarketingReportPDF = ({ data }: DailyMarketingReportPDFProps) => {
{formatCurrency(summary?.total_sales_amount ?? 0)}
+
+
+ Total HPP Per KG:
+
+
+ {formatCurrency(summary?.total_hpp_price_per_kg ?? 0)}
+
+
Date: Tue, 13 Jan 2026 10:21:22 +0700
Subject: [PATCH 26/29] chore: add total hpp price per kg footer
---
.../pages/report/DailyMarketingsTable.tsx | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/src/components/pages/report/DailyMarketingsTable.tsx b/src/components/pages/report/DailyMarketingsTable.tsx
index d6914cf1..6b46a001 100644
--- a/src/components/pages/report/DailyMarketingsTable.tsx
+++ b/src/components/pages/report/DailyMarketingsTable.tsx
@@ -60,9 +60,10 @@ const DailyMarketingsTable = ({
footer: 'Total',
},
{
- accessorKey: 'do_date',
- header: 'Tanggal DO',
- cell: (props) => formatDate(props.row.original.do_date, 'DD-MMM-YYYY'),
+ accessorKey: 'realization_date',
+ header: 'Tanggal Realisasi',
+ cell: (props) =>
+ formatDate(props.row.original.realization_date, 'DD-MMM-YYYY'),
},
{
accessorKey: 'aging_days',
@@ -106,10 +107,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatNumber(props.row.original.qty),
footer: () => {
const totalQty = isResponseSuccess(dailyMarketings)
- ? dailyMarketings.data.summary.total_qty
+ ? dailyMarketings?.total?.total_qty
: 0;
- return formatNumber(totalQty);
+ return totalQty ? formatNumber(totalQty) : '-';
},
},
{
@@ -123,10 +124,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatNumber(props.row.original.total_weight_kg),
footer: () => {
const totalWeightKg = isResponseSuccess(dailyMarketings)
- ? dailyMarketings.data.summary.total_weight_kg
+ ? dailyMarketings?.total?.total_weight_kg
: 0;
- return formatNumber(totalWeightKg);
+ return totalWeightKg ? formatNumber(totalWeightKg) : '-';
},
},
{
@@ -138,6 +139,13 @@ const DailyMarketingsTable = ({
accessorKey: 'hpp_price_per_kg',
header: 'HPP (Rp)',
cell: (props) => formatCurrency(props.row.original.hpp_price_per_kg),
+ footer: () => {
+ const totalHppPricePerKg = isResponseSuccess(dailyMarketings)
+ ? dailyMarketings?.total?.total_hpp_price_per_kg
+ : 0;
+
+ return totalHppPricePerKg ? formatCurrency(totalHppPricePerKg) : '-';
+ },
},
{
accessorKey: 'sales_amount',
@@ -145,10 +153,10 @@ const DailyMarketingsTable = ({
cell: (props) => formatCurrency(props.row.original.sales_amount),
footer: () => {
const totalSalesAmount = isResponseSuccess(dailyMarketings)
- ? dailyMarketings.data.summary.total_sales_amount
+ ? dailyMarketings?.total?.total_sales_amount
: 0;
- return formatCurrency(totalSalesAmount);
+ return totalSalesAmount ? formatCurrency(totalSalesAmount) : '-';
},
},
];
@@ -167,7 +175,7 @@ const DailyMarketingsTable = ({
if (!open) {
setOpen(
isResponseSuccess(dailyMarketings)
- ? dailyMarketings.data.rows.length > 0
+ ? dailyMarketings.data.length > 0
: false
);
}
@@ -215,9 +223,7 @@ const DailyMarketingsTable = ({
data={
- isResponseSuccess(dailyMarketings)
- ? dailyMarketings?.data.rows
- : []
+ isResponseSuccess(dailyMarketings) ? dailyMarketings?.data : []
}
columns={dailyMarketingColumns}
pageSize={pageSize}
@@ -242,7 +248,7 @@ const DailyMarketingsTable = ({
containerClassName: cn({
'w-full mb-20':
isResponseSuccess(dailyMarketings) &&
- dailyMarketings?.data?.rows.length === 0,
+ dailyMarketings?.data?.length === 0,
}),
}}
/>
From f32024d19aed21a9622f4d61d8455625f576e40f Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Tue, 13 Jan 2026 10:21:31 +0700
Subject: [PATCH 27/29] chore: use DailyMarketingReportResponse
---
src/services/api/report/marketing-report.ts | 26 ++++++++++++---------
1 file changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/services/api/report/marketing-report.ts b/src/services/api/report/marketing-report.ts
index 5d81605e..f55336ac 100644
--- a/src/services/api/report/marketing-report.ts
+++ b/src/services/api/report/marketing-report.ts
@@ -2,11 +2,14 @@ import * as XLSX from 'xlsx';
import toast from 'react-hot-toast';
import { BaseApiService } from '@/services/api/base';
-import { httpClient, httpClientFetcher } from '@/services/http/client';
+import { httpClientFetcher } from '@/services/http/client';
import { BaseApiResponse } from '@/types/api/api-general';
-import { DailyMarketingReport } from '@/types/api/report/marketing';
+import {
+ DailyMarketingReport,
+ DailyMarketingReportResponse,
+} from '@/types/api/report/marketing';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
-import { formatDate, sleep } from '@/lib/helper';
+import { formatDate } from '@/lib/helper';
export class MarketingReportApiService extends BaseApiService<
DailyMarketingReport,
@@ -19,10 +22,8 @@ export class MarketingReportApiService extends BaseApiService<
async getAllDailyMarketingFetcher(
endpoint: string
- ): Promise> {
- return await httpClientFetcher>(
- endpoint
- );
+ ): Promise {
+ return await httpClientFetcher(endpoint);
}
async exportDailyMarketingToExcel(initialQueryString: string) {
@@ -42,16 +43,19 @@ export class MarketingReportApiService extends BaseApiService<
return;
}
- const rows = dailyMarketingsReport.data.rows;
+ const rows = dailyMarketingsReport.data;
const formattedRows = [];
for (let i = 0; i < rows.length; i++) {
formattedRows.push({
...rows[i],
- created_user: rows[i].created_user.name,
- created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'),
- updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'),
+ // created_user: rows[i].created_user.name,
+ // created_at: formatDate(rows[i].created_at, 'YYYY-MM-DD'),
+ // updated_at: formatDate(rows[i].updated_at, 'YYYY-MM-DD'),
+ so_date: formatDate(rows[i].so_date, 'YYYY-MM-DD'),
+ realization_date: formatDate(rows[i].realization_date, 'YYYY-MM-DD'),
+ sales: rows[i].sales.name,
warehouse: rows[i].warehouse.name,
customer: rows[i].customer.name,
product: rows[i].product.name,
From 4f4787e08836c84debb2d9559cf5ed989c040123 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Tue, 13 Jan 2026 10:22:19 +0700
Subject: [PATCH 28/29] chore: create DailyMarketingReportResponse
---
src/types/api/report/marketing.d.ts | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/src/types/api/report/marketing.d.ts b/src/types/api/report/marketing.d.ts
index d1e81f77..4a0ab306 100644
--- a/src/types/api/report/marketing.d.ts
+++ b/src/types/api/report/marketing.d.ts
@@ -1,4 +1,4 @@
-import { BaseMetadata } from '@/types/api/api-general';
+import { BaseApiResponse, BaseMetadata } from '@/types/api/api-general';
import { BaseCustomer, Customer } from '@/types/api/master-data/customer';
import {
BaseWarehouseArea,
@@ -9,16 +9,17 @@ import {
import { Location } from '@/types/api/master-data/location';
import { Area } from '@/types/api/master-data/area';
import { BaseProduct } from '@/types/api/master-data/product';
+import { BaseUser } from '@/types/api/user';
export type BaseDailyMarketingRow = {
- no: number;
- so_date: string; // e.g. "01-Dec-2025"
- do_date: string; // e.g. "08-Dec-2025"
+ id: number;
+ so_date: string;
+ realization_date: string;
aging_days: number;
warehouse: BaseWarehouseArea | BaseWarehouseLocation | BaseWarehouseKandang;
customer: BaseCustomer;
- sales: string;
+ sales: BaseUser;
product: BaseProduct;
do_number: string;
@@ -43,12 +44,13 @@ export interface SalesSummary {
total_weight_kg: number;
total_sales_amount: number;
total_hpp_amount: number;
+ total_hpp_price_per_kg: number;
}
-export type DailyMarketingReport = {
- rows: DailyMarketingRow[];
- summary: SalesSummary;
-};
+export type DailyMarketingReport = DailyMarketingRow[];
+
+export type DailyMarketingReportResponse =
+ BaseApiResponse & { total: SalesSummary };
export type MarketingReportFilters = {
area_id?: number;
From b486d25a8be3fb960170dc63928e4946c8061ac8 Mon Sep 17 00:00:00 2001
From: ValdiANS
Date: Tue, 13 Jan 2026 10:34:54 +0700
Subject: [PATCH 29/29] chore: access sales.name
---
src/components/pages/report/DailyMarketingsTable.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/pages/report/DailyMarketingsTable.tsx b/src/components/pages/report/DailyMarketingsTable.tsx
index 6b46a001..94da96f8 100644
--- a/src/components/pages/report/DailyMarketingsTable.tsx
+++ b/src/components/pages/report/DailyMarketingsTable.tsx
@@ -85,6 +85,7 @@ const DailyMarketingsTable = ({
{
accessorKey: 'sales',
header: 'Sales/Marketing',
+ cell: (props) => props.row.original.sales.name,
},
{
accessorKey: 'vehicle_number',