From b9fb0099233fc8fa8824d9440cac04cff5863d7a Mon Sep 17 00:00:00 2001 From: fccview Date: Wed, 5 Nov 2025 21:41:15 +0000 Subject: [PATCH] huge translations work --- .../Cronjobs/CronJobList.tsx | 529 ++++-------------- .../Cronjobs/Parts/CronJobEmptyState.tsx | 40 ++ .../Cronjobs/Parts/CronJobItem.tsx | 198 +++++++ .../FeatureComponents/Layout/Sidebar.tsx | 6 +- .../Layout/TabbedInterface.tsx | 6 +- .../Modals/CreateTaskModal.tsx | 35 +- .../Modals/CronJobListsModals.tsx | 121 ++++ .../Modals/SelectScriptModal.tsx | 22 +- .../Scripts/CronExpressionHelper.tsx | 4 +- .../Scripts/ScriptsManager.tsx | 16 +- .../FeatureComponents/System/SystemInfo.tsx | 29 +- .../FeatureComponents/System/SystemStatus.tsx | 6 +- .../FeatureComponents/User/UserFilter.tsx | 6 +- .../GlobalComponents/Badges/StatusBadge.tsx | 12 +- app/_consts/global.ts | 4 + app/_hooks/useCronJobState.ts | 202 +++++++ app/_translations/en.json | 94 ++++ app/_translations/it.json | 94 ++++ app/_utils/parser-utils.ts | 5 +- app/api/system-stats/route.ts | 60 +- app/i18n.ts | 24 + app/layout.tsx | 36 +- app/page.tsx | 5 +- next.config.js | 13 +- package.json | 1 + yarn.lock | 93 ++- 26 files changed, 1124 insertions(+), 537 deletions(-) create mode 100644 app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx create mode 100644 app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx create mode 100644 app/_components/FeatureComponents/Modals/CronJobListsModals.tsx create mode 100644 app/_consts/global.ts create mode 100644 app/_hooks/useCronJobState.ts create mode 100644 app/_translations/en.json create mode 100644 app/_translations/it.json create mode 100644 app/i18n.ts diff --git a/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx b/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx index 14c77a2..76b7469 100644 --- a/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx +++ b/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx @@ -2,42 +2,16 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/GlobalComponents/Cards/Card"; import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; -import { - Trash2, - Clock, - Edit, - Plus, - Files, - User, - Play, - Pause, - Code, -} from "lucide-react"; +import { Clock, Plus } from "lucide-react"; import { CronJob } from "@/app/_utils/cronjob-utils"; -import { useState, useMemo, useEffect } from "react"; -import { CreateTaskModal } from "@/app/_components/FeatureComponents/Modals/CreateTaskModal"; -import { EditTaskModal } from "@/app/_components/FeatureComponents/Modals/EditTaskModal"; -import { DeleteTaskModal } from "@/app/_components/FeatureComponents/Modals/DeleteTaskModal"; -import { CloneTaskModal } from "@/app/_components/FeatureComponents/Modals/CloneTaskModal"; -import { UserFilter } from "@/app/_components/FeatureComponents/User/UserFilter"; -import { ErrorBadge } from "@/app/_components/GlobalComponents/Badges/ErrorBadge"; -import { ErrorDetailsModal } from "@/app/_components/FeatureComponents/Modals/ErrorDetailsModal"; import { Script } from "@/app/_utils/scripts-utils"; +import { UserFilter } from "@/app/_components/FeatureComponents/User/UserFilter"; -import { - getJobErrorsByJobId, - JobError, -} from "@/app/_utils/error-utils"; -import { - handleErrorClick, - handleDelete, - handleClone, - handlePause, - handleResume, - handleRun, - handleEditSubmit, - handleNewCronSubmit, -} from "@/app/_components/FeatureComponents/Cronjobs/helpers"; +import { useCronJobState } from "@/app/_hooks/useCronJobState"; +import { CronJobItem } from "@/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem"; +import { CronJobEmptyState } from "@/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState"; +import { CronJobListModals } from "@/app/_components/FeatureComponents/Modals/CronJobListsModals"; +import { useTranslations } from "next-intl"; interface CronJobListProps { cronJobs: CronJob[]; @@ -45,206 +19,46 @@ interface CronJobListProps { } export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => { - const [deletingId, setDeletingId] = useState(null); - const [editingJob, setEditingJob] = useState(null); - const [isEditModalOpen, setIsEditModalOpen] = useState(false); - const [isNewCronModalOpen, setIsNewCronModalOpen] = useState(false); - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [isCloneModalOpen, setIsCloneModalOpen] = useState(false); - const [jobToDelete, setJobToDelete] = useState(null); - const [jobToClone, setJobToClone] = useState(null); - const [isCloning, setIsCloning] = useState(false); - const [runningJobId, setRunningJobId] = useState(null); - const [selectedUser, setSelectedUser] = useState(null); - const [jobErrors, setJobErrors] = useState>({}); - const [errorModalOpen, setErrorModalOpen] = useState(false); - const [selectedError, setSelectedError] = useState(null); - - useEffect(() => { - const savedUser = localStorage.getItem("selectedCronUser"); - if (savedUser) { - setSelectedUser(savedUser); - } - }, []); - - useEffect(() => { - if (selectedUser) { - localStorage.setItem("selectedCronUser", selectedUser); - } else { - localStorage.removeItem("selectedCronUser"); - } - }, [selectedUser]); - - const [editForm, setEditForm] = useState({ - schedule: "", - command: "", - comment: "", - }); - const [newCronForm, setNewCronForm] = useState({ - schedule: "", - command: "", - comment: "", - selectedScriptId: null as string | null, - user: "", - }); - - const filteredJobs = useMemo(() => { - if (!selectedUser) return cronJobs; - return cronJobs.filter((job) => job.user === selectedUser); - }, [cronJobs, selectedUser]); - - useEffect(() => { - const errors: Record = {}; - filteredJobs.forEach((job) => { - errors[job.id] = getJobErrorsByJobId(job.id); - }); - setJobErrors(errors); - }, [filteredJobs]); - - const handleErrorClickLocal = (error: JobError) => { - handleErrorClick(error, setSelectedError, setErrorModalOpen); - }; - - const refreshJobErrorsLocal = () => { - const errors: Record = {}; - filteredJobs.forEach((job) => { - errors[job.id] = getJobErrorsByJobId(job.id); - }); - setJobErrors(errors); - }; - - const handleDeleteLocal = async (id: string) => { - await handleDelete(id, { - setDeletingId, - setIsDeleteModalOpen, - setJobToDelete, - setIsCloneModalOpen, - setJobToClone, - setIsCloning, - setIsEditModalOpen, - setEditingJob, - setIsNewCronModalOpen, - setNewCronForm, - setRunningJobId, - refreshJobErrors: refreshJobErrorsLocal, - jobToClone, - editingJob, - editForm, - newCronForm, - }); - }; - - const handleCloneLocal = async (newComment: string) => { - await handleClone(newComment, { - setDeletingId, - setIsDeleteModalOpen, - setJobToDelete, - setIsCloneModalOpen, - setJobToClone, - setIsCloning, - setIsEditModalOpen, - setEditingJob, - setIsNewCronModalOpen, - setNewCronForm, - setRunningJobId, - refreshJobErrors: refreshJobErrorsLocal, - jobToClone, - editingJob, - editForm, - newCronForm, - }); - }; - - const handlePauseLocal = async (id: string) => { - await handlePause(id); - }; - - const handleResumeLocal = async (id: string) => { - await handleResume(id); - }; - - const handleRunLocal = async (id: string) => { - await handleRun(id, { - setDeletingId, - setIsDeleteModalOpen, - setJobToDelete, - setIsCloneModalOpen, - setJobToClone, - setIsCloning, - setIsEditModalOpen, - setEditingJob, - setIsNewCronModalOpen, - setNewCronForm, - setRunningJobId, - refreshJobErrors: refreshJobErrorsLocal, - jobToClone, - editingJob, - editForm, - newCronForm, - }); - }; - - const confirmDelete = (job: CronJob) => { - setJobToDelete(job); - setIsDeleteModalOpen(true); - }; - - const confirmClone = (job: CronJob) => { - setJobToClone(job); - setIsCloneModalOpen(true); - }; - - const handleEdit = (job: CronJob) => { - setEditingJob(job); - setEditForm({ - schedule: job.schedule, - command: job.command, - comment: job.comment || "", - }); - setIsEditModalOpen(true); - }; - - const handleEditSubmitLocal = async (e: React.FormEvent) => { - await handleEditSubmit(e, { - setDeletingId, - setIsDeleteModalOpen, - setJobToDelete, - setIsCloneModalOpen, - setJobToClone, - setIsCloning, - setIsEditModalOpen, - setEditingJob, - setIsNewCronModalOpen, - setNewCronForm, - setRunningJobId, - refreshJobErrors: refreshJobErrorsLocal, - jobToClone, - editingJob, - editForm, - newCronForm, - }); - }; - - const handleNewCronSubmitLocal = async (e: React.FormEvent) => { - await handleNewCronSubmit(e, { - setDeletingId, - setIsDeleteModalOpen, - setJobToDelete, - setIsCloneModalOpen, - setJobToClone, - setIsCloning, - setIsEditModalOpen, - setEditingJob, - setIsNewCronModalOpen, - setNewCronForm, - setRunningJobId, - refreshJobErrors: refreshJobErrorsLocal, - jobToClone, - editingJob, - editForm, - newCronForm, - }); - }; + const t = useTranslations(); + const { + deletingId, + runningJobId, + selectedUser, + setSelectedUser, + jobErrors, + errorModalOpen, + setErrorModalOpen, + selectedError, + setSelectedError, + filteredJobs, + isNewCronModalOpen, + setIsNewCronModalOpen, + isEditModalOpen, + setIsEditModalOpen, + isDeleteModalOpen, + setIsDeleteModalOpen, + isCloneModalOpen, + setIsCloneModalOpen, + jobToDelete, + jobToClone, + isCloning, + editForm, + setEditForm, + newCronForm, + setNewCronForm, + handleErrorClickLocal, + refreshJobErrorsLocal, + handleDeleteLocal, + handleCloneLocal, + handlePauseLocal, + handleResumeLocal, + handleRunLocal, + confirmDelete, + confirmClone, + handleEdit, + handleEditSubmitLocal, + handleNewCronSubmitLocal, + } = useCronJobState({ cronJobs, scripts }); return ( <> @@ -257,12 +71,12 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
- Scheduled Tasks + {t("cronjobs.scheduledTasks")}

- {filteredJobs.length} of {cronJobs.length} scheduled job - {filteredJobs.length !== 1 ? "s" : ""} - {selectedUser && ` for ${selectedUser}`} + {t("cronjobs.nOfNJObs", { filtered: filteredJobs.length, total: cronJobs.length })} + {" "} + {selectedUser && t("cronjobs.forUser", { user: selectedUser })}

@@ -271,7 +85,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => { className="btn-primary glow-primary" > - New Task + {t("cronjobs.newTask")} @@ -285,219 +99,74 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => { {filteredJobs.length === 0 ? ( -
-
- -
-

- {selectedUser - ? `No tasks for user ${selectedUser}` - : "No scheduled tasks yet"} -

-

- {selectedUser - ? `No scheduled tasks found for user ${selectedUser}. Try selecting a different user or create a new task.` - : "Create your first scheduled task to automate your system operations and boost productivity."} -

- -
+ setIsNewCronModalOpen(true)} + /> ) : (
{filteredJobs.map((job) => ( -
-
-
-
- - {job.schedule} - -
-
-                            {job.command}
-                          
-
-
- -
-
- - {job.user} -
- {job.paused && ( - - Paused - - )} - -
- - {job.comment && ( -

- {job.comment} -

- )} -
- -
- - - - {job.paused ? ( - - ) : ( - - )} - -
-
-
+ job={job} + errors={jobErrors[job.id] || []} + runningJobId={runningJobId} + deletingId={deletingId} + onRun={handleRunLocal} + onEdit={handleEdit} + onClone={confirmClone} + onResume={handleResumeLocal} + onPause={handlePauseLocal} + onDelete={confirmDelete} + onErrorClick={handleErrorClickLocal} + onErrorDismiss={refreshJobErrorsLocal} + /> ))}
)} - setIsNewCronModalOpen(false)} - onSubmit={handleNewCronSubmitLocal} + + + isNewCronModalOpen={isNewCronModalOpen} + onNewCronModalClose={() => setIsNewCronModalOpen(false)} + onNewCronSubmit={handleNewCronSubmitLocal} + newCronForm={newCronForm} + onNewCronFormChange={(updates) => setNewCronForm((prev) => ({ ...prev, ...updates })) } - /> - setIsEditModalOpen(false)} - onSubmit={handleEditSubmitLocal} - form={editForm} - onFormChange={(updates) => + isEditModalOpen={isEditModalOpen} + onEditModalClose={() => setIsEditModalOpen(false)} + onEditSubmit={handleEditSubmitLocal} + editForm={editForm} + onEditFormChange={(updates) => setEditForm((prev) => ({ ...prev, ...updates })) } - /> - setIsDeleteModalOpen(false)} - onConfirm={() => + isDeleteModalOpen={isDeleteModalOpen} + onDeleteModalClose={() => setIsDeleteModalOpen(false)} + onDeleteConfirm={() => jobToDelete ? handleDeleteLocal(jobToDelete.id) : undefined } - job={jobToDelete} - /> + jobToDelete={jobToDelete} - setIsCloneModalOpen(false)} - onConfirm={handleCloneLocal} + isCloneModalOpen={isCloneModalOpen} + onCloneModalClose={() => setIsCloneModalOpen(false)} + onCloneConfirm={handleCloneLocal} + jobToClone={jobToClone} isCloning={isCloning} - /> - {errorModalOpen && selectedError && ( - { - setErrorModalOpen(false); - setSelectedError(null); - }} - error={{ - title: selectedError.title, - message: selectedError.message, - details: selectedError.details, - command: selectedError.command, - output: selectedError.output, - stderr: selectedError.stderr, - timestamp: selectedError.timestamp, - jobId: selectedError.jobId, - }} - /> - )} + isErrorModalOpen={errorModalOpen} + onErrorModalClose={() => { + setErrorModalOpen(false); + setSelectedError(null); + }} + selectedError={selectedError} + /> ); -}; +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx new file mode 100644 index 0000000..5c9b9fc --- /dev/null +++ b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; +import { Clock, Plus } from "lucide-react"; + +interface CronJobEmptyStateProps { + selectedUser: string | null; + onNewTaskClick: () => void; +} + +export const CronJobEmptyState = ({ + selectedUser, + onNewTaskClick, +}: CronJobEmptyStateProps) => { + return ( +
+
+ +
+

+ {selectedUser + ? `No tasks for user ${selectedUser}` + : "No scheduled tasks yet"} +

+

+ {selectedUser + ? `No scheduled tasks found for user ${selectedUser}. Try selecting a different user or create a new task.` + : "Create your first scheduled task to automate your system operations and boost productivity."} +

+ +
+ ); +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx new file mode 100644 index 0000000..430be9a --- /dev/null +++ b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx @@ -0,0 +1,198 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; +import { + Trash2, + Edit, + Files, + User, + Play, + Pause, + Code, + Info, +} from "lucide-react"; +import { CronJob } from "@/app/_utils/cronjob-utils"; +import { JobError } from "@/app/_utils/error-utils"; +import { ErrorBadge } from "@/app/_components/GlobalComponents/Badges/ErrorBadge"; +import { parseCronExpression, type CronExplanation } from "@/app/_utils/parser-utils"; +import { useLocale } from "next-intl"; +import { useTranslations } from "next-intl"; + +interface CronJobItemProps { + job: CronJob; + errors: JobError[]; + runningJobId: string | null; + deletingId: string | null; + onRun: (id: string) => void; + onEdit: (job: CronJob) => void; + onClone: (job: CronJob) => void; + onResume: (id: string) => void; + onPause: (id: string) => void; + onDelete: (job: CronJob) => void; + onErrorClick: (error: JobError) => void; + onErrorDismiss: () => void; +} + +export const CronJobItem = ({ + job, + errors, + runningJobId, + deletingId, + onRun, + onEdit, + onClone, + onResume, + onPause, + onDelete, + onErrorClick, + onErrorDismiss, +}: CronJobItemProps) => { + const [cronExplanation, setCronExplanation] = useState(null); + const locale = useLocale(); + const t = useTranslations(); + + useEffect(() => { + if (job.schedule) { + const explanation = parseCronExpression(job.schedule, locale); + setCronExplanation(explanation); + } else { + setCronExplanation(null); + } + }, [job.schedule]); + return ( +
+
+
+
+ + {job.schedule} + +
+
+                                {job.command}
+                            
+
+
+ + {cronExplanation?.isValid && ( +
+ +

+ {cronExplanation.humanReadable} +

+
+ )} + +
+
+ + {job.user} +
+ {job.paused && ( + + {t("cronjobs.paused")} + + )} + +
+ + {job.comment && ( +

+ {job.comment} +

+ )} +
+ +
+ + + + {job.paused ? ( + + ) : ( + + )} + +
+
+
+ ); +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Layout/Sidebar.tsx b/app/_components/FeatureComponents/Layout/Sidebar.tsx index 7301b7f..0102260 100644 --- a/app/_components/FeatureComponents/Layout/Sidebar.tsx +++ b/app/_components/FeatureComponents/Layout/Sidebar.tsx @@ -11,10 +11,10 @@ import { HardDrive, Wifi, } from "lucide-react"; +import { useTranslations } from "next-intl"; export interface SidebarProps extends HTMLAttributes { children: React.ReactNode; - title?: string; defaultCollapsed?: boolean; quickStats?: { cpu: number; @@ -28,13 +28,13 @@ export const Sidebar = forwardRef( { className, children, - title = "System Overview", defaultCollapsed = false, quickStats, ...props }, ref ) => { + const t = useTranslations(); const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); const [isMobileOpen, setIsMobileOpen] = useState(false); @@ -113,7 +113,7 @@ export const Sidebar = forwardRef( {(!isCollapsed || !isCollapsed) && (

- {title} + {t("sidebar.systemOverview")}

)} diff --git a/app/_components/FeatureComponents/Layout/TabbedInterface.tsx b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx index db842b6..f5c6d02 100644 --- a/app/_components/FeatureComponents/Layout/TabbedInterface.tsx +++ b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx @@ -6,6 +6,7 @@ import { ScriptsManager } from "@/app/_components/FeatureComponents/Scripts/Scri import { CronJob } from "@/app/_utils/cronjob-utils"; import { Script } from "@/app/_utils/scripts-utils"; import { Clock, FileText } from "lucide-react"; +import { useTranslations } from "next-intl"; interface TabbedInterfaceProps { cronJobs: CronJob[]; @@ -19,6 +20,7 @@ export const TabbedInterface = ({ const [activeTab, setActiveTab] = useState<"cronjobs" | "scripts">( "cronjobs" ); + const t = useTranslations(); return (
@@ -32,7 +34,7 @@ export const TabbedInterface = ({ }`} > - Cron Jobs + {t("cronjobs.cronJobs")} {cronJobs.length} @@ -45,7 +47,7 @@ export const TabbedInterface = ({ }`} > - Scripts + {t("scripts.scripts")} {scripts.length} diff --git a/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx b/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx index c55fa57..e30c527 100644 --- a/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx +++ b/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx @@ -10,6 +10,7 @@ import { UserSwitcher } from "@/app/_components/FeatureComponents/User/UserSwitc import { Plus, Terminal, FileText, X } from "lucide-react"; import { getScriptContent } from "@/app/_server/actions/scripts"; import { getHostScriptPath } from "@/app/_server/actions/scripts"; +import { useTranslations } from "next-intl"; interface Script { id: string; @@ -46,6 +47,7 @@ export const CreateTaskModal = ({ useState(""); const [isSelectScriptModalOpen, setIsSelectScriptModalOpen] = useState(false); const selectedScript = scripts.find((s) => s.id === form.selectedScriptId); + const t = useTranslations(); useEffect(() => { const loadScriptContent = async () => { @@ -86,13 +88,13 @@ export const CreateTaskModal = ({
@@ -145,9 +147,9 @@ export const CreateTaskModal = ({
-
Saved Script
+
{t("scripts.savedScript")}
- Select from library + {t("scripts.selectFromLibrary")}
@@ -182,7 +184,7 @@ export const CreateTaskModal = ({ onClick={() => setIsSelectScriptModalOpen(true)} className="h-8 px-2 text-xs" > - Change + {t("common.change")}