mirror of
https://github.com/johndoe6345789/workforce-pay-bill-p.git
synced 2026-04-24 13:24:57 +00:00
Generated by Spark: Add translations to Purchase Orders and Onboarding views
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user