Generated by Spark: Add translations to Purchase Orders and Onboarding views

This commit is contained in:
2026-01-27 14:53:20 +00:00
committed by GitHub
parent a518cd7a6b
commit 3e587ec81c
5 changed files with 773 additions and 80 deletions

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'
import { useKV } from '@github/spark/hooks'
import { useTranslation } from '@/hooks/use-translation'
import { UserPlus, CheckCircle, Clock, FileText, Upload, Envelope, ArrowRight, Warning } from '@phosphor-icons/react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
@@ -38,6 +39,7 @@ interface OnboardingStepStatus {
}
export function OnboardingWorkflowManager() {
const { t } = useTranslation()
const [workflows = [], setWorkflows] = useKV<OnboardingWorkflow[]>('onboarding-workflows', [])
const [isCreateOpen, setIsCreateOpen] = useState(false)
const [formData, setFormData] = useState({
@@ -47,17 +49,17 @@ export function OnboardingWorkflowManager() {
})
const defaultSteps: OnboardingStepStatus[] = [
{ step: 'personal-info', label: 'Personal Information', status: 'pending' },
{ step: 'right-to-work', label: 'Right to Work', status: 'pending' },
{ step: 'tax-forms', label: 'Tax Forms', status: 'pending' },
{ step: 'bank-details', label: 'Bank Details', status: 'pending' },
{ step: 'compliance-docs', label: 'Compliance Documents', status: 'pending' },
{ step: 'contract-signing', label: 'Contract Signing', status: 'pending' }
{ step: 'personal-info', label: t('onboarding.steps.personalInfo'), status: 'pending' },
{ step: 'right-to-work', label: t('onboarding.steps.rightToWork'), status: 'pending' },
{ step: 'tax-forms', label: t('onboarding.steps.taxForms'), status: 'pending' },
{ step: 'bank-details', label: t('onboarding.steps.bankDetails'), status: 'pending' },
{ step: 'compliance-docs', label: t('onboarding.steps.complianceDocs'), status: 'pending' },
{ step: 'contract-signing', label: t('onboarding.steps.contractSigning'), status: 'pending' }
]
const handleCreate = () => {
if (!formData.workerName || !formData.email || !formData.startDate) {
toast.error('Please fill in all required fields')
toast.error(t('onboarding.createDialog.fillAllFields'))
return
}
@@ -74,7 +76,7 @@ export function OnboardingWorkflowManager() {
}
setWorkflows(current => [...(current || []), newWorkflow])
toast.success(`Onboarding workflow created for ${formData.workerName}`)
toast.success(t('onboarding.messages.createSuccess', { workerName: formData.workerName }))
setFormData({ workerName: '', email: '', startDate: '' })
setIsCreateOpen(false)
@@ -107,11 +109,11 @@ export function OnboardingWorkflowManager() {
}
})
})
toast.success('Step completed')
toast.success(t('onboarding.messages.stepCompleted'))
}
const handleSendReminder = (workflow: OnboardingWorkflow) => {
toast.success(`Reminder email sent to ${workflow.email}`)
toast.success(t('onboarding.messages.reminderSent', { email: workflow.email }))
}
const inProgressWorkflows = workflows.filter(w => w.status === 'in-progress' || w.status === 'not-started')
@@ -122,45 +124,45 @@ export function OnboardingWorkflowManager() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-semibold tracking-tight">Digital Onboarding</h2>
<p className="text-muted-foreground mt-1">Manage worker onboarding workflows</p>
<h2 className="text-3xl font-semibold tracking-tight">{t('onboarding.title')}</h2>
<p className="text-muted-foreground mt-1">{t('onboarding.subtitle')}</p>
</div>
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogTrigger asChild>
<Button>
<UserPlus size={18} className="mr-2" />
Start Onboarding
{t('onboarding.startOnboarding')}
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Start New Onboarding</DialogTitle>
<DialogTitle>{t('onboarding.createDialog.title')}</DialogTitle>
<DialogDescription>
Create a digital onboarding workflow for a new worker
{t('onboarding.createDialog.description')}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="ob-name">Worker Name *</Label>
<Label htmlFor="ob-name">{t('onboarding.createDialog.workerNameLabel')}</Label>
<Input
id="ob-name"
value={formData.workerName}
onChange={(e) => setFormData({ ...formData, workerName: e.target.value })}
placeholder="John Smith"
placeholder={t('onboarding.createDialog.workerNamePlaceholder')}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ob-email">Email Address *</Label>
<Label htmlFor="ob-email">{t('onboarding.createDialog.emailLabel')}</Label>
<Input
id="ob-email"
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
placeholder="john.smith@example.com"
placeholder={t('onboarding.createDialog.emailPlaceholder')}
/>
</div>
<div className="space-y-2">
<Label htmlFor="ob-start">Expected Start Date *</Label>
<Label htmlFor="ob-start">{t('onboarding.createDialog.startDateLabel')}</Label>
<Input
id="ob-start"
type="date"
@@ -170,8 +172,8 @@ export function OnboardingWorkflowManager() {
</div>
</div>
<div className="flex justify-end gap-2">
<Button variant="outline" onClick={() => setIsCreateOpen(false)}>Cancel</Button>
<Button onClick={handleCreate}>Start Onboarding</Button>
<Button variant="outline" onClick={() => setIsCreateOpen(false)}>{t('onboarding.createDialog.cancel')}</Button>
<Button onClick={handleCreate}>{t('onboarding.createDialog.start')}</Button>
</div>
</DialogContent>
</Dialog>
@@ -180,7 +182,7 @@ export function OnboardingWorkflowManager() {
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">In Progress</CardTitle>
<CardTitle className="text-sm text-muted-foreground">{t('onboarding.metrics.inProgress')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold">{inProgressWorkflows.length}</div>
@@ -189,7 +191,7 @@ export function OnboardingWorkflowManager() {
<Card>
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Completed</CardTitle>
<CardTitle className="text-sm text-muted-foreground">{t('onboarding.metrics.completed')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold text-success">{completedWorkflows.length}</div>
@@ -198,7 +200,7 @@ export function OnboardingWorkflowManager() {
<Card className="border-l-4 border-warning/20">
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Blocked</CardTitle>
<CardTitle className="text-sm text-muted-foreground">{t('onboarding.metrics.blocked')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold">{blockedWorkflows.length}</div>
@@ -207,19 +209,19 @@ export function OnboardingWorkflowManager() {
<Card>
<CardHeader>
<CardTitle className="text-sm text-muted-foreground">Avg. Time</CardTitle>
<CardTitle className="text-sm text-muted-foreground">{t('onboarding.metrics.avgTime')}</CardTitle>
</CardHeader>
<CardContent>
<div className="text-3xl font-semibold">3.2 days</div>
<div className="text-3xl font-semibold">{t('onboarding.metrics.avgTimeDays', { days: '3.2' })}</div>
</CardContent>
</Card>
</div>
<Tabs defaultValue="in-progress" className="space-y-4">
<TabsList>
<TabsTrigger value="in-progress">In Progress ({inProgressWorkflows.length})</TabsTrigger>
<TabsTrigger value="completed">Completed ({completedWorkflows.length})</TabsTrigger>
<TabsTrigger value="blocked">Blocked ({blockedWorkflows.length})</TabsTrigger>
<TabsTrigger value="in-progress">{t('onboarding.tabs.inProgress', { count: inProgressWorkflows.length })}</TabsTrigger>
<TabsTrigger value="completed">{t('onboarding.tabs.completed', { count: completedWorkflows.length })}</TabsTrigger>
<TabsTrigger value="blocked">{t('onboarding.tabs.blocked', { count: blockedWorkflows.length })}</TabsTrigger>
</TabsList>
<TabsContent value="in-progress" className="space-y-3">
@@ -229,27 +231,42 @@ export function OnboardingWorkflowManager() {
workflow={workflow}
onCompleteStep={handleCompleteStep}
onSendReminder={handleSendReminder}
t={t}
/>
))}
{inProgressWorkflows.length === 0 && (
<Card className="p-12 text-center">
<UserPlus size={48} className="mx-auto text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">No active onboardings</h3>
<p className="text-muted-foreground">Start a new onboarding workflow to begin</p>
<h3 className="text-lg font-semibold mb-2">{t('onboarding.emptyStates.noActive')}</h3>
<p className="text-muted-foreground">{t('onboarding.emptyStates.noActiveDescription')}</p>
</Card>
)}
</TabsContent>
<TabsContent value="completed" className="space-y-3">
{completedWorkflows.map(workflow => (
<OnboardingCard key={workflow.id} workflow={workflow} />
<OnboardingCard key={workflow.id} workflow={workflow} t={t} />
))}
{completedWorkflows.length === 0 && (
<Card className="p-12 text-center">
<CheckCircle size={48} className="mx-auto text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">{t('onboarding.emptyStates.noCompleted')}</h3>
<p className="text-muted-foreground">{t('onboarding.emptyStates.noCompletedDescription')}</p>
</Card>
)}
</TabsContent>
<TabsContent value="blocked" className="space-y-3">
{blockedWorkflows.map(workflow => (
<OnboardingCard key={workflow.id} workflow={workflow} />
<OnboardingCard key={workflow.id} workflow={workflow} t={t} />
))}
{blockedWorkflows.length === 0 && (
<Card className="p-12 text-center">
<Warning size={48} className="mx-auto text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">{t('onboarding.emptyStates.noBlocked')}</h3>
<p className="text-muted-foreground">{t('onboarding.emptyStates.noBlockedDescription')}</p>
</Card>
)}
</TabsContent>
</Tabs>
</div>
@@ -260,9 +277,10 @@ interface OnboardingCardProps {
workflow: OnboardingWorkflow
onCompleteStep?: (workflowId: string, step: OnboardingStep) => void
onSendReminder?: (workflow: OnboardingWorkflow) => void
t: (key: string, params?: Record<string, string | number>) => string
}
function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: OnboardingCardProps) {
function OnboardingCard({ workflow, onCompleteStep, onSendReminder, t }: OnboardingCardProps) {
const [showDetails, setShowDetails] = useState(false)
const statusConfig = {
@@ -290,7 +308,7 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
<div className="flex items-center gap-3 mb-1">
<h3 className="font-semibold text-lg">{workflow.workerName}</h3>
<Badge variant={workflow.status === 'completed' ? 'success' : workflow.status === 'blocked' ? 'destructive' : 'warning'}>
{workflow.status}
{t(`onboarding.status.${workflow.status.replace('-', '') as 'notStarted' | 'inProgress' | 'completed' | 'blocked'}`)}
</Badge>
</div>
<p className="text-sm text-muted-foreground">{workflow.email}</p>
@@ -298,7 +316,7 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Progress</span>
<span className="text-muted-foreground">{t('onboarding.card.progress')}</span>
<span className="font-medium">{workflow.progress}%</span>
</div>
<Progress value={workflow.progress} className="h-2" />
@@ -306,17 +324,17 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 text-sm">
<div>
<p className="text-muted-foreground">Start Date</p>
<p className="text-muted-foreground">{t('onboarding.card.startDate')}</p>
<p className="font-medium">{new Date(workflow.startDate).toLocaleDateString()}</p>
</div>
<div>
<p className="text-muted-foreground">Current Step</p>
<p className="text-muted-foreground">{t('onboarding.card.currentStep')}</p>
<p className="font-medium">
{workflow.steps.find(s => s.step === workflow.currentStep)?.label || 'N/A'}
</p>
</div>
<div>
<p className="text-muted-foreground">Completed Steps</p>
<p className="text-muted-foreground">{t('onboarding.card.status')}</p>
<p className="font-medium">
{workflow.steps.filter(s => s.status === 'completed').length} / {workflow.steps.length}
</p>
@@ -333,7 +351,7 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
<p className="text-sm font-medium">{step.label}</p>
{step.completedDate && (
<p className="text-xs text-muted-foreground">
Completed {new Date(step.completedDate).toLocaleDateString()}
{t('onboarding.stepStatus.completed')} {new Date(step.completedDate).toLocaleDateString()}
</p>
)}
</div>
@@ -351,7 +369,7 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
variant="outline"
onClick={() => onCompleteStep(workflow.id, step.step)}
>
Mark Complete
{t('onboarding.completeStep')}
</Button>
)}
</div>
@@ -364,12 +382,12 @@ function OnboardingCard({ workflow, onCompleteStep, onSendReminder }: Onboarding
<div className="flex flex-col gap-2 ml-4">
<Button size="sm" variant="outline" onClick={() => setShowDetails(!showDetails)}>
{showDetails ? 'Hide' : 'View'} Steps
{showDetails ? t('common.close') : t('onboarding.viewWorkflow')}
</Button>
{workflow.status !== 'completed' && onSendReminder && (
<Button size="sm" variant="outline" onClick={() => onSendReminder(workflow)}>
<Envelope size={16} className="mr-2" />
Remind
{t('onboarding.sendReminder')}
</Button>
)}
</div>

View File

@@ -1,6 +1,7 @@
import { useState, useEffect, useMemo } from 'react'
import { usePurchaseOrdersCrud } from '@/hooks/use-purchase-orders-crud'
import { useInvoicesCrud } from '@/hooks/use-invoices-crud'
import { useTranslation } from '@/hooks/use-translation'
import {
FileText,
Plus,
@@ -33,6 +34,7 @@ import { cn } from '@/lib/utils'
import type { PurchaseOrder, LinkedInvoice, Invoice } from '@/lib/types'
export function PurchaseOrderTracking() {
const { t } = useTranslation()
const { entities: purchaseOrders, create, update, remove } = usePurchaseOrdersCrud()
const { invoices } = useInvoicesCrud()
const [searchQuery, setSearchQuery] = useState('')
@@ -109,13 +111,13 @@ export function PurchaseOrderTracking() {
const handleCreate = async () => {
if (!formData.poNumber || !formData.clientName || !formData.totalValue) {
toast.error('Please fill in all required fields')
toast.error(t('purchaseOrders.createDialog.fillAllFields'))
return
}
const totalValue = parseFloat(formData.totalValue)
if (isNaN(totalValue) || totalValue <= 0) {
toast.error('Total value must be a positive number')
toast.error(t('purchaseOrders.createDialog.invalidValue'))
return
}
@@ -142,7 +144,7 @@ export function PurchaseOrderTracking() {
try {
await create(newPO)
toast.success(`Purchase Order ${newPO.poNumber} created successfully`)
toast.success(t('purchaseOrders.messages.createSuccess', { poNumber: newPO.poNumber }))
setFormData({
poNumber: '',
@@ -156,7 +158,7 @@ export function PurchaseOrderTracking() {
})
setIsCreateOpen(false)
} catch (error) {
toast.error('Failed to create purchase order')
toast.error(t('purchaseOrders.messages.createError'))
console.error(error)
}
}
@@ -166,17 +168,21 @@ export function PurchaseOrderTracking() {
const invoice = invoices.find(inv => inv.id === invoiceId)
if (!invoice) {
toast.error('Invoice not found')
toast.error(t('purchaseOrders.messages.invoiceNotFound'))
return
}
if (selectedPO.linkedInvoices.some(li => li.invoiceId === invoiceId)) {
toast.error('Invoice already linked to this PO')
toast.error(t('purchaseOrders.messages.alreadyLinked'))
return
}
if (invoice.amount > selectedPO.remainingValue) {
toast.warning(`Invoice amount (${invoice.currency}${invoice.amount}) exceeds remaining PO value (${selectedPO.currency}${selectedPO.remainingValue})`)
toast.warning(t('purchaseOrders.messages.exceedsRemaining', {
currency: invoice.currency,
amount: invoice.amount,
remaining: selectedPO.remainingValue
}))
}
const linkedInvoice: LinkedInvoice = {
@@ -203,10 +209,13 @@ export function PurchaseOrderTracking() {
lastModifiedDate: new Date().toISOString()
})
setSelectedPO(updatedPO)
toast.success(`Invoice ${invoice.invoiceNumber} linked to PO ${selectedPO.poNumber}`)
toast.success(t('purchaseOrders.messages.linkSuccess', {
invoiceNumber: invoice.invoiceNumber,
poNumber: selectedPO.poNumber
}))
setIsLinkInvoiceOpen(false)
} catch (error) {
toast.error('Failed to link invoice')
toast.error(t('purchaseOrders.messages.linkError'))
console.error(error)
}
}
@@ -230,28 +239,28 @@ export function PurchaseOrderTracking() {
lastModifiedDate: new Date().toISOString()
})
setSelectedPO(updatedPO)
toast.success(`Invoice ${linkedInvoice.invoiceNumber} unlinked from PO`)
toast.success(t('purchaseOrders.messages.unlinkSuccess', { invoiceNumber: linkedInvoice.invoiceNumber }))
} catch (error) {
toast.error('Failed to unlink invoice')
toast.error(t('purchaseOrders.messages.unlinkError'))
console.error(error)
}
}
const handleDelete = async (po: PurchaseOrder) => {
if (po.linkedInvoices.length > 0) {
toast.error('Cannot delete PO with linked invoices. Unlink all invoices first.')
toast.error(t('purchaseOrders.messages.cannotDeleteLinked'))
return
}
try {
await remove(po.id)
toast.success(`Purchase Order ${po.poNumber} deleted`)
toast.success(t('purchaseOrders.messages.deleteSuccess', { poNumber: po.poNumber }))
if (selectedPO?.id === po.id) {
setIsDetailOpen(false)
setSelectedPO(null)
}
} catch (error) {
toast.error('Failed to delete purchase order')
toast.error(t('purchaseOrders.messages.deleteError'))
console.error(error)
}
}
@@ -293,7 +302,7 @@ export function PurchaseOrderTracking() {
<div className="flex items-center justify-center h-96">
<div className="text-center">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-primary"></div>
<p className="mt-4 text-muted-foreground">Loading purchase orders...</p>
<p className="mt-4 text-muted-foreground">{t('common.loading')}</p>
</div>
</div>
)
@@ -303,12 +312,12 @@ export function PurchaseOrderTracking() {
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-3xl font-semibold tracking-tight">Purchase Order Tracking</h2>
<p className="text-muted-foreground mt-1">Track and manage client purchase orders with invoice linking</p>
<h2 className="text-3xl font-semibold tracking-tight">{t('purchaseOrders.title')}</h2>
<p className="text-muted-foreground mt-1">{t('purchaseOrders.subtitle')}</p>
</div>
<Button onClick={() => setIsCreateOpen(true)}>
<Plus size={18} className="mr-2" />
Create PO
{t('purchaseOrders.createPO')}
</Button>
</div>

View File

@@ -724,13 +724,235 @@
"exampleDescription": "Professional services rendered",
"exampleHourly": "hours @ {{rate}}/hr"
},
"purchaseOrders": {
"title": "Purchase Order Tracking",
"subtitle": "Track purchase orders and link invoices",
"createPO": "Create Purchase Order",
"editPO": "Edit Purchase Order",
"deletePO": "Delete Purchase Order",
"viewDetails": "View Details",
"poNumber": "PO Number",
"clientName": "Client Name",
"clientId": "Client ID",
"issueDate": "Issue Date",
"expiryDate": "Expiry Date",
"totalValue": "Total Value",
"remainingValue": "Remaining Value",
"utilisedValue": "Utilised Value",
"utilization": "Utilization",
"currency": "Currency",
"notes": "Notes",
"approvedBy": "Approved By",
"approvedDate": "Approved Date",
"linkedInvoices": "Linked Invoices",
"linkInvoice": "Link Invoice",
"unlinkInvoice": "Unlink Invoice",
"noLinkedInvoices": "No invoices linked to this PO yet",
"status": {
"active": "Active",
"expiringSoon": "Expiring Soon",
"expired": "Expired",
"fulfilled": "Fulfilled",
"cancelled": "Cancelled",
"all": "All Status"
},
"metrics": {
"activePOs": "Active POs",
"totalValue": "Total Value",
"remainingValue": "Remaining Value",
"utilization": "Avg Utilization",
"expiringSoon": "Expiring Soon",
"expiryWarning": "POs expiring within 30 days"
},
"createDialog": {
"title": "Create Purchase Order",
"description": "Create a new purchase order for client billing",
"poNumberLabel": "PO Number *",
"poNumberPlaceholder": "PO-2024-001",
"clientNameLabel": "Client Name *",
"clientNamePlaceholder": "Enter client name",
"clientIdLabel": "Client ID",
"clientIdPlaceholder": "CLIENT-001",
"totalValueLabel": "Total Value *",
"totalValuePlaceholder": "10000.00",
"expiryDateLabel": "Expiry Date",
"currencyLabel": "Currency",
"notesLabel": "Notes",
"notesPlaceholder": "Additional notes or terms",
"approvedByLabel": "Approved By",
"approvedByPlaceholder": "Approver name",
"cancel": "Cancel",
"create": "Create PO",
"fillAllFields": "Please fill in all required fields",
"invalidValue": "Total value must be a positive number"
},
"detailDialog": {
"title": "Purchase Order Details",
"overview": "Overview",
"linkedInvoicesTab": "Linked Invoices",
"auditTrail": "Audit Trail",
"poNumber": "PO Number",
"client": "Client",
"status": "Status",
"issueDate": "Issue Date",
"expiryDate": "Expiry Date",
"financials": "Financials",
"totalValue": "Total Value",
"utilisedValue": "Utilised",
"remainingValue": "Remaining",
"utilization": "Utilization",
"additionalInfo": "Additional Information",
"approvedBy": "Approved By",
"approvedDate": "Approved Date",
"createdBy": "Created By",
"createdDate": "Created Date",
"lastModified": "Last Modified",
"notes": "Notes",
"noNotes": "No notes",
"actions": "Actions",
"linkInvoice": "Link Invoice",
"edit": "Edit",
"delete": "Delete",
"close": "Close",
"invoiceNumber": "Invoice #",
"amount": "Amount",
"linkedDate": "Linked Date",
"linkedBy": "Linked By",
"unlink": "Unlink"
},
"linkInvoiceDialog": {
"title": "Link Invoice to PO",
"description": "Select an invoice to link to {{poNumber}}",
"availableInvoices": "Available Invoices",
"invoiceNumber": "Invoice Number",
"client": "Client",
"amount": "Amount",
"date": "Date",
"selectInvoice": "Select Invoice",
"cancel": "Cancel",
"link": "Link Invoice",
"noInvoices": "No available invoices",
"noInvoicesDescription": "All invoices are already linked or no invoices exist"
},
"searchPlaceholder": "Search by PO number or client name...",
"filterByStatus": "Filter by Status",
"tabs": {
"active": "Active",
"expiringSoon": "Expiring Soon",
"expired": "Expired",
"fulfilled": "Fulfilled",
"all": "All"
},
"emptyStates": {
"noPOs": "No purchase orders found",
"noPOsDescription": "Create your first purchase order to start tracking",
"noPOsForFilter": "No purchase orders match your filters"
},
"card": {
"client": "Client",
"expires": "Expires",
"expired": "Expired",
"noExpiry": "No expiry",
"utilization": "Utilization",
"remaining": "Remaining",
"linkedInvoices": "{{count}} linked invoices",
"daysUntilExpiry": "{{days}} days until expiry"
},
"messages": {
"createSuccess": "Purchase Order {{poNumber}} created successfully",
"createError": "Failed to create purchase order",
"deleteSuccess": "Purchase Order {{poNumber}} deleted",
"deleteError": "Failed to delete purchase order",
"cannotDeleteLinked": "Cannot delete PO with linked invoices. Unlink all invoices first.",
"linkSuccess": "Invoice {{invoiceNumber}} linked to PO {{poNumber}}",
"linkError": "Failed to link invoice",
"unlinkSuccess": "Invoice {{invoiceNumber}} unlinked from PO",
"unlinkError": "Failed to unlink invoice",
"invoiceNotFound": "Invoice not found",
"alreadyLinked": "Invoice already linked to this PO",
"exceedsRemaining": "Invoice amount ({{currency}}{{amount}}) exceeds remaining PO value ({{currency}}{{remaining}})"
}
},
"onboarding": {
"title": "Onboarding",
"workflow": "Onboarding Workflow",
"step": "Step",
"complete": "Complete",
"pending": "Pending",
"assignTasks": "Assign Tasks"
"title": "Digital Onboarding",
"subtitle": "Manage worker onboarding workflows",
"startOnboarding": "Start Onboarding",
"viewWorkflow": "View Workflow",
"sendReminder": "Send Reminder",
"completeStep": "Complete Step",
"blockWorkflow": "Block Workflow",
"unblockWorkflow": "Unblock Workflow",
"status": {
"notStarted": "Not Started",
"inProgress": "In Progress",
"completed": "Completed",
"blocked": "Blocked"
},
"metrics": {
"inProgress": "In Progress",
"completed": "Completed",
"blocked": "Blocked",
"avgTime": "Avg. Time",
"avgTimeDays": "{{days}} days"
},
"steps": {
"personalInfo": "Personal Information",
"rightToWork": "Right to Work",
"taxForms": "Tax Forms",
"bankDetails": "Bank Details",
"complianceDocs": "Compliance Documents",
"contractSigning": "Contract Signing"
},
"stepStatus": {
"pending": "Pending",
"inProgress": "In Progress",
"completed": "Completed",
"blocked": "Blocked"
},
"createDialog": {
"title": "Start New Onboarding",
"description": "Create a digital onboarding workflow for a new worker",
"workerNameLabel": "Worker Name *",
"workerNamePlaceholder": "John Smith",
"emailLabel": "Email Address *",
"emailPlaceholder": "john.smith@example.com",
"startDateLabel": "Expected Start Date *",
"cancel": "Cancel",
"start": "Start Onboarding",
"fillAllFields": "Please fill in all required fields"
},
"tabs": {
"inProgress": "In Progress ({{count}})",
"completed": "Completed ({{count}})",
"blocked": "Blocked ({{count}})"
},
"card": {
"workerId": "Worker ID",
"email": "Email",
"startDate": "Start Date",
"progress": "Progress",
"currentStep": "Current Step",
"status": "Status",
"viewDetails": "View Details",
"sendReminder": "Send Reminder",
"completeCurrentStep": "Complete Current Step"
},
"emptyStates": {
"noActive": "No active onboardings",
"noActiveDescription": "Start a new onboarding workflow to begin",
"noCompleted": "No completed onboardings",
"noCompletedDescription": "Completed workflows will appear here",
"noBlocked": "No blocked onboardings",
"noBlockedDescription": "Workflows requiring attention will appear here"
},
"messages": {
"createSuccess": "Onboarding workflow created for {{workerName}}",
"createError": "Failed to create onboarding workflow",
"stepCompleted": "Step completed",
"stepCompletedError": "Failed to complete step",
"reminderSent": "Reminder email sent to {{email}}",
"reminderError": "Failed to send reminder"
}
},
"auditTrail": {
"title": "Audit Trail",

View File

@@ -724,13 +724,235 @@
"exampleDescription": "Servicios profesionales prestados",
"exampleHourly": "horas @ {{rate}}/hr"
},
"purchaseOrders": {
"title": "Seguimiento de Órdenes de Compra",
"subtitle": "Rastrear órdenes de compra y vincular facturas",
"createPO": "Crear Orden de Compra",
"editPO": "Editar Orden de Compra",
"deletePO": "Eliminar Orden de Compra",
"viewDetails": "Ver Detalles",
"poNumber": "Número de OC",
"clientName": "Nombre del Cliente",
"clientId": "ID del Cliente",
"issueDate": "Fecha de Emisión",
"expiryDate": "Fecha de Vencimiento",
"totalValue": "Valor Total",
"remainingValue": "Valor Restante",
"utilisedValue": "Valor Utilizado",
"utilization": "Utilización",
"currency": "Moneda",
"notes": "Notas",
"approvedBy": "Aprobado Por",
"approvedDate": "Fecha de Aprobación",
"linkedInvoices": "Facturas Vinculadas",
"linkInvoice": "Vincular Factura",
"unlinkInvoice": "Desvincular Factura",
"noLinkedInvoices": "Aún no hay facturas vinculadas a esta OC",
"status": {
"active": "Activa",
"expiringSoon": "Vence Pronto",
"expired": "Vencida",
"fulfilled": "Cumplida",
"cancelled": "Cancelada",
"all": "Todos los Estados"
},
"metrics": {
"activePOs": "OCs Activas",
"totalValue": "Valor Total",
"remainingValue": "Valor Restante",
"utilization": "Utilización Promedio",
"expiringSoon": "Vence Pronto",
"expiryWarning": "OCs que vencen en 30 días"
},
"createDialog": {
"title": "Crear Orden de Compra",
"description": "Crear una nueva orden de compra para facturación del cliente",
"poNumberLabel": "Número de OC *",
"poNumberPlaceholder": "OC-2024-001",
"clientNameLabel": "Nombre del Cliente *",
"clientNamePlaceholder": "Ingresar nombre del cliente",
"clientIdLabel": "ID del Cliente",
"clientIdPlaceholder": "CLIENTE-001",
"totalValueLabel": "Valor Total *",
"totalValuePlaceholder": "10000.00",
"expiryDateLabel": "Fecha de Vencimiento",
"currencyLabel": "Moneda",
"notesLabel": "Notas",
"notesPlaceholder": "Notas o términos adicionales",
"approvedByLabel": "Aprobado Por",
"approvedByPlaceholder": "Nombre del aprobador",
"cancel": "Cancelar",
"create": "Crear OC",
"fillAllFields": "Por favor complete todos los campos requeridos",
"invalidValue": "El valor total debe ser un número positivo"
},
"detailDialog": {
"title": "Detalles de la Orden de Compra",
"overview": "Resumen",
"linkedInvoicesTab": "Facturas Vinculadas",
"auditTrail": "Registro de Auditoría",
"poNumber": "Número de OC",
"client": "Cliente",
"status": "Estado",
"issueDate": "Fecha de Emisión",
"expiryDate": "Fecha de Vencimiento",
"financials": "Finanzas",
"totalValue": "Valor Total",
"utilisedValue": "Utilizado",
"remainingValue": "Restante",
"utilization": "Utilización",
"additionalInfo": "Información Adicional",
"approvedBy": "Aprobado Por",
"approvedDate": "Fecha de Aprobación",
"createdBy": "Creado Por",
"createdDate": "Fecha de Creación",
"lastModified": "Última Modificación",
"notes": "Notas",
"noNotes": "Sin notas",
"actions": "Acciones",
"linkInvoice": "Vincular Factura",
"edit": "Editar",
"delete": "Eliminar",
"close": "Cerrar",
"invoiceNumber": "Factura #",
"amount": "Monto",
"linkedDate": "Fecha de Vinculación",
"linkedBy": "Vinculado Por",
"unlink": "Desvincular"
},
"linkInvoiceDialog": {
"title": "Vincular Factura a OC",
"description": "Seleccionar una factura para vincular a {{poNumber}}",
"availableInvoices": "Facturas Disponibles",
"invoiceNumber": "Número de Factura",
"client": "Cliente",
"amount": "Monto",
"date": "Fecha",
"selectInvoice": "Seleccionar Factura",
"cancel": "Cancelar",
"link": "Vincular Factura",
"noInvoices": "No hay facturas disponibles",
"noInvoicesDescription": "Todas las facturas ya están vinculadas o no existen facturas"
},
"searchPlaceholder": "Buscar por número de OC o nombre del cliente...",
"filterByStatus": "Filtrar por Estado",
"tabs": {
"active": "Activas",
"expiringSoon": "Vencen Pronto",
"expired": "Vencidas",
"fulfilled": "Cumplidas",
"all": "Todas"
},
"emptyStates": {
"noPOs": "No se encontraron órdenes de compra",
"noPOsDescription": "Cree su primera orden de compra para comenzar el seguimiento",
"noPOsForFilter": "No hay órdenes de compra que coincidan con sus filtros"
},
"card": {
"client": "Cliente",
"expires": "Vence",
"expired": "Vencida",
"noExpiry": "Sin vencimiento",
"utilization": "Utilización",
"remaining": "Restante",
"linkedInvoices": "{{count}} facturas vinculadas",
"daysUntilExpiry": "{{days}} días hasta el vencimiento"
},
"messages": {
"createSuccess": "Orden de Compra {{poNumber}} creada exitosamente",
"createError": "Error al crear la orden de compra",
"deleteSuccess": "Orden de Compra {{poNumber}} eliminada",
"deleteError": "Error al eliminar la orden de compra",
"cannotDeleteLinked": "No se puede eliminar una OC con facturas vinculadas. Desvincule todas las facturas primero.",
"linkSuccess": "Factura {{invoiceNumber}} vinculada a OC {{poNumber}}",
"linkError": "Error al vincular la factura",
"unlinkSuccess": "Factura {{invoiceNumber}} desvinculada de la OC",
"unlinkError": "Error al desvincular la factura",
"invoiceNotFound": "Factura no encontrada",
"alreadyLinked": "La factura ya está vinculada a esta OC",
"exceedsRemaining": "El monto de la factura ({{currency}}{{amount}}) excede el valor restante de la OC ({{currency}}{{remaining}})"
}
},
"onboarding": {
"title": "Incorporación",
"workflow": "Flujo de Incorporación",
"step": "Paso",
"complete": "Completo",
"pending": "Pendiente",
"assignTasks": "Asignar Tareas"
"title": "Incorporación Digital",
"subtitle": "Gestionar flujos de trabajo de incorporación de trabajadores",
"startOnboarding": "Iniciar Incorporación",
"viewWorkflow": "Ver Flujo de Trabajo",
"sendReminder": "Enviar Recordatorio",
"completeStep": "Completar Paso",
"blockWorkflow": "Bloquear Flujo",
"unblockWorkflow": "Desbloquear Flujo",
"status": {
"notStarted": "No Iniciado",
"inProgress": "En Progreso",
"completed": "Completado",
"blocked": "Bloqueado"
},
"metrics": {
"inProgress": "En Progreso",
"completed": "Completados",
"blocked": "Bloqueados",
"avgTime": "Tiempo Prom.",
"avgTimeDays": "{{days}} días"
},
"steps": {
"personalInfo": "Información Personal",
"rightToWork": "Derecho a Trabajar",
"taxForms": "Formularios Fiscales",
"bankDetails": "Detalles Bancarios",
"complianceDocs": "Documentos de Cumplimiento",
"contractSigning": "Firma de Contrato"
},
"stepStatus": {
"pending": "Pendiente",
"inProgress": "En Progreso",
"completed": "Completado",
"blocked": "Bloqueado"
},
"createDialog": {
"title": "Iniciar Nueva Incorporación",
"description": "Crear un flujo de trabajo de incorporación digital para un nuevo trabajador",
"workerNameLabel": "Nombre del Trabajador *",
"workerNamePlaceholder": "Juan Pérez",
"emailLabel": "Dirección de Correo *",
"emailPlaceholder": "juan.perez@ejemplo.com",
"startDateLabel": "Fecha de Inicio Esperada *",
"cancel": "Cancelar",
"start": "Iniciar Incorporación",
"fillAllFields": "Por favor complete todos los campos requeridos"
},
"tabs": {
"inProgress": "En Progreso ({{count}})",
"completed": "Completados ({{count}})",
"blocked": "Bloqueados ({{count}})"
},
"card": {
"workerId": "ID del Trabajador",
"email": "Correo Electrónico",
"startDate": "Fecha de Inicio",
"progress": "Progreso",
"currentStep": "Paso Actual",
"status": "Estado",
"viewDetails": "Ver Detalles",
"sendReminder": "Enviar Recordatorio",
"completeCurrentStep": "Completar Paso Actual"
},
"emptyStates": {
"noActive": "No hay incorporaciones activas",
"noActiveDescription": "Inicie un nuevo flujo de trabajo de incorporación para comenzar",
"noCompleted": "No hay incorporaciones completadas",
"noCompletedDescription": "Los flujos de trabajo completados aparecerán aquí",
"noBlocked": "No hay incorporaciones bloqueadas",
"noBlockedDescription": "Los flujos de trabajo que requieren atención aparecerán aquí"
},
"messages": {
"createSuccess": "Flujo de trabajo de incorporación creado para {{workerName}}",
"createError": "Error al crear el flujo de trabajo de incorporación",
"stepCompleted": "Paso completado",
"stepCompletedError": "Error al completar el paso",
"reminderSent": "Correo de recordatorio enviado a {{email}}",
"reminderError": "Error al enviar el recordatorio"
}
},
"auditTrail": {
"title": "Registro de Auditoría",

View File

@@ -724,13 +724,235 @@
"exampleDescription": "Services professionnels rendus",
"exampleHourly": "heures @ {{rate}}/hr"
},
"purchaseOrders": {
"title": "Suivi des Bons de Commande",
"subtitle": "Suivre les bons de commande et lier les factures",
"createPO": "Créer un Bon de Commande",
"editPO": "Modifier le Bon de Commande",
"deletePO": "Supprimer le Bon de Commande",
"viewDetails": "Voir les Détails",
"poNumber": "Numéro de BC",
"clientName": "Nom du Client",
"clientId": "ID du Client",
"issueDate": "Date d'Émission",
"expiryDate": "Date d'Expiration",
"totalValue": "Valeur Totale",
"remainingValue": "Valeur Restante",
"utilisedValue": "Valeur Utilisée",
"utilization": "Utilisation",
"currency": "Devise",
"notes": "Notes",
"approvedBy": "Approuvé Par",
"approvedDate": "Date d'Approbation",
"linkedInvoices": "Factures Liées",
"linkInvoice": "Lier une Facture",
"unlinkInvoice": "Délier la Facture",
"noLinkedInvoices": "Aucune facture liée à ce BC pour le moment",
"status": {
"active": "Actif",
"expiringSoon": "Expire Bientôt",
"expired": "Expiré",
"fulfilled": "Rempli",
"cancelled": "Annulé",
"all": "Tous les États"
},
"metrics": {
"activePOs": "BC Actifs",
"totalValue": "Valeur Totale",
"remainingValue": "Valeur Restante",
"utilization": "Utilisation Moy.",
"expiringSoon": "Expire Bientôt",
"expiryWarning": "BC expirant dans 30 jours"
},
"createDialog": {
"title": "Créer un Bon de Commande",
"description": "Créer un nouveau bon de commande pour la facturation client",
"poNumberLabel": "Numéro de BC *",
"poNumberPlaceholder": "BC-2024-001",
"clientNameLabel": "Nom du Client *",
"clientNamePlaceholder": "Entrer le nom du client",
"clientIdLabel": "ID du Client",
"clientIdPlaceholder": "CLIENT-001",
"totalValueLabel": "Valeur Totale *",
"totalValuePlaceholder": "10000.00",
"expiryDateLabel": "Date d'Expiration",
"currencyLabel": "Devise",
"notesLabel": "Notes",
"notesPlaceholder": "Notes ou conditions supplémentaires",
"approvedByLabel": "Approuvé Par",
"approvedByPlaceholder": "Nom de l'approbateur",
"cancel": "Annuler",
"create": "Créer le BC",
"fillAllFields": "Veuillez remplir tous les champs obligatoires",
"invalidValue": "La valeur totale doit être un nombre positif"
},
"detailDialog": {
"title": "Détails du Bon de Commande",
"overview": "Aperçu",
"linkedInvoicesTab": "Factures Liées",
"auditTrail": "Piste d'Audit",
"poNumber": "Numéro de BC",
"client": "Client",
"status": "État",
"issueDate": "Date d'Émission",
"expiryDate": "Date d'Expiration",
"financials": "Finances",
"totalValue": "Valeur Totale",
"utilisedValue": "Utilisé",
"remainingValue": "Restant",
"utilization": "Utilisation",
"additionalInfo": "Informations Supplémentaires",
"approvedBy": "Approuvé Par",
"approvedDate": "Date d'Approbation",
"createdBy": "Créé Par",
"createdDate": "Date de Création",
"lastModified": "Dernière Modification",
"notes": "Notes",
"noNotes": "Aucune note",
"actions": "Actions",
"linkInvoice": "Lier une Facture",
"edit": "Modifier",
"delete": "Supprimer",
"close": "Fermer",
"invoiceNumber": "Facture #",
"amount": "Montant",
"linkedDate": "Date de Liaison",
"linkedBy": "Lié Par",
"unlink": "Délier"
},
"linkInvoiceDialog": {
"title": "Lier une Facture au BC",
"description": "Sélectionner une facture à lier à {{poNumber}}",
"availableInvoices": "Factures Disponibles",
"invoiceNumber": "Numéro de Facture",
"client": "Client",
"amount": "Montant",
"date": "Date",
"selectInvoice": "Sélectionner une Facture",
"cancel": "Annuler",
"link": "Lier la Facture",
"noInvoices": "Aucune facture disponible",
"noInvoicesDescription": "Toutes les factures sont déjà liées ou aucune facture n'existe"
},
"searchPlaceholder": "Rechercher par numéro de BC ou nom du client...",
"filterByStatus": "Filtrer par État",
"tabs": {
"active": "Actifs",
"expiringSoon": "Expirent Bientôt",
"expired": "Expirés",
"fulfilled": "Remplis",
"all": "Tous"
},
"emptyStates": {
"noPOs": "Aucun bon de commande trouvé",
"noPOsDescription": "Créez votre premier bon de commande pour commencer le suivi",
"noPOsForFilter": "Aucun bon de commande ne correspond à vos filtres"
},
"card": {
"client": "Client",
"expires": "Expire",
"expired": "Expiré",
"noExpiry": "Pas d'expiration",
"utilization": "Utilisation",
"remaining": "Restant",
"linkedInvoices": "{{count}} factures liées",
"daysUntilExpiry": "{{days}} jours jusqu'à l'expiration"
},
"messages": {
"createSuccess": "Bon de Commande {{poNumber}} créé avec succès",
"createError": "Échec de la création du bon de commande",
"deleteSuccess": "Bon de Commande {{poNumber}} supprimé",
"deleteError": "Échec de la suppression du bon de commande",
"cannotDeleteLinked": "Impossible de supprimer un BC avec des factures liées. Déliez toutes les factures d'abord.",
"linkSuccess": "Facture {{invoiceNumber}} liée au BC {{poNumber}}",
"linkError": "Échec de la liaison de la facture",
"unlinkSuccess": "Facture {{invoiceNumber}} déliée du BC",
"unlinkError": "Échec de la déliaison de la facture",
"invoiceNotFound": "Facture introuvable",
"alreadyLinked": "La facture est déjà liée à ce BC",
"exceedsRemaining": "Le montant de la facture ({{currency}}{{amount}}) dépasse la valeur restante du BC ({{currency}}{{remaining}})"
}
},
"onboarding": {
"title": "Intégration",
"workflow": "Flux d'Intégration",
"step": "Étape",
"complete": "Complet",
"pending": "En Attente",
"assignTasks": "Assigner des Tâches"
"title": "Intégration Numérique",
"subtitle": "Gérer les flux de travail d'intégration des travailleurs",
"startOnboarding": "Démarrer l'Intégration",
"viewWorkflow": "Voir le Flux de Travail",
"sendReminder": "Envoyer un Rappel",
"completeStep": "Terminer l'Étape",
"blockWorkflow": "Bloquer le Flux",
"unblockWorkflow": "Débloquer le Flux",
"status": {
"notStarted": "Non Commencé",
"inProgress": "En Cours",
"completed": "Terminé",
"blocked": "Bloqué"
},
"metrics": {
"inProgress": "En Cours",
"completed": "Terminés",
"blocked": "Bloqués",
"avgTime": "Temps Moy.",
"avgTimeDays": "{{days}} jours"
},
"steps": {
"personalInfo": "Informations Personnelles",
"rightToWork": "Droit de Travailler",
"taxForms": "Formulaires Fiscaux",
"bankDetails": "Détails Bancaires",
"complianceDocs": "Documents de Conformité",
"contractSigning": "Signature du Contrat"
},
"stepStatus": {
"pending": "En Attente",
"inProgress": "En Cours",
"completed": "Terminé",
"blocked": "Bloqué"
},
"createDialog": {
"title": "Démarrer une Nouvelle Intégration",
"description": "Créer un flux de travail d'intégration numérique pour un nouveau travailleur",
"workerNameLabel": "Nom du Travailleur *",
"workerNamePlaceholder": "Jean Dupont",
"emailLabel": "Adresse E-mail *",
"emailPlaceholder": "jean.dupont@exemple.com",
"startDateLabel": "Date de Début Prévue *",
"cancel": "Annuler",
"start": "Démarrer l'Intégration",
"fillAllFields": "Veuillez remplir tous les champs obligatoires"
},
"tabs": {
"inProgress": "En Cours ({{count}})",
"completed": "Terminés ({{count}})",
"blocked": "Bloqués ({{count}})"
},
"card": {
"workerId": "ID du Travailleur",
"email": "E-mail",
"startDate": "Date de Début",
"progress": "Progrès",
"currentStep": "Étape Actuelle",
"status": "État",
"viewDetails": "Voir les Détails",
"sendReminder": "Envoyer un Rappel",
"completeCurrentStep": "Terminer l'Étape Actuelle"
},
"emptyStates": {
"noActive": "Aucune intégration active",
"noActiveDescription": "Démarrez un nouveau flux de travail d'intégration pour commencer",
"noCompleted": "Aucune intégration terminée",
"noCompletedDescription": "Les flux de travail terminés apparaîtront ici",
"noBlocked": "Aucune intégration bloquée",
"noBlockedDescription": "Les flux de travail nécessitant une attention apparaîtront ici"
},
"messages": {
"createSuccess": "Flux de travail d'intégration créé pour {{workerName}}",
"createError": "Échec de la création du flux de travail d'intégration",
"stepCompleted": "Étape terminée",
"stepCompletedError": "Échec de la complétion de l'étape",
"reminderSent": "E-mail de rappel envoyé à {{email}}",
"reminderError": "Échec de l'envoi du rappel"
}
},
"auditTrail": {
"title": "Piste d'Audit",