From abc533cf864f852353fa6658ff3a3a234739bfaf Mon Sep 17 00:00:00 2001 From: Vipeesh Date: Mon, 3 Aug 2026 08:15:02 +0000 Subject: [PATCH] feat: update ASM UI with latest changes --- asm_app/src/components/Header.tsx | 79 +- asm_app/src/components/QRScanner.tsx | 240 +++ asm_app/src/components/Sidebar.tsx | 122 +- asm_app/src/config/api.ts | 1 + asm_app/src/locales/ar/translation.json | 18 +- asm_app/src/locales/en/translation.json | 18 +- asm_app/src/pages/AssetDetail.tsx | 8 +- asm_app/src/pages/AssetList.tsx | 17 +- asm_app/src/pages/AssetMaintenanceDetail.tsx | 6 +- asm_app/src/pages/AssetMaintenanceList.tsx | 8 +- asm_app/src/pages/EventsList.tsx | 2 +- asm_app/src/pages/IssueDetail.tsx | 6 +- asm_app/src/pages/IssueList.tsx | 6 +- asm_app/src/pages/ItemDetail.tsx | 6 +- asm_app/src/pages/ItemList.tsx | 8 +- asm_app/src/pages/Login.tsx | 630 +++--- asm_app/src/pages/MaintenanceTeamDetail.tsx | 6 +- asm_app/src/pages/MaintenanceTeamList.tsx | 6 +- asm_app/src/pages/ModernDashboard.tsx | 413 +++- asm_app/src/pages/PPMDetail.tsx | 6 +- asm_app/src/pages/PPMList.tsx | 8 +- asm_app/src/pages/PPMPlanner.tsx | 7 +- asm_app/src/pages/UserProfilePage.tsx | 38 +- asm_app/src/pages/UsersList.tsx | 2 +- asm_app/src/pages/WorkOrderDetail.tsx | 6 +- asm_app/src/pages/WorkOrderList.tsx | 14 +- asm_app/src/services/apiService.ts | 96 + asm_app/src/utils/qrNavigation.ts | 49 + .../public/asm_app/assets/index-BIWpBVtY.js | 1735 ---------------- .../public/asm_app/assets/index-BOZnpaxf.css | 1 - .../public/asm_app/assets/index-DpwoT1cm.css | 1 + .../public/asm_app/assets/index-rQSfSDmu.js | 1740 +++++++++++++++++ asm_ui_app/public/asm_app/index.html | 4 +- asm_ui_app/www/asm_app.html | 4 +- asm_ui_app/www/update-password.html | 333 ++++ 35 files changed, 3347 insertions(+), 2297 deletions(-) create mode 100644 asm_app/src/components/QRScanner.tsx create mode 100644 asm_app/src/utils/qrNavigation.ts delete mode 100644 asm_ui_app/public/asm_app/assets/index-BIWpBVtY.js delete mode 100644 asm_ui_app/public/asm_app/assets/index-BOZnpaxf.css create mode 100644 asm_ui_app/public/asm_app/assets/index-DpwoT1cm.css create mode 100644 asm_ui_app/public/asm_app/assets/index-rQSfSDmu.js create mode 100644 asm_ui_app/www/update-password.html diff --git a/asm_app/src/components/Header.tsx b/asm_app/src/components/Header.tsx index ac85560..9f79fe2 100644 --- a/asm_app/src/components/Header.tsx +++ b/asm_app/src/components/Header.tsx @@ -1,9 +1,9 @@ -import React from 'react'; -import { useNavigate } from 'react-router-dom'; +import React, { useEffect, useState } from 'react'; +import { useNavigate, useLocation } from 'react-router-dom'; import { useTheme } from '../contexts/ThemeContext'; import { useLanguage } from '../contexts/LanguageContext'; import { useTranslation } from 'react-i18next'; -import { Moon, Sun, Languages, LogOut } from 'lucide-react'; +import { Moon, Sun, Languages, LogOut, UserCircle } from 'lucide-react'; import NotificationBell from './NotificationBell'; interface HeaderProps { @@ -12,15 +12,47 @@ interface HeaderProps { const Header: React.FC = ({ userEmail }) => { const navigate = useNavigate(); + const location = useLocation(); const { theme, toggleTheme } = useTheme(); const { language, changeLanguage } = useLanguage(); const { t } = useTranslation(); + const [userDisplayName, setUserDisplayName] = useState(''); - // const handleLogout = () => { - // localStorage.removeItem('user'); - // localStorage.removeItem('sid'); - // navigate('/login'); - // }; + useEffect(() => { + const fetchUserDisplayName = async () => { + try { + const userResponse = await fetch('/api/method/frappe.auth.get_logged_user', { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + }); + const userData = await userResponse.json(); + const email = userData.message; + + if (email) { + const fullNameResponse = await fetch( + `/api/resource/User/${encodeURIComponent(email)}?fields=["full_name"]`, + { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + credentials: 'include', + } + ); + const fullNameData = await fullNameResponse.json(); + setUserDisplayName(fullNameData.data?.full_name || email); + return; + } + } catch (error) { + console.error('Error fetching user display name:', error); + } + + if (userEmail) { + setUserDisplayName(userEmail); + } + }; + + fetchUserDisplayName(); + }, [userEmail]); const handleLogout = async () => { localStorage.removeItem('user'); @@ -32,7 +64,6 @@ const Header: React.FC = ({ userEmail }) => { .find(row => row.startsWith('X-Frappe-CSRF-Token=')) ?.split('=')[1] || ''; - // Step 1: Kill server-side session in Redis await fetch('/api/method/frappe.auth.logout', { method: 'POST', headers: { @@ -42,11 +73,9 @@ const Header: React.FC = ({ userEmail }) => { credentials: 'include', }); - // Step 2: Clear Frappe web session cookies fully await fetch('/?cmd=web_logout', { credentials: 'include', }); - } catch (err) { console.error('Logout error:', err); } finally { @@ -54,21 +83,29 @@ const Header: React.FC = ({ userEmail }) => { } }; + const isProfileActive = location.pathname === '/user-profile'; + return (
- {/* User Email (optional, can be shown on hover or always) */} - {/* {userEmail && ( -
- {userEmail} -
- )} */} + - {/* Notification Bell */}
- {/* Language Toggle */} - {/* Theme Toggle */} - {/* Logout */} + + +
+

+ Point the camera at an asset QR code. You will stay inside the app. +

+ + {starting && !error && ( +

Starting camera…

+ )} + + {error && ( +
+ {error} +
+ )} + +
+
+ +
+ +
+
+ , + document.body + ); +}; + +export interface ScanQRButtonProps { + className?: string; + label?: string; + title?: string; + onScan?: (decodedText: string, appPath: string | null) => void; +} + +/** + * Reusable Scan QR trigger + scanner modal. Use anywhere you need camera QR → in-app navigation. + */ +export const ScanQRButton: React.FC = ({ + className, + label = 'Scan QR', + title, + onScan, +}) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> + + setIsOpen(false)} + onScan={onScan} + title={title} + /> + + ); +}; + +export default QRScanner; diff --git a/asm_app/src/components/Sidebar.tsx b/asm_app/src/components/Sidebar.tsx index c9c476f..27742fb 100644 --- a/asm_app/src/components/Sidebar.tsx +++ b/asm_app/src/components/Sidebar.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; +import React, { useState } from 'react'; +import { Link, useLocation } from 'react-router-dom'; import { useLanguage } from '../contexts/LanguageContext'; import { useTranslation } from 'react-i18next'; import { @@ -15,8 +15,6 @@ import { ShoppingCart, FileText, HelpCircle, - UserCircle - } from 'lucide-react'; interface SidebarLink { @@ -34,12 +32,9 @@ interface SidebarProps { const Sidebar: React.FC = ({ userEmail }) => { const [isCollapsed, setIsCollapsed] = useState(false); const location = useLocation(); - const navigate = useNavigate(); const { isRTL } = useLanguage(); const { t } = useTranslation(); - const [userFullName, setUserFullName] = useState(''); - // Get base URL for assets (handles both dev and production) // BASE_URL in Vite already includes trailing slash in production, but not in dev const baseUrl = import.meta.env.BASE_URL || '/'; @@ -55,51 +50,6 @@ const Sidebar: React.FC = ({ userEmail }) => { ? `${baseUrl}sidebar-background.jpg${imageVersion}` : `${baseUrl}/sidebar-background.jpg${imageVersion}`; - // ✅ Fetch user full name on mount - useEffect(() => { - const fetchUserFullName = async () => { - try { - // First get the logged-in user - const userResponse = await fetch('/api/method/frappe.auth.get_logged_user', { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include' - }); - const userData = await userResponse.json(); - const userEmail = userData.message; - - if (userEmail) { - // Then fetch the user's full name - const fullNameResponse = await fetch(`/api/resource/User/${encodeURIComponent(userEmail)}?fields=["full_name"]`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include' - }); - const fullNameData = await fullNameResponse.json(); - - if (fullNameData.data?.full_name) { - setUserFullName(fullNameData.data.full_name); - } else { - // Fallback to email if full name not found - setUserFullName(userEmail); - } - } - } catch (error) { - console.error('Error fetching user full name:', error); - // Fallback to email prop if API fails - if (userEmail) { - setUserFullName(userEmail); - } - } - }; - - fetchUserFullName(); - }, [userEmail]); - // Role-based visibility logic // const isMaintenanceManagerKASH = userEmail === 'maintenancemanager-kash@gmail.com'; // const isMaintenanceManagerTH = userEmail === 'maintenancemanager-th@gmail.com'; @@ -269,13 +219,6 @@ const Sidebar: React.FC = ({ userEmail }) => { const isActive = (path: string) => { return location.pathname === path; }; - - // ✅ Handle User Profile click - const handleUserProfileClick = () => { - navigate('/user-profile'); - }; - - return (
= ({ userEmail }) => { ))} - {/* User Info & Version (Bottom) */} -
- {/* {!isCollapsed && userEmail && ( -
-
- {t('sidebar.loggedInAs')} -
-
- {userEmail} -
*/} - - {!isCollapsed && (userFullName || userEmail) && ( -
-
- {t('sidebar.loggedInAs')} -
-
- {userFullName || userEmail} -
- - {/* ✅ User Profile Button */} - -
- )} - - {/* Collapsed state - just show icon button */} - {isCollapsed && ( - - )} - + {/* Version (Bottom) */} +
{!isCollapsed && (
{t('sidebar.version')} diff --git a/asm_app/src/config/api.ts b/asm_app/src/config/api.ts index 9cfd9e3..ce18d6e 100644 --- a/asm_app/src/config/api.ts +++ b/asm_app/src/config/api.ts @@ -75,6 +75,7 @@ const API_CONFIG: ApiConfig = { LOGIN: '/api/method/login', LOGOUT: '/api/method/logout', CSRF_TOKEN: '/api/method/frappe.sessions.get_csrf_token', + RESET_PASSWORD: '/api/method/frappe.core.doctype.user.user.reset_password', // File Upload UPLOAD_FILE: '/api/method/upload_file', diff --git a/asm_app/src/locales/ar/translation.json b/asm_app/src/locales/ar/translation.json index b843bca..5e8c480 100644 --- a/asm_app/src/locales/ar/translation.json +++ b/asm_app/src/locales/ar/translation.json @@ -32,7 +32,7 @@ "sidebar": { "title": "أصول سيرا", "loggedInAs": "تم تسجيل الدخول كـ:", - "version": "أصول سيرا نظام إدارة الأصول الإصدار 1.0" + "version": "أصول سيرا نظام إدارة الأصول الإصدار 2.26" }, "login": { "title": "أصول سيرا", @@ -41,7 +41,21 @@ "emailPlaceholder": "أدخل بريدك الإلكتروني", "passwordPlaceholder": "أدخل كلمة المرور", "loginFailed": "فشل تسجيل الدخول. يرجى التحقق من بيانات الاعتماد الخاصة بك.", - "demoLogin": "تسجيل دخول تجريبي" + "demoLogin": "تسجيل دخول تجريبي", + "forgotPassword": "نسيت كلمة المرور؟", + "forgotPasswordTitle": "إعادة تعيين كلمة المرور", + "forgotPasswordHint": "أدخل بريدك الإلكتروني أو اسم المستخدم. سنرسل لك رابطًا لإعادة تعيين كلمة المرور.", + "forgotPasswordUserRequired": "يرجى إدخال بريدك الإلكتروني أو اسم المستخدم.", + "forgotPasswordUserPlaceholder": "البريد الإلكتروني أو اسم المستخدم", + "forgotPasswordSubmit": "إرسال رابط إعادة التعيين", + "forgotPasswordClose": "إغلاق", + "forgotPasswordSentSuccess": "إذا كان هناك حساب لهذا المستخدم، فقد أُرسلت تعليمات إعادة تعيين كلمة المرور بالبريد الإلكتروني.", + "forgotPasswordNotFound": "لم يتم العثور على حساب بهذا البريد الإلكتروني أو اسم المستخدم.", + "forgotPasswordTimeout": "انتهت مهلة الطلب. يرجى المحاولة مرة أخرى.", + "forgotPasswordCannotReset": "إعادة تعيين كلمة المرور غير متاحة لهذا الحساب.", + "forgotPasswordFailed": "تعذر إرسال رابط إعادة التعيين. يرجى المحاولة لاحقًا.", + "finishingSignOut": "جاري إنهاء تسجيل الخروج…", + "afterPasswordResetSignIn": "تم تحديث كلمة المرور. يرجى تسجيل الدخول بكلمة المرور الجديدة." }, "dashboard": { "title": "لوحة التحكم", diff --git a/asm_app/src/locales/en/translation.json b/asm_app/src/locales/en/translation.json index c02c10c..48b0e9b 100644 --- a/asm_app/src/locales/en/translation.json +++ b/asm_app/src/locales/en/translation.json @@ -34,7 +34,7 @@ "sidebar": { "title": "Seera-ASM", "loggedInAs": "Logged in as:", - "version": "Seera-ASM v1.0", + "version": "Seera-ASM v2.26", "inventory": "Inventory", "ppmPlanner": "PPM Planner", "maintenanceCalendar": "Maintenance Calendar", @@ -51,7 +51,21 @@ "emailPlaceholder": "Enter your email", "passwordPlaceholder": "Enter your password", "loginFailed": "Login failed. Please check your credentials.", - "demoLogin": "Demo Login" + "demoLogin": "Demo Login", + "forgotPassword": "Forgot password?", + "forgotPasswordTitle": "Reset your password", + "forgotPasswordHint": "Enter your email address or username. We will send you a link to reset your password.", + "forgotPasswordUserRequired": "Please enter your email or username.", + "forgotPasswordUserPlaceholder": "Email or username", + "forgotPasswordSubmit": "Send reset link", + "forgotPasswordClose": "Close", + "forgotPasswordSentSuccess": "If an account exists for that user, password reset instructions have been sent by email.", + "forgotPasswordNotFound": "No account was found with that email or username.", + "forgotPasswordTimeout": "The request timed out. Please try again.", + "forgotPasswordCannotReset": "Password reset is not available for this account.", + "forgotPasswordFailed": "Could not send the reset link. Please try again later.", + "finishingSignOut": "Finishing sign-out…", + "afterPasswordResetSignIn": "Your password was updated. Please sign in with your new password." }, "dashboard": { "title": "Dashboard", diff --git a/asm_app/src/pages/AssetDetail.tsx b/asm_app/src/pages/AssetDetail.tsx index 8c64a9a..56e2696 100644 --- a/asm_app/src/pages/AssetDetail.tsx +++ b/asm_app/src/pages/AssetDetail.tsx @@ -604,7 +604,7 @@ const AssetDetail: React.FC = () => { if (error && !isNewAsset && !isDuplicating) { return ( -
+

Error: {error}

- + */} - -
-
-
+
- + ) : ( + t('common.login') + )} + + +

+

+ + + + {forgotOpen && ( +
+
e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="forgot-password-title" + > +
+

+ {t('login.forgotPassword')} +

+ +
+ +

+ {t('login.forgotPasswordHint')} +

+ +
+ { + setForgotEmail(e.target.value); + setForgotError(null); + setForgotMessage(null); + }} + disabled={forgotLoading} + /> + + {forgotError && ( +
+

{forgotError}

+
+ )} + + {forgotMessage && ( +
+

{forgotMessage}

+
+ )} + +
+ + +
+
+
+
+ )}
); }; export default Login; - - - -// import React, { useState } from 'react'; -// import { useNavigate } from 'react-router-dom'; -// import { useAuth } from '../hooks/useApi'; - -// interface LoginFormData { -// email: string; -// password: string; -// } - -// const Login: React.FC = () => { -// const [formData, setFormData] = useState({ -// email: '', -// password: '', -// }); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); -// const navigate = useNavigate(); -// const { login } = useAuth(); - -// const handleChange = (e: React.ChangeEvent) => { -// const { name, value } = e.target; -// setFormData(prev => ({ -// ...prev, -// [name]: value, -// })); -// setError(null); -// }; - -// const handleSubmit = async (e: React.FormEvent) => { -// e.preventDefault(); -// setLoading(true); -// setError(null); - -// try { -// const response = await login(formData); - -// if (response && response.message) { -// // Store user info in localStorage with email field -// const userData = { -// ...response.message, -// email: formData.email // Ensure email is stored -// }; -// localStorage.setItem('user', JSON.stringify(userData)); -// navigate('/dashboard'); -// } else { -// setError('Login failed. Please check your credentials.'); -// } -// } catch (err: any) { -// setError(err.message || 'Login failed. Please try again.'); -// } finally { -// setLoading(false); -// } -// }; - -// const handleDemoLogin = () => { -// // Create dummy user data for demo purposes -// const demoUser = { -// full_name: 'Demo User', -// email: 'demo@seeraarabia.com', -// user_image: '', -// roles: ['System Manager', 'Administrator'] -// }; - -// // Store demo user in localStorage -// localStorage.setItem('user', JSON.stringify(demoUser)); -// navigate('/dashboard'); -// }; - -// return ( -//
-//
-//
-//
-//
-// {/* Seera Arabia Logo */} -// Seera Arabia { -// // Fallback to gradient background with SVG if image not found -// const container = e.currentTarget.parentElement; -// if (container) { -// container.classList.add('bg-gradient-to-br', 'from-indigo-600', 'to-purple-600'); -// } -// e.currentTarget.style.display = 'none'; -// e.currentTarget.nextElementSibling?.classList.remove('hidden'); -// }} -// /> -// -// -// -// -// -//
-//
-//

-// Seera Arabia -//

-//

-// Asset Management System -//

-//

-// Sign in to continue -//

-//
- -//
-//
-//
-// -// -//
-//
-// -// -//
-//
- -// {error && ( -//
-//
{error}
-//
-// )} - -//
-// - -//
-//
-//
-//
-//
-// or -//
-//
- -// -//
-// -//
-//
-// ); -// }; - -// export default Login; diff --git a/asm_app/src/pages/MaintenanceTeamDetail.tsx b/asm_app/src/pages/MaintenanceTeamDetail.tsx index 36f29f8..d876901 100644 --- a/asm_app/src/pages/MaintenanceTeamDetail.tsx +++ b/asm_app/src/pages/MaintenanceTeamDetail.tsx @@ -341,7 +341,7 @@ const MaintenanceTeamDetail: React.FC = () => { if (error && !isNewTeam) { return ( -
+

{t('maintenance.errorLoadingTeam')}

{error}

@@ -352,11 +352,11 @@ const MaintenanceTeamDetail: React.FC = () => { } return ( -
+
{/* Header */} -
+
-

Users

+

Users

+ */} - -
-
- -
- - - - - - - - - - - - - - - ${Ft} - -
Asset IDAsset NameSerial NumberDue DatePeriodicityMaintenance TypeAssigned ToStatus
-
- -
- Summary: ${wt.length} planned maintenance entries for the next 5 years -
-
- - - `),Mt.document.close()}catch(oe){console.error("Error fetching PPM Plan:",oe),alert("Failed to load PPM Plan. Please try again.")}}),Ve=()=>le(null,null,function*(){if(!e||i){alert("Please save the asset first to view Asset History");return}try{const oe=yield rt.apiCall(`/api/resource/Asset/${e}`),we=(oe==null?void 0:oe.data)||{},Ie=yield rt.apiCall(`/api/resource/Work_Order?filters=[["asset","=","${e}"]]&fields=["name","work_order_type","repair_status","creation","total_repair_cost"]`),ot=(Ie==null?void 0:Ie.data)||[],bt=yield rt.apiCall(`/api/resource/Asset Maintenance?filters=[["asset_name","=","${e}"]]&fields=["name","maintenance_team","custom_type_of_maintenance"]`),qe=(bt==null?void 0:bt.data)||[];let Ue=[],ht=[];for(const et of qe){const pe=yield rt.apiCall(`/api/resource/Asset Maintenance/${et.name}`),ze=(pe==null?void 0:pe.data)||{};ze.asset_maintenance_tasks&&(ht=[...ht,...ze.asset_maintenance_tasks.map(Ut=>Se(ne({},Ut),{maintenance_id:et.name}))]);const st=yield rt.apiCall(`/api/resource/Asset Maintenance Log?filters=[["asset_maintenance","=","${et.name}"]]&fields=["name","maintenance_status","assign_to_name","maintenance_type","due_date","completion_date","periodicity","actions_performed"]&order_by=completion_date desc`);Ue=[...Ue,...(st==null?void 0:st.data)||[]]}const wt=Ue.filter(et=>et.maintenance_status==="Completed"),Mt=Ue.filter(et=>et.maintenance_status!=="Completed"),Ft={};Ue.forEach(et=>{Ft[et.maintenance_status]=(Ft[et.maintenance_status]||0)+1});const je=we.custom_spare_parts||[],it={};je.forEach(et=>{const pe=et.work_order||"Unassigned";it[pe]||(it[pe]=[]),it[pe].push(et)});const vt=window.open("","_blank");if(!vt)return;const Bt=ot.length>0?ot.map((et,pe)=>{const ze=et.repair_status==="Completed"?"bg-green-100 text-green-800":et.repair_status==="Work In Progress"?"bg-yellow-100 text-yellow-800":"bg-gray-100 text-gray-800";return` - - ${et.name} - ${et.work_order_type||"-"} - ${et.repair_status||"-"} - ${parseFloat(et.total_repair_cost||0).toFixed(2)} ر.س - ${et.creation?new Date(et.creation).toLocaleDateString():"-"} - - `}).join(""):'No Work Orders Found';let De="";Object.keys(it).length>0?Object.keys(it).forEach(et=>{De+=` -
-
- Work Order: - ${et} -
- - - - - - - - - - - ${it[et].map((pe,ze)=>` - - - - - - - `).join("")} - -
Item NameQuantityCostAmount
${pe.item_code||"-"}${pe.qty||0}${pe.rate||0} ر.س${pe.amount||0} ر.س
-
- `}):De='

No Spare Parts Used

';const ct=qe.length>0?qe.map(et=>` -
-
-
-

Maintenance ID

-

${et.name}

-
-
-

Type

-

${et.custom_type_of_maintenance||"-"}

-
-
-
-

Maintenance Team

-

${et.maintenance_team||"-"}

-
-
- `).join(""):'

No Maintenance Records Found

',ut=ht.length>0?ht.map((et,pe)=>` - - ${et.assign_to_name||"-"} - ${et.periodicity||"-"} - ${et.next_due_date||"-"} - ${et.maintenance_id||"-"} - - `).join(""):'No Maintenance Tasks Found',Nt=Object.keys(Ft).length>0?Object.entries(Ft).map(([et,pe])=>` -
- ${et} - ${pe} -
- `).join(""):'

No Data

',Rt=wt.length>0?wt.map((et,pe)=>` - - ${et.name} - ${et.assign_to_name||"-"} - ${et.maintenance_type||"-"} - ${et.due_date||"-"} - ${et.completion_date||"-"} - ${et.periodicity||"-"} - ${et.actions_performed||"-"} - - `).join(""):'No Completed Maintenance Logs',ia=Mt.length>0?Mt.map((et,pe)=>{const ze=et.maintenance_status==="Planned"?"bg-blue-100 text-blue-800":"bg-yellow-100 text-yellow-800";return` - - ${et.name} - ${et.assign_to_name||"-"} - ${et.maintenance_type||"-"} - ${et.due_date||"-"} - ${et.periodicity||"-"} - ${et.maintenance_status||"-"} - ${et.actions_performed||"-"} - - `}).join(""):'No Pending/Planned Maintenance Logs';vt.document.write(` - - - - Asset History - ${e} - - + +
diff --git a/asm_ui_app/www/asm_app.html b/asm_ui_app/www/asm_app.html index f354cbc..89ff065 100644 --- a/asm_ui_app/www/asm_app.html +++ b/asm_ui_app/www/asm_app.html @@ -7,8 +7,8 @@ Seera Arabia - Asset Management System - - + +
diff --git a/asm_ui_app/www/update-password.html b/asm_ui_app/www/update-password.html new file mode 100644 index 0000000..a519948 --- /dev/null +++ b/asm_ui_app/www/update-password.html @@ -0,0 +1,333 @@ +{% extends "templates/web.html" %} + +{% block title %} {{_("Reset Password")}} {% endblock %} +{% block head_include %} +{% endblock %} +{% block page_content %} +
+
+

{{ _("Reset Password") if frappe.db.get_default('company') else _("Set Password")}}

+
+
+ +
+ +
+
+ + +
+
+ + +
+ + + + + {%- if not disable_signup -%} + + {%- endif -%} +
+
+ + + + +{% endblock %} + +{% block style %} + +{% endblock %}