commit
32a36cceff
444 files changed
+67186
No files matched your search
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
LineChart,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
CartesianGrid,
|
||||
Tooltip,
|
||||
Legend,
|
||||
ResponsiveContainer,
|
||||
} from 'recharts';
|
||||
import { ConsumptionPatternPoint } from '../../types';
|
||||
|
||||
interface ConsumptionPatternChartProps {
|
||||
data: ConsumptionPatternPoint[];
|
||||
}
|
||||
|
||||
const ConsumptionPatternChart: React.FC<ConsumptionPatternChartProps> = ({ data }) => {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm">
|
||||
<h2 className="text-xl font-bold mb-4">Pola Konsumsi Harian</h2>
|
||||
<div className="h-64">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={data} margin={{ top: 5, right: 20, left: -10, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" />
|
||||
<XAxis
|
||||
dataKey="hour"
|
||||
fontSize={12}
|
||||
tick={{ fill: '#6b7280' }}
|
||||
unit=":00"
|
||||
interval={3}
|
||||
/>
|
||||
<YAxis fontSize={12} tick={{ fill: '#6b7280' }} unit="%" width={40} />
|
||||
<Tooltip
|
||||
contentStyle={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
formatter={(value: number) => [`${value.toFixed(1)}%`, '']}
|
||||
/>
|
||||
<Legend wrapperStyle={{ fontSize: '14px' }} verticalAlign="top" align="right" />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="actual"
|
||||
stroke="#ef4444"
|
||||
strokeWidth={2.5}
|
||||
name="Aktual"
|
||||
dot={{ r: 3 }}
|
||||
activeDot={{ r: 5 }}
|
||||
connectNulls
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="predicted"
|
||||
stroke="#8884d8"
|
||||
strokeWidth={2}
|
||||
name="Prediksi AI"
|
||||
strokeDasharray="5 5"
|
||||
dot={false}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConsumptionPatternChart;
|
||||
@@ -0,0 +1,87 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { FeedingEvent } from '../../types';
|
||||
|
||||
interface DailyEventsListProps {
|
||||
selectedDate: Date;
|
||||
events: FeedingEvent[];
|
||||
onEditEvent: (event: FeedingEvent) => void;
|
||||
}
|
||||
|
||||
const eventConfig = {
|
||||
completed: { icon: 'fa-check-circle', color: 'text-green-500' },
|
||||
upcoming: { icon: 'fa-clock', color: 'text-blue-500' },
|
||||
skipped: { icon: 'fa-ban', color: 'text-gray-500' },
|
||||
};
|
||||
|
||||
const typeConfig = {
|
||||
'AI Recommended': { icon: 'fa-robot', color: 'text-purple-600' },
|
||||
Manual: { icon: 'fa-user', color: 'text-gray-600' },
|
||||
Skipped: { icon: 'fa-forward', color: 'text-gray-500' },
|
||||
};
|
||||
|
||||
const DailyEventsList: React.FC<DailyEventsListProps> = ({ selectedDate, events, onEditEvent }) => {
|
||||
const dailyEvents = useMemo(() => {
|
||||
return events
|
||||
.filter(
|
||||
(event) =>
|
||||
event.time.getFullYear() === selectedDate.getFullYear() &&
|
||||
event.time.getMonth() === selectedDate.getMonth() &&
|
||||
event.time.getDate() === selectedDate.getDate()
|
||||
)
|
||||
.sort((a, b) => a.time.getTime() - b.time.getTime());
|
||||
}, [selectedDate, events]);
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm h-full flex flex-col">
|
||||
<h2 className="text-xl font-bold mb-4">
|
||||
Jadwal untuk{' '}
|
||||
<span className="text-red-600">
|
||||
{selectedDate.toLocaleDateString('id-ID', { day: 'numeric', month: 'long' })}
|
||||
</span>
|
||||
</h2>
|
||||
{dailyEvents.length > 0 ? (
|
||||
<div className="space-y-4 overflow-y-auto pr-2 flex-grow">
|
||||
{dailyEvents.map((event) => {
|
||||
const statusInfo = eventConfig[event.status];
|
||||
const typeInfo = typeConfig[event.type];
|
||||
return (
|
||||
<div
|
||||
key={event.id}
|
||||
className="p-3 bg-gray-50 border border-gray-200 rounded-lg flex items-center space-x-4"
|
||||
>
|
||||
<div className={`text-2xl w-8 text-center ${statusInfo.color}`}>
|
||||
<i className={`fa-solid ${statusInfo.icon}`}></i>
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<p className="font-bold text-gray-800">
|
||||
Pukul{' '}
|
||||
{event.time.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })}
|
||||
</p>
|
||||
<p className="text-sm text-gray-600 flex items-center space-x-2">
|
||||
<span className={typeInfo.color}>
|
||||
<i className={`fa-solid ${typeInfo.icon}`}></i> {event.type}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onEditEvent(event)}
|
||||
className="text-gray-500 hover:text-blue-600 text-lg"
|
||||
>
|
||||
<i className="fa-solid fa-pencil"></i>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-grow flex flex-col items-center justify-center text-center text-gray-500">
|
||||
<i className="fa-solid fa-calendar-check text-4xl text-gray-300"></i>
|
||||
<p className="mt-3 font-semibold">Tidak ada jadwal pakan</p>
|
||||
<p className="text-sm">Tidak ada jadwal pemberian pakan untuk tanggal ini.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyEventsList;
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from 'react';
|
||||
import { FeedingEvent } from '../../types';
|
||||
|
||||
interface DailyScheduleTimelineProps {
|
||||
events: FeedingEvent[];
|
||||
}
|
||||
|
||||
const eventConfig = {
|
||||
completed: {
|
||||
icon: 'fa-check-circle',
|
||||
color: 'text-green-500',
|
||||
bg: 'bg-green-100',
|
||||
line: 'bg-green-500',
|
||||
},
|
||||
upcoming: { icon: 'fa-clock', color: 'text-blue-500', bg: 'bg-blue-100', line: 'bg-gray-300' },
|
||||
skipped: { icon: 'fa-ban', color: 'text-gray-500', bg: 'bg-gray-100', line: 'bg-gray-300' },
|
||||
};
|
||||
|
||||
const typeConfig = {
|
||||
'AI Recommended': { icon: 'fa-robot', color: 'text-purple-600' },
|
||||
Manual: { icon: 'fa-user', color: 'text-gray-600' },
|
||||
Skipped: { icon: 'fa-forward', color: 'text-gray-500' },
|
||||
};
|
||||
|
||||
const DailyScheduleTimeline: React.FC<DailyScheduleTimelineProps> = ({ events }) => {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm">
|
||||
<h2 className="text-xl font-bold mb-6">Linimasa Pemberian Pakan Harian</h2>
|
||||
<div className="relative pl-5">
|
||||
{/* Vertical Line */}
|
||||
<div className="absolute left-11 top-0 bottom-0 w-0.5 bg-gray-200"></div>
|
||||
|
||||
{events.map((event) => {
|
||||
const config = eventConfig[event.status];
|
||||
const typeInfo = typeConfig[event.type];
|
||||
|
||||
return (
|
||||
<div key={event.id} className="relative flex items-start mb-6">
|
||||
{/* Time and Icon */}
|
||||
<div className="flex-shrink-0 flex flex-col items-center mr-6 z-10">
|
||||
<div
|
||||
className={`w-12 h-12 rounded-full flex items-center justify-center ${config.bg}`}
|
||||
>
|
||||
<i className={`fa-solid ${config.icon} text-2xl ${config.color}`}></i>
|
||||
</div>
|
||||
<div className="mt-1 font-bold text-gray-700">
|
||||
{event.time.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card Details */}
|
||||
<div className="bg-gray-50 border border-gray-200 rounded-lg p-4 w-full">
|
||||
<div className="flex justify-between items-center">
|
||||
<p className={`font-semibold text-lg ${config.color}`}>
|
||||
{event.status.charAt(0).toUpperCase() + event.status.slice(1)}
|
||||
</p>
|
||||
<div
|
||||
className={`flex items-center text-sm font-medium ${typeInfo.color}`}
|
||||
title={event.type}
|
||||
>
|
||||
<i className={`fa-solid ${typeInfo.icon} mr-2`}></i>
|
||||
<span>{event.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 mt-1">
|
||||
Status jadwal untuk pukul{' '}
|
||||
{event.time.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' })}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DailyScheduleTimeline;
|
||||
@@ -0,0 +1,226 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { FeedingEvent } from '../../types';
|
||||
import { useAppContext } from '../../context/AppContext.tsx';
|
||||
|
||||
interface FeedScheduleFormModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (eventData: Omit<FeedingEvent, 'id' | 'status'>, id?: string) => void;
|
||||
eventToEdit: FeedingEvent | null;
|
||||
initialDate: Date;
|
||||
}
|
||||
|
||||
const FeedScheduleFormModal: React.FC<FeedScheduleFormModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
onSave,
|
||||
eventToEdit,
|
||||
initialDate,
|
||||
}) => {
|
||||
const { users, roles } = useAppContext();
|
||||
|
||||
const operators = useMemo(() => {
|
||||
const operatorRoleId = roles.find((r) => r.name === 'Operator')?.id;
|
||||
if (!operatorRoleId) return [];
|
||||
return users.filter((u) => u.roleId === operatorRoleId);
|
||||
}, [users, roles]);
|
||||
|
||||
const [time, setTime] = useState('08:00');
|
||||
const [type, setType] = useState<'AI Recommended' | 'Manual'>('Manual');
|
||||
const [operatorId, setOperatorId] = useState(operators[0]?.id || '');
|
||||
const [feedType, setFeedType] = useState('CP 511 Bravo');
|
||||
const [quantityKg, setQuantityKg] = useState('250');
|
||||
|
||||
useEffect(() => {
|
||||
if (eventToEdit) {
|
||||
const eventTime = eventToEdit.time;
|
||||
setTime(
|
||||
`${String(eventTime.getHours()).padStart(2, '0')}:${String(eventTime.getMinutes()).padStart(2, '0')}`
|
||||
);
|
||||
if (eventToEdit.type === 'AI Recommended' || eventToEdit.type === 'Manual') {
|
||||
setType(eventToEdit.type);
|
||||
}
|
||||
setOperatorId(eventToEdit.operatorId);
|
||||
setFeedType(eventToEdit.feedType);
|
||||
setQuantityKg(String(eventToEdit.quantityKg));
|
||||
} else {
|
||||
setTime('08:00');
|
||||
setType('Manual');
|
||||
setOperatorId(operators[0]?.id || '');
|
||||
setFeedType('CP 511 Bravo');
|
||||
setQuantityKg('250');
|
||||
}
|
||||
}, [eventToEdit, operators]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const [hours, minutes] = time.split(':').map(Number);
|
||||
const eventDate = eventToEdit ? new Date(eventToEdit.time) : new Date(initialDate);
|
||||
eventDate.setHours(hours, minutes, 0, 0);
|
||||
|
||||
const operator = operators.find((o) => o.id === operatorId);
|
||||
if (!operator) return;
|
||||
|
||||
const kg = parseInt(quantityKg, 10);
|
||||
|
||||
onSave(
|
||||
{
|
||||
time: eventDate,
|
||||
type,
|
||||
operatorId,
|
||||
operatorName: operator.name,
|
||||
feedType,
|
||||
quantityKg: kg,
|
||||
quantitySacks: Math.round(kg / 50),
|
||||
},
|
||||
eventToEdit?.id
|
||||
);
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-60 flex justify-center items-center z-50 p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-white rounded-xl shadow-2xl w-full max-w-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-2xl font-bold text-gray-800">
|
||||
{eventToEdit ? 'Edit Jadwal Pakan' : 'Tambah Jadwal Pakan'}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600 text-2xl"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Tanggal</label>
|
||||
<input
|
||||
type="text"
|
||||
readOnly
|
||||
value={(eventToEdit ? eventToEdit.time : initialDate).toLocaleDateString(
|
||||
'id-ID',
|
||||
{ weekday: 'long', day: 'numeric', month: 'long' }
|
||||
)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-gray-100"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="operatorId"
|
||||
className="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Petugas
|
||||
</label>
|
||||
<select
|
||||
id="operatorId"
|
||||
value={operatorId}
|
||||
onChange={(e) => setOperatorId(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white"
|
||||
required
|
||||
>
|
||||
{operators.map((o) => (
|
||||
<option key={o.id} value={o.id}>
|
||||
{o.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label htmlFor="time" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Waktu
|
||||
</label>
|
||||
<input
|
||||
type="time"
|
||||
id="time"
|
||||
value={time}
|
||||
onChange={(e) => setTime(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Tipe
|
||||
</label>
|
||||
<select
|
||||
id="type"
|
||||
value={type}
|
||||
onChange={(e) => setType(e.target.value as 'AI Recommended' | 'Manual')}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md bg-white"
|
||||
>
|
||||
<option value="Manual">Manual</option>
|
||||
<option value="AI Recommended">AI Recommended</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="feedType"
|
||||
className="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Jenis Pakan
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="feedType"
|
||||
value={feedType}
|
||||
onChange={(e) => setFeedType(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
htmlFor="quantityKg"
|
||||
className="block text-sm font-medium text-gray-700 mb-1"
|
||||
>
|
||||
Jumlah (Kg)
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="quantityKg"
|
||||
value={quantityKg}
|
||||
onChange={(e) => setQuantityKg(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm"
|
||||
required
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-gray-50 px-6 py-4 rounded-b-xl flex justify-end space-x-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="bg-gray-200 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-300 font-medium"
|
||||
>
|
||||
Batal
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 font-medium shadow-sm"
|
||||
>
|
||||
Simpan Jadwal
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedScheduleFormModal;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from 'react';
|
||||
import { ScheduleInfluencingFactors } from '../../types';
|
||||
import { formatNumber } from '../../utils/formatters.ts';
|
||||
|
||||
const statusStyles = {
|
||||
ok: 'text-green-600',
|
||||
warning: 'text-yellow-600',
|
||||
critical: 'text-red-600',
|
||||
};
|
||||
|
||||
const FactorCard: React.FC<{
|
||||
icon: string;
|
||||
label: string;
|
||||
value: number;
|
||||
unit: string;
|
||||
status: 'ok' | 'warning' | 'critical';
|
||||
}> = ({ icon, label, value, unit, status }) => (
|
||||
<div className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
|
||||
<div className={`text-2xl w-8 text-center ${statusStyles[status]}`}>
|
||||
<i className={`fa-solid ${icon}`}></i>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-gray-500 font-medium">{label}</p>
|
||||
<p className="font-bold text-gray-800">
|
||||
{formatNumber(value)} {unit}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface InfluencingFactorsProps {
|
||||
factors: ScheduleInfluencingFactors;
|
||||
}
|
||||
|
||||
const InfluencingFactors: React.FC<InfluencingFactorsProps> = ({ factors }) => {
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm">
|
||||
<h2 className="text-xl font-bold mb-4">Faktor-faktor Pengaruh</h2>
|
||||
<div className="space-y-3">
|
||||
<FactorCard
|
||||
icon="fa-temperature-high"
|
||||
label="Suhu Lingkungan"
|
||||
value={factors.temperature.value}
|
||||
unit={factors.temperature.unit}
|
||||
status={factors.temperature.status}
|
||||
/>
|
||||
<FactorCard
|
||||
icon="fa-droplet"
|
||||
label="Kelembapan"
|
||||
value={factors.humidity.value}
|
||||
unit={factors.humidity.unit}
|
||||
status={factors.humidity.status}
|
||||
/>
|
||||
<FactorCard
|
||||
icon="fa-person-running"
|
||||
label="Tingkat Aktivitas"
|
||||
value={factors.activityLevel.value}
|
||||
unit={factors.activityLevel.unit}
|
||||
status={factors.activityLevel.status}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfluencingFactors;
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { FeedScheduleRecommendation } from '../../types';
|
||||
|
||||
interface ScheduleOverviewProps {
|
||||
recommendation: FeedScheduleRecommendation;
|
||||
}
|
||||
|
||||
const ScheduleOverview: React.FC<ScheduleOverviewProps> = ({ recommendation }) => {
|
||||
const { nextFeedingTime, lastFeedingTime, reason } = recommendation;
|
||||
|
||||
const formatTime = (date: Date) =>
|
||||
date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit' });
|
||||
const formatDate = (date: Date) =>
|
||||
date.toLocaleDateString('id-ID', { weekday: 'long', day: 'numeric', month: 'long' });
|
||||
|
||||
return (
|
||||
<div className="bg-white p-6 rounded-xl shadow-sm border border-red-200 bg-red-50/50">
|
||||
<h2 className="text-xl font-bold text-gray-800 mb-4">Rekomendasi Jadwal AI</h2>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
<i className="fa-solid fa-clock text-4xl text-red-500"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-gray-600">Jadwal Pakan Berikutnya</p>
|
||||
<p className="text-3xl font-bold text-red-600">{formatTime(nextFeedingTime)}</p>
|
||||
<p className="text-sm text-gray-500">{formatDate(nextFeedingTime)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
<i className="fa-solid fa-check-circle text-4xl text-green-500"></i>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-semibold text-gray-600">Pemberian Pakan Terakhir</p>
|
||||
<p className="text-2xl font-bold text-gray-700">{formatTime(lastFeedingTime)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 pt-4 border-t border-red-200">
|
||||
<p className="text-sm font-semibold text-gray-600 flex items-center">
|
||||
<i className="fa-solid fa-lightbulb text-yellow-500 mr-2"></i> Alasan Rekomendasi AI:
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-700 italic">"{reason}"</p>
|
||||
</div>
|
||||
<div className="mt-6 pt-4 border-t border-red-200 flex items-center justify-end space-x-3">
|
||||
<button className="px-4 py-2 bg-white border border-gray-300 text-gray-800 text-sm font-semibold rounded-lg hover:bg-gray-100 transition shadow-sm">
|
||||
<i className="fa-solid fa-clock-rotate-left mr-2"></i> Tunda 30 Menit
|
||||
</button>
|
||||
<button className="px-4 py-2 bg-green-600 text-white text-sm font-semibold rounded-lg hover:bg-green-700 transition shadow-sm">
|
||||
<i className="fa-solid fa-play-circle mr-2"></i> Mulai Sekarang
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScheduleOverview;
|
||||
Reference in new issue
Block a user