diff --git a/app/Http/Controllers/Api/Admin/Setting/SettingController.php b/app/Http/Controllers/Api/Admin/Setting/SettingController.php index 8f1f667..b42f736 100644 --- a/app/Http/Controllers/Api/Admin/Setting/SettingController.php +++ b/app/Http/Controllers/Api/Admin/Setting/SettingController.php @@ -6,6 +6,7 @@ namespace App\Http\Controllers\Api\Admin\Setting; use App\Http\Controllers\Controller; use App\Http\Facade\SignRSA; +use App\Http\Services\Telegram; use App\Models\Setting; use App\Support\Cache\CacheKey; use Illuminate\Http\JsonResponse; @@ -102,6 +103,26 @@ class SettingController extends Controller ]); } + /** + * POST /administrator/settings/test-telegram + * Sends a test message using the currently saved Telegram config. + */ + public function testTelegram(): JsonResponse + { + $response = (new Telegram())->SendMessage('🔔 Đây là tin nhắn test từ Portal Admin — cấu hình Telegram đang hoạt động!'); + + if (!$response || !$response->successful()) { + return response()->json([ + 'error' => true, + 'message' => $response + ? 'Telegram trả về lỗi: ' . $response->body() + : 'Chưa cấu hình đủ Bot Token / Bot ID.', + ], 422); + } + + return response()->json(['error' => false, 'message' => 'Đã gửi tin nhắn test thành công.']); + } + public function uploadMedia(Request $request): JsonResponse { $request->validate([ diff --git a/config/cors.php b/config/cors.php index 9b6c369..ce26015 100644 --- a/config/cors.php +++ b/config/cors.php @@ -24,10 +24,9 @@ return [ 'http://localhost:5174', 'http://localhost:4173', 'https://portal.tgs.com.vn', - 'https://demo.client.tgs.com.vn', - 'https://demo.admin.tgs.com.vn', - 'http://demo.client.tgs.com.vn', - 'http://demo.admin.tgs.com.vn', + 'https://admin.portal.tgs.com.vn', + 'http://portal.tgs.com.vn', + 'http://admin.portal.tgs.com.vn', ], 'allowed_origins_patterns' => [], diff --git a/routes/api.php b/routes/api.php index 777e481..e7c2b63 100644 --- a/routes/api.php +++ b/routes/api.php @@ -454,6 +454,7 @@ Route::prefix('v1') ->group(function () { Route::get('/', 'index'); Route::put('/', 'update'); + Route::post('test-telegram', 'testTelegram'); }); Route::prefix('media') diff --git a/storage/.DS_Store b/storage/.DS_Store index ada9623..5e70cc7 100644 Binary files a/storage/.DS_Store and b/storage/.DS_Store differ diff --git a/storage/app/app-header.tsx b/storage/app/app-header.tsx deleted file mode 100644 index 6554823..0000000 --- a/storage/app/app-header.tsx +++ /dev/null @@ -1,514 +0,0 @@ -import { Fragment, useState } from 'react' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useNavigate, useRouterState } from '@tanstack/react-router' -import dayjs from 'dayjs' -import relativeTime from 'dayjs/plugin/relativeTime' -import 'dayjs/locale/vi' -import { - BellOutlined, - CheckOutlined, - ClockCircleOutlined, - GlobalOutlined, - InboxOutlined, - LogoutOutlined, - MoonOutlined, - QuestionCircleOutlined, - SearchOutlined, - SettingOutlined, - ShoppingCartOutlined, - TeamOutlined, - UserOutlined, -} from '@ant-design/icons' -import { - Avatar, - Badge, - Breadcrumb, - Button, - ConfigProvider, - Divider, - Dropdown, - Flex, - Input, - List, - Popover, - Select, - Space, - Switch, - Tag, - Tooltip, - Typography, -} from 'antd' -import { useAuthStore } from '@/stores/auth-store' -import { SidebarTrigger } from '@/components/ui/sidebar' -import { - fetchNotifications, - markAllNotificationsRead, - markNotificationRead, - type NotificationItem, -} from '@/features/notifications/api' - -dayjs.extend(relativeTime) -dayjs.locale('vi') - -const { Text } = Typography - -// ---------------------------------------------------------------------------- -// Breadcrumb labels (mirrors src/components/layout/app-breadcrumb.tsx) -// ---------------------------------------------------------------------------- - -const segmentLabels: Record = { - '': 'Tổng quan', - dashboard: 'Tổng quan', - create: 'Thêm mới', - groups: 'Nhóm', - tasks: 'Công việc', - customers: 'Khách hàng', - 'product-service': 'Sản phẩm / Dịch vụ', - products: 'Sản phẩm', - 'product-groups': 'Nhóm sản phẩm', - 'vcenter-manager': 'vCenter Manager', - 'infra-management': 'Hạ tầng', - 'order-invoice': 'Đơn hàng / Hoá đơn', - apps: 'Ứng dụng', - chats: 'Tin nhắn', - users: 'Người dùng', - settings: 'Cài đặt', - account: 'Tài khoản', - appearance: 'Giao diện', - notifications: 'Thông báo', - display: 'Hiển thị', - 'help-center': 'Trung tâm trợ giúp', - tickets: 'Ticket hỗ trợ', - roles: 'Vai trò', - 'activity-logs': 'Nhật ký hoạt động', - 'server-manager': 'Server Manager', - 'domain-manager': 'Domain Manager', - 'domain-fluctuations': 'Khai báo biến động tên miền', - 'ip-pool': 'IP Pool', - 'os-groups': 'OS Groups', - addons: 'Addons', - transactions: 'Giao dịch', - posts: 'Bài viết', - 'post-categories': 'Danh mục bài viết', - resolve: 'Phân giải', -} - -function labelFor(segment: string) { - if (/^\d+$/.test(segment)) return 'Chi tiết' - return segmentLabels[segment] ?? segment -} - -// ---------------------------------------------------------------------------- -// Notifications -// ---------------------------------------------------------------------------- - -const notifIconMap: Record< - NotificationItem['type'], - { icon: typeof BellOutlined; color: string } -> = { - ticket_reply: { icon: TeamOutlined, color: '#722ed1' }, - deposit_success: { icon: ShoppingCartOutlined, color: '#f16723' }, -} - -// ---------------------------------------------------------------------------- -// AppHeader -// ---------------------------------------------------------------------------- - -export function AppHeader() { - const pathname = useRouterState({ select: (s) => s.location.pathname }) - const { auth } = useAuthStore() - - const [notifOpen, setNotifOpen] = useState(false) - const [settingsOpen, setSettingsOpen] = useState(false) - const [darkMode, setDarkMode] = useState(false) - const [compact, setCompact] = useState(false) - const [language, setLanguage] = useState<'vi' | 'en'>('vi') - - const qc = useQueryClient() - const { data: notifData } = useQuery({ - queryKey: ['notifications'], - queryFn: fetchNotifications, - refetchInterval: 60_000, - }) - const notifs = notifData?.data.items ?? [] - const unreadCount = notifData?.data.unread_count ?? 0 - - const markOneRead = useMutation({ - mutationFn: markNotificationRead, - onSuccess: () => qc.invalidateQueries({ queryKey: ['notifications'] }), - }) - const markAllRead = useMutation({ - mutationFn: markAllNotificationsRead, - onSuccess: () => qc.invalidateQueries({ queryKey: ['notifications'] }), - }) - - const segments = pathname.split('/').filter(Boolean) - const crumbs = - segments.length === 0 - ? [{ title: Tổng quan }] - : segments.map((seg, idx) => { - const isLast = idx === segments.length - 1 - const label = labelFor(seg) - return { - title: isLast ? {label} : {label}, - } - }) - - const fullName = auth.user - ? `${auth.user.firstname ?? ''} ${auth.user.lastname ?? ''}`.trim() || - auth.user.username - : 'Khách' - const userRole = auth.user?.roles?.[0] ?? 'Người dùng' - const initials = - fullName - .split(' ') - .filter(Boolean) - .slice(-2) - .map((s) => s[0]?.toUpperCase()) - .join('') || 'U' - - const navigate = useNavigate() - - const userMenuItems = [ - { - key: 'profile', - icon: , - label: 'Hồ sơ', - onClick: () => navigate({ to: '/settings' }), - }, - { - key: 'logout', - icon: , - label: 'Đăng xuất', - danger: true, - onClick: () => auth.reset(), - }, - ] - - return ( - -
- - -
- -
-
- } - placeholder='Tìm kiếm...' - className='!hidden lg:!inline-flex' - style={{ width: 280 }} - /> - {/* Mobile/Tablet: search as icon button only */} - - - - - -
- {notifs.length === 0 ? ( -
- -
- Chưa có thông báo -
-
- ) : ( - { - const meta = notifIconMap[n.type] ?? { icon: BellOutlined, color: '#8c8c8c' } - const Icon = meta.icon - const unread = !n.is_read - return ( - { if (unread) markOneRead.mutate(n.id) }} - style={{ - padding: '10px 8px', - borderRadius: 6, - cursor: 'pointer', - background: unread ? '#f0f7ff' : 'transparent', - marginBottom: 4, - }} - > - -
- -
-
- - - {n.title} - - {unread && ( - - )} - - {n.body && ( - - {n.body} - - )} - - {dayjs(n.created_at).fromNow()} - -
-
-
- ) - }} - /> - )} -
- - -
- } - > - - -