ai insight rework #2

This commit is contained in:
Alberto-Audrix committed 2026-10-01 13:23:45 +07:00
1 parent 9d76f54d33
commit e1987d6999
39 files changed
+2311 -830

No files matched your search

+86 -85
View File
@@ -55,7 +55,9 @@ const SettingsPage: React.FC = () => {
const [saving, setSaving] = useState(false);
const [siteName, setSiteName] = useState('');
const [kandangName, setKandangName] = useState('');
const [feedInButtonUrls, setFeedInButtonUrls] = useState<FeedInButtonUrl[]>([emptyFeedInButtonUrl()]);
const [feedInButtonUrls, setFeedInButtonUrls] = useState<FeedInButtonUrl[]>([
emptyFeedInButtonUrl(),
]);
const [flockName, setFlockName] = useState('');
const [editingCycleId, setEditingCycleId] = useState<number | null>(null);
const [startDate, setStartDate] = useState(defaultCycleForm().startDate);
@@ -135,7 +137,9 @@ const SettingsPage: React.FC = () => {
React.useEffect(() => {
const urls = selectedKandang?.feed_in_button_urls ?? [];
setFeedInButtonUrls(urls.length > 0 ? urls.map((entry) => ({ ...entry })) : [emptyFeedInButtonUrl()]);
setFeedInButtonUrls(
urls.length > 0 ? urls.map((entry) => ({ ...entry })) : [emptyFeedInButtonUrl()]
);
}, [selectedKandang?.id, selectedKandang?.feed_in_button_urls]);
const wrap = async (fn: () => Promise<void>) => {
@@ -155,10 +159,7 @@ const SettingsPage: React.FC = () => {
return (
<div>
<PageHeader
title="Pengaturan"
subtitle="Site, kandang, lantai (IoT), dan siklus produksi"
/>
<PageHeader title="Pengaturan" subtitle="Site, kandang, lantai (IoT), dan siklus produksi" />
{error && (
<div className="mb-4">
<Banner kind="error">{error}</Banner>
@@ -284,9 +285,7 @@ const SettingsPage: React.FC = () => {
type="button"
className="border border-red-600 text-red-600 px-4 py-2 rounded-lg text-sm font-semibold hover:bg-red-50 disabled:opacity-50"
disabled={
saving ||
!registerCode ||
slugify(registerCode) === pusatRegistration.code
saving || !registerCode || slugify(registerCode) === pusatRegistration.code
}
onClick={() => {
const code = slugify(registerCode);
@@ -438,7 +437,9 @@ const SettingsPage: React.FC = () => {
type="button"
className="text-xs text-red-500 hover:underline"
onClick={() =>
setFeedInButtonUrls((rows) => rows.filter((_, rowIndex) => rowIndex !== index))
setFeedInButtonUrls((rows) =>
rows.filter((_, rowIndex) => rowIndex !== index)
)
}
>
Hapus URL
@@ -510,42 +511,46 @@ const SettingsPage: React.FC = () => {
{flocks.map((flock) => {
const webhookUrl = `${window.location.origin}/api/v1/panel-iot/push/${flock.id}/`;
return (
<li key={flock.id} className="py-3 space-y-2">
<div className="flex items-center justify-between gap-2">
<span className="text-sm font-medium text-gray-700">{flock.flock_name}</span>
{isSuperAdmin && (
<button
type="button"
className="text-xs text-red-500 hover:underline"
onClick={() =>
void wrap(async () => {
await api.flocks.remove(flock.id);
flash('Lantai dihapus.');
})
}
>
Hapus
</button>
)}
</div>
<div className="space-y-1">
<label className="block text-xs text-gray-500">Webhook URL (IoT Panel → Dashboard)</label>
<div className="flex items-center gap-2 rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
<code className="flex-1 text-xs text-gray-700 break-all font-mono">{webhookUrl}</code>
<button
type="button"
className="shrink-0 text-xs text-red-600 hover:underline font-semibold"
onClick={() => {
void navigator.clipboard.writeText(webhookUrl);
flash('Webhook URL disalin.');
}}
>
Salin
</button>
<li key={flock.id} className="py-3 space-y-2">
<div className="flex items-center justify-between gap-2">
<span className="text-sm font-medium text-gray-700">{flock.flock_name}</span>
{isSuperAdmin && (
<button
type="button"
className="text-xs text-red-500 hover:underline"
onClick={() =>
void wrap(async () => {
await api.flocks.remove(flock.id);
flash('Lantai dihapus.');
})
}
>
Hapus
</button>
)}
</div>
</div>
</li>
);
<div className="space-y-1">
<label className="block text-xs text-gray-500">
Webhook URL (IoT Panel → Dashboard)
</label>
<div className="flex items-center gap-2 rounded-lg bg-gray-50 border border-gray-200 px-3 py-2">
<code className="flex-1 text-xs text-gray-700 break-all font-mono">
{webhookUrl}
</code>
<button
type="button"
className="shrink-0 text-xs text-red-600 hover:underline font-semibold"
onClick={() => {
void navigator.clipboard.writeText(webhookUrl);
flash('Webhook URL disalin.');
}}
>
Salin
</button>
</div>
</div>
</li>
);
})}
</ul>
)}
@@ -638,42 +643,42 @@ const SettingsPage: React.FC = () => {
{cycles.map((cycle) => {
const status = resolveCycleStatus(cycle);
return (
<li key={cycle.id} className="py-2 text-sm text-gray-700">
<div className="flex justify-between gap-2">
<button className="text-left" onClick={() => setSelectedCycleId(cycle.id)}>
{formatDateId(cycle.start_date)}
{cycle.end_date ? ` – ${formatDateId(cycle.end_date)}` : ''}
<span className="block text-xs text-gray-400">
{statusLabel(cycle)}
{status === 'pending_close' && cycle.proposed_end_date
? ` · usulan tutup ${formatDateId(cycle.proposed_end_date)}`
: ''}{' '}
· {cycle.doc_in_count} ekor · {cycle.chick_in_weight} g
</span>
</button>
<div className="flex flex-col items-end gap-1">
<button
className="text-xs text-blue-600 hover:underline"
onClick={() => beginCycleEdit(cycle)}
>
Ubah
</button>
<button
className="text-xs text-red-500 hover:underline"
onClick={() =>
void wrap(async () => {
if (editingCycleId === cycle.id) resetCycleForm();
await api.cycles.remove(cycle.id);
flash('Siklus dihapus.');
})
}
>
Hapus
<li key={cycle.id} className="py-2 text-sm text-gray-700">
<div className="flex justify-between gap-2">
<button className="text-left" onClick={() => setSelectedCycleId(cycle.id)}>
{formatDateId(cycle.start_date)}
{cycle.end_date ? ` – ${formatDateId(cycle.end_date)}` : ''}
<span className="block text-xs text-gray-400">
{statusLabel(cycle)}
{status === 'pending_close' && cycle.proposed_end_date
? ` · usulan tutup ${formatDateId(cycle.proposed_end_date)}`
: ''}{' '}
· {cycle.doc_in_count} ekor · {cycle.chick_in_weight} g
</span>
</button>
<div className="flex flex-col items-end gap-1">
<button
className="text-xs text-blue-600 hover:underline"
onClick={() => beginCycleEdit(cycle)}
>
Ubah
</button>
<button
className="text-xs text-red-500 hover:underline"
onClick={() =>
void wrap(async () => {
if (editingCycleId === cycle.id) resetCycleForm();
await api.cycles.remove(cycle.id);
flash('Siklus dihapus.');
})
}
>
Hapus
</button>
</div>
</div>
</div>
</li>
);
</li>
);
})}
</ul>
</section>
@@ -681,9 +686,7 @@ const SettingsPage: React.FC = () => {
<ConfirmDialog
open={pusatConfirm != null}
title={
pusatConfirm?.kind === 'change-code' ? 'Ubah kode pusat?' : 'Daftar ke pusat?'
}
title={pusatConfirm?.kind === 'change-code' ? 'Ubah kode pusat?' : 'Daftar ke pusat?'}
message={
pusatConfirm?.kind === 'change-code' ? (
<p>
@@ -698,9 +701,7 @@ const SettingsPage: React.FC = () => {
</p>
) : null
}
confirmLabel={
pusatConfirm?.kind === 'change-code' ? 'Ya, ubah kode' : 'Ya, daftar'
}
confirmLabel={pusatConfirm?.kind === 'change-code' ? 'Ya, ubah kode' : 'Ya, daftar'}
busy={saving}
onCancel={() => {
if (!saving) setPusatConfirm(null);