diff --git a/src/app/alert-settings/page.tsx b/src/app/alert-settings/page.tsx index d848aab..ea7589a 100644 --- a/src/app/alert-settings/page.tsx +++ b/src/app/alert-settings/page.tsx @@ -6,7 +6,8 @@ import { AlertConditionDto, CreateAlertConditionDto, UpdateAlertConditionDto, - AlertRuleDto + AlertRuleDto, + CreateAlertRuleRequest } from '@/lib/api' import { Bell, @@ -77,7 +78,7 @@ function AlertSettingsContent() { deviceId: deviceId, notificationType: 0, timeType: 2, - isActive: true, + isEnabled: true, rules: [] }) @@ -103,11 +104,12 @@ function AlertSettingsContent() { deviceId: deviceId, notificationType: 0, timeType: 2, - isActive: true, + isEnabled: true, rules: [{ sensorType: 0, comparisonType: 0, - threshold: 30 + value1: 30, + order: 0 }] }) setShowModal(true) @@ -116,11 +118,17 @@ function AlertSettingsContent() { const openEditModal = (alert: AlertConditionDto) => { setEditingAlert(alert) setFormData({ - deviceId: alert.deviceId, notificationType: alert.notificationType, timeType: alert.timeType, - isActive: alert.isActive, - rules: alert.rules.map(r => ({ ...r })) + isEnabled: alert.isEnabled, + deviceId: alert.deviceId, + rules: alert.rules.map((r, idx) => ({ + sensorType: r.sensorType, + comparisonType: r.comparisonType, + value1: r.value1, + value2: r.value2, + order: idx + })) }) setShowModal(true) } @@ -181,11 +189,16 @@ function AlertSettingsContent() { try { const updateDto: UpdateAlertConditionDto = { id: alert.id, - deviceId: alert.deviceId, notificationType: alert.notificationType, timeType: alert.timeType, - isActive: !alert.isActive, - rules: alert.rules + isEnabled: !alert.isEnabled, + rules: alert.rules.map((r, idx) => ({ + sensorType: r.sensorType, + comparisonType: r.comparisonType, + value1: r.value1, + value2: r.value2, + order: idx + })) } await api.updateAlertCondition(updateDto) await loadAlerts() @@ -203,20 +216,24 @@ function AlertSettingsContent() { { sensorType: 0, comparisonType: 0, - threshold: 30 + value1: 30, + order: formData.rules.length } ] }) } const removeRule = (index: number) => { + const newRules = formData.rules + .filter((_, i) => i !== index) + .map((r, idx) => ({ ...r, order: idx })) setFormData({ ...formData, - rules: formData.rules.filter((_, i) => i !== index) + rules: newRules }) } - const updateRule = (index: number, updates: Partial) => { + const updateRule = (index: number, updates: Partial) => { const newRules = [...formData.rules] newRules[index] = { ...newRules[index], ...updates } setFormData({ ...formData, rules: newRules }) @@ -255,51 +272,62 @@ function AlertSettingsContent() { const formatRuleText = (rule: AlertRuleDto) => { const comp = COMPARISON_TYPES.find(c => c.value === rule.comparisonType) if (comp?.needsMax) { - return `${getComparisonLabel(rule.comparisonType)}: ${rule.threshold} - ${rule.thresholdMax ?? '?'} ${getSensorUnit(rule.sensorType)}` + return `${getComparisonLabel(rule.comparisonType)}: ${rule.value1} - ${rule.value2 ?? '?'} ${getSensorUnit(rule.sensorType)}` } const symbol = rule.comparisonType === 0 ? '>' : rule.comparisonType === 1 ? '<' : '?' - return `${symbol} ${rule.threshold} ${getSensorUnit(rule.sensorType)}` + return `${symbol} ${rule.value1} ${getSensorUnit(rule.sensorType)}` } const generatePreviewText = () => { if (formData.rules.length === 0) { - return 'هنوز هیچ شرطی تعریف نشده است.' + return '🤔 هنوز شرطی تعریف نکردی!' } // نوع اطلاع‌رسانی - const notifText = formData.notificationType === 0 ? 'تماس تلفنی' : 'پیامک' + const notifText = formData.notificationType === 0 ? 'بهت زنگ بزنم' : 'برات پیامک بفرستم' // زمان const timeText = formData.timeType === 0 - ? 'در روز' + ? 'فقط توی روز' : formData.timeType === 1 - ? 'در شب' - : 'در هر زمان' + ? 'فقط توی شب' + : '' // قوانین const rulesText = formData.rules.map(rule => { const sensorName = getSensorLabel(rule.sensorType) + const unit = getSensorUnit(rule.sensorType) const comp = COMPARISON_TYPES.find(c => c.value === rule.comparisonType) if (comp?.needsMax) { // بین یا خارج از محدوده - const compText = rule.comparisonType === 2 ? 'بین' : 'خارج از' - return `${sensorName} ${compText} ${rule.threshold} تا ${rule.thresholdMax ?? '؟'} ${getSensorUnit(rule.sensorType)}` + if (rule.comparisonType === 2) { + return `${sensorName} بین ${rule.value1} تا ${rule.value2 ?? '؟'} ${unit} باشه` + } else { + return `${sensorName} از بازه ${rule.value1} تا ${rule.value2 ?? '؟'} ${unit} خارج شه` + } } else { // بزرگتر یا کوچکتر - const compText = rule.comparisonType === 0 ? 'بیشتر از' : 'کمتر از' - return `${sensorName} ${compText} ${rule.threshold} ${getSensorUnit(rule.sensorType)}` + const compText = rule.comparisonType === 0 ? 'از' : 'از' + const direction = rule.comparisonType === 0 ? 'بالاتر' : 'پایین‌تر' + return `${sensorName} ${direction} ${compText} ${rule.value1} ${unit} بره` } }) // ساخت جمله نهایی + let baseText = '' if (rulesText.length === 1) { - return `ارسال ${notifText} برای زمانی که ${timeText} ${rulesText[0]} باشد.` + baseText = `وقتی ${rulesText[0]}` } else { const lastRule = rulesText[rulesText.length - 1] const otherRules = rulesText.slice(0, -1).join(' و ') - return `ارسال ${notifText} برای زمانی که ${timeText} ${otherRules} و ${lastRule} باشند.` + baseText = `وقتی ${otherRules} و ${lastRule}` } + + // اضافه کردن زمان اگر مشخص شده + const timePrefix = timeText ? `${timeText}، ` : '' + + return `${timePrefix}${baseText}، ${notifText} 📱` } if (loading) { @@ -358,7 +386,7 @@ function AlertSettingsContent() { ) : (
{alerts.map((alert) => ( -
+
{/* Header */} @@ -388,12 +416,12 @@ function AlertSettingsContent() {
{/* Modal Body */} -
+ + {/* Preview - Sticky at top */} +
+
+
+
+ +
+
+
📢 پیش‌نمایش زنده
+
+ {generatePreviewText()} +
+
+
+
+
+ +
{/* Notification Type */}
-
+
{formData.rules.map((rule, index) => { const needsMax = COMPARISON_TYPES.find(c => c.value === rule.comparisonType)?.needsMax + const selectedSensor = SENSOR_TYPES.find(s => s.value === rule.sensorType) + const SensorIcon = selectedSensor?.icon + const selectedComp = COMPARISON_TYPES.find(c => c.value === rule.comparisonType) + return ( -
-
- - {formData.rules.length === 1 ? 'شرط' : `شرط ${index + 1}`} - +
+
+
+ + {formData.rules.length === 1 ? '⚡ شرط' : `⚡ شرط ${index + 1}`} + +
{formData.rules.length > 1 && (
- {/* Sensor Type */} + {/* Sensor Type - Dropdown Style */}
-
- {/* Preview */} -
-
-
- -
-
-
پیش‌نمایش هشدار
-
- {generatePreviewText()} -
-
-
-
- {/* Active Status */}
setFormData({ ...formData, isActive: e.target.checked })} + id="isEnabled" + checked={formData.isEnabled} + onChange={(e) => setFormData({ ...formData, isEnabled: e.target.checked })} className="w-4 h-4 text-orange-600 border-gray-300 rounded focus:ring-orange-500" /> -
@@ -717,6 +778,7 @@ function AlertSettingsContent() { {saving ? 'در حال ذخیره...' : editingAlert ? 'ذخیره تغییرات' : 'افزودن هشدار'}
+
diff --git a/src/lib/api.ts b/src/lib/api.ts index 7407c5a..869b538 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -93,19 +93,32 @@ export type DailyReportDto = { } export type AlertRuleDto = { - id?: number + id: number + alertConditionId: number sensorType: 0 | 1 | 2 | 3 | 4 // Temperature=0, Humidity=1, Soil=2, Gas=3, Lux=4 comparisonType: 0 | 1 | 2 | 3 // GreaterThan=0, LessThan=1, Between=2, OutOfRange=3 - threshold: number - thresholdMax?: number // برای Between و OutOfRange + value1: number + value2?: number // برای Between و OutOfRange + order: number +} + +export type CreateAlertRuleRequest = { + sensorType: 0 | 1 | 2 | 3 | 4 + comparisonType: 0 | 1 | 2 | 3 + value1: number + value2?: number + order: number } export type AlertConditionDto = { id: number deviceId: number + deviceName: string notificationType: 0 | 1 // Call=0, SMS=1 timeType: 0 | 1 | 2 // Day=0, Night=1, Always=2 - isActive: boolean + callCooldownMinutes: number + smsCooldownMinutes: number + isEnabled: boolean rules: AlertRuleDto[] createdAt: string updatedAt: string @@ -115,17 +128,20 @@ export type CreateAlertConditionDto = { deviceId: number notificationType: 0 | 1 timeType: 0 | 1 | 2 - isActive: boolean - rules: AlertRuleDto[] + callCooldownMinutes?: number + smsCooldownMinutes?: number + isEnabled: boolean + rules: CreateAlertRuleRequest[] } export type UpdateAlertConditionDto = { id: number - deviceId: number notificationType: 0 | 1 timeType: 0 | 1 | 2 - isActive: boolean - rules: AlertRuleDto[] + callCooldownMinutes?: number + smsCooldownMinutes?: number + isEnabled: boolean + rules: CreateAlertRuleRequest[] } const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? 'https://ghback.nabaksoft.ir' @@ -196,9 +212,8 @@ export const api = { http(`${API_BASE}/api/DailyReport?deviceId=${deviceId}&persianDate=${encodeURIComponent(persianDate)}`), // Alert Conditions - getAlertConditions: (deviceId?: number) => { - const params = deviceId ? `?deviceId=${deviceId}` : '' - return http(`${API_BASE}/api/alertconditions${params}`) + getAlertConditions: (deviceId: number) => { + return http(`${API_BASE}/api/alertconditions/device/${deviceId}`) }, getAlertCondition: (id: number) => http(`${API_BASE}/api/alertconditions/${id}`),