car_mms/app/routes/settings.tsx
2025-09-11 14:22:27 +03:00

314 lines
13 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from '@remix-run/node';
import { Form, useActionData, useLoaderData, useNavigation } from '@remix-run/react';
import { useState } from 'react';
import { requireAuth } from '~/lib/auth-middleware.server';
import { DashboardLayout } from '~/components/layout/DashboardLayout';
import {
getAppSettings,
updateSettings,
type AppSettings,
initializeDefaultSettings
} from '~/lib/settings-management.server';
export async function loader({ request }: LoaderFunctionArgs) {
const user = await requireAuth(request, 2); // Admin level required (1=superadmin, 2=admin)
try {
// Initialize default settings if needed
await initializeDefaultSettings();
const settings = await getAppSettings();
return json({ user, settings, success: true });
} catch (error) {
console.error('Settings loader error:', error);
return json({
user,
settings: null,
success: false,
error: 'Failed to load settings'
});
}
}
export async function action({ request }: ActionFunctionArgs) {
await requireAuth(request, 2); // Admin level required
const formData = await request.formData();
const intent = formData.get('intent');
if (intent === 'updateSettings') {
try {
const settings: Partial<AppSettings> = {
dateFormat: formData.get('dateFormat') as 'ar-SA' | 'en-US',
currency: formData.get('currency') as string,
numberFormat: formData.get('numberFormat') as 'ar-SA' | 'en-US',
currencySymbol: formData.get('currencySymbol') as string,
dateDisplayFormat: formData.get('dateDisplayFormat') as string,
};
await updateSettings(settings);
return json({
success: true,
message: 'Settings updated successfully'
});
} catch (error) {
console.error('Settings update error:', error);
return json({
success: false,
error: 'Failed to update settings'
});
}
}
return json({ success: false, error: 'Invalid action' });
}
export default function SettingsPage() {
const { user, settings, success, error } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const navigation = useNavigation();
const isSubmitting = navigation.state === 'submitting';
const [formData, setFormData] = useState<AppSettings>(
settings || {
dateFormat: 'ar-SA',
currency: 'JOD',
numberFormat: 'ar-SA',
currencySymbol: 'د.أ',
dateDisplayFormat: 'dd/MM/yyyy'
}
);
const handleInputChange = (key: keyof AppSettings, value: string) => {
setFormData(prev => ({ ...prev, [key]: value }));
};
if (!success || !settings) {
return (
<DashboardLayout user={user}>
<div className="container mx-auto px-4 py-8">
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
<h2 className="text-lg font-semibold text-red-800 mb-2">خطأ في تحميل الإعدادات</h2>
<p className="text-red-600">{error || 'Failed to load settings'}</p>
</div>
</div>
</DashboardLayout>
);
}
return (
<DashboardLayout user={user}>
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
<div className="bg-white rounded-lg shadow-md">
<div className="px-6 py-4 border-b border-gray-200">
<h1 className="text-2xl font-bold text-gray-900"> إعدادات النظام</h1>
<p className="text-gray-600 mt-1">تكوين إعدادات التطبيق العامة</p>
</div>
<div className="p-6">
{actionData?.success && (
<div className="mb-6 bg-green-50 border border-green-200 rounded-lg p-4">
<p className="text-green-800"> {actionData.message}</p>
</div>
)}
{actionData?.error && (
<div className="mb-6 bg-red-50 border border-red-200 rounded-lg p-4">
<p className="text-red-800"> {actionData.error}</p>
</div>
)}
<Form method="post" className="space-y-6">
<input type="hidden" name="intent" value="updateSettings" />
{/* Date Format Settings */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="text-lg font-semibold text-gray-900 mb-4">📅 إعدادات التاريخ</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
تنسيق التاريخ
</label>
<select
name="dateFormat"
value={formData.dateFormat}
onChange={(e) => handleInputChange('dateFormat', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="ar-SA">عربي (ar-SA) - ٢٠٢٥/١١/٩</option>
<option value="en-US">إنجليزي (en-US) - 11/9/2025</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
نمط عرض التاريخ
</label>
<select
name="dateDisplayFormat"
value={formData.dateDisplayFormat}
onChange={(e) => handleInputChange('dateDisplayFormat', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="dd/MM/yyyy">يوم/شهر/سنة (09/11/2025)</option>
<option value="MM/dd/yyyy">شهر/يوم/سنة (11/09/2025)</option>
<option value="yyyy-MM-dd">سنة-شهر-يوم (2025-11-09)</option>
</select>
</div>
</div>
<div className="mt-4 p-3 bg-blue-50 rounded-md">
<p className="text-sm text-blue-800">
<strong>معاينة:</strong> {new Date().toLocaleDateString(formData.dateFormat)}
</p>
</div>
</div>
{/* Currency Settings */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="text-lg font-semibold text-gray-900 mb-4">💰 إعدادات العملة</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
رمز العملة
</label>
<select
name="currency"
value={formData.currency}
onChange={(e) => handleInputChange('currency', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="JOD">دينار أردني (JOD)</option>
<option value="USD">دولار أمريكي (USD)</option>
<option value="EUR">يورو (EUR)</option>
<option value="SAR">ريال سعودي (SAR)</option>
<option value="AED">درهم إماراتي (AED)</option>
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
رمز العملة المعروض
</label>
<input
type="text"
name="currencySymbol"
value={formData.currencySymbol}
onChange={(e) => handleInputChange('currencySymbol', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="د.أ"
/>
</div>
</div>
<div className="mt-4 p-3 bg-blue-50 rounded-md">
<p className="text-sm text-blue-800">
<strong>معاينة:</strong> {(1234.56).toLocaleString(formData.numberFormat, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} {formData.currencySymbol}
</p>
</div>
</div>
{/* Number Format Settings */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="text-lg font-semibold text-gray-900 mb-4">🔢 إعدادات الأرقام</h3>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
تنسيق الأرقام
</label>
<select
name="numberFormat"
value={formData.numberFormat}
onChange={(e) => handleInputChange('numberFormat', e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="ar-SA">عربي (ar-SA) - ١٬٢٣٤٫٥٦</option>
<option value="en-US">إنجليزي (en-US) - 1,234.56</option>
</select>
</div>
<div className="mt-4 p-3 bg-blue-50 rounded-md">
<p className="text-sm text-blue-800">
<strong>معاينة الأرقام:</strong> {(123456.789).toLocaleString(formData.numberFormat)}
</p>
<p className="text-sm text-blue-800">
<strong>معاينة الكيلومترات:</strong> {(45000).toLocaleString(formData.numberFormat)} كم
</p>
</div>
</div>
{/* Action Buttons */}
<div className="flex justify-end space-x-4 pt-6 border-t border-gray-200">
<button
type="button"
onClick={() => setFormData(settings)}
className="px-4 py-2 text-gray-700 bg-gray-200 rounded-md hover:bg-gray-300 transition-colors"
disabled={isSubmitting}
>
إعادة تعيين
</button>
<button
type="submit"
disabled={isSubmitting}
className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50 transition-colors"
>
{isSubmitting ? 'جاري الحفظ...' : 'حفظ الإعدادات'}
</button>
</div>
</Form>
</div>
</div>
{/* Settings Preview Section */}
<div className="mt-8 bg-white rounded-lg shadow-md">
<div className="px-6 py-4 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">👁 معاينة الإعدادات</h2>
</div>
<div className="p-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="font-medium text-gray-900 mb-2">التاريخ والوقت</h4>
<p className="text-sm text-gray-600">
التاريخ: {new Date().toLocaleDateString(formData.dateFormat)}
</p>
<p className="text-sm text-gray-600">
التاريخ والوقت: {new Date().toLocaleString(formData.dateFormat)}
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="font-medium text-gray-900 mb-2">العملة والأرقام</h4>
<p className="text-sm text-gray-600">
السعر: {(250.75).toLocaleString(formData.numberFormat, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} {formData.currencySymbol}
</p>
<p className="text-sm text-gray-600">
الكيلومترات: {(87500).toLocaleString(formData.numberFormat)} كم
</p>
</div>
<div className="bg-gray-50 p-4 rounded-lg">
<h4 className="font-medium text-gray-900 mb-2">أرقام كبيرة</h4>
<p className="text-sm text-gray-600">
المبلغ الإجمالي: {(1234567.89).toLocaleString(formData.numberFormat, {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})} {formData.currencySymbol}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</DashboardLayout>
);
}