From 0980c6584b83e06448344d8a4ea6692240215c9e Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Sun, 22 Feb 2026 20:27:01 -0500 Subject: [PATCH] feat: localize "Find Shifts" tab strings and add `filled` status to shift role queries. --- .../lib/src/l10n/en.i18n.json | 38 +- .../lib/src/l10n/es.i18n.json | 720 +++++++++--------- .../shifts_connector_repository_impl.dart | 264 ++++--- .../widgets/tabs/find_shifts_tab.dart | 40 +- .../connector/shiftRole/queries.gql | 24 + 5 files changed, 620 insertions(+), 466 deletions(-) diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json index 19e2ed7d..e29e862d 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/en.i18n.json @@ -104,7 +104,7 @@ "client_authentication": { "get_started_page": { "title": "Take Control of Your\nShifts and Events", - "subtitle": "Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page—all in one place", + "subtitle": "Streamline your operations with powerful tools to manage schedules, track performance, and keep your team on the same page\u2014all in one place", "sign_in_button": "Sign In", "create_account_button": "Create Account" }, @@ -452,7 +452,7 @@ }, "empty_states": { "no_shifts_today": "No shifts scheduled for today", - "find_shifts_cta": "Find shifts →", + "find_shifts_cta": "Find shifts \u2192", "no_shifts_tomorrow": "No shifts for tomorrow", "no_recommended_shifts": "No recommended shifts" }, @@ -462,7 +462,7 @@ "amount": "$amount" }, "recommended_card": { - "act_now": "• ACT NOW", + "act_now": "\u2022 ACT NOW", "one_day": "One Day", "today": "Today", "applied_for": "Applied for $title", @@ -695,7 +695,7 @@ "eta_label": "$min min", "locked_desc": "Most app features are locked while commute mode is on. You'll be able to clock in once you arrive.", "turn_off": "Turn Off Commute Mode", - "arrived_title": "You've Arrived! 🎉", + "arrived_title": "You've Arrived! \ud83c\udf89", "arrived_desc": "You're at the shift location. Ready to clock in?" }, "swipe": { @@ -967,16 +967,16 @@ "required": "REQUIRED", "add_photo": "Add Photo", "added": "Added", - "pending": "⏳ Pending verification" + "pending": "\u23f3 Pending verification" }, "attestation": "I certify that I own these items and will wear them to my shifts. I understand that items are pending manager verification at my first shift.", "actions": { "save": "Save Attire" }, "validation": { - "select_required": "✓ Select all required items", - "upload_required": "✓ Upload photos of required items", - "accept_attestation": "✓ Accept attestation" + "select_required": "\u2713 Select all required items", + "upload_required": "\u2713 Upload photos of required items", + "accept_attestation": "\u2713 Accept attestation" } }, "staff_shifts": { @@ -1095,8 +1095,18 @@ }, "card": { "cancelled": "CANCELLED", - "compensation": "• 4hr compensation" + "compensation": "\u2022 4hr compensation" } + }, + "find_shifts": { + "search_hint": "Search jobs, location...", + "filter_all": "All Jobs", + "filter_one_day": "One Day", + "filter_multi_day": "Multi-Day", + "filter_long_term": "Long Term", + "no_jobs_title": "No jobs available", + "no_jobs_subtitle": "Check back later", + "application_submitted": "Shift application submitted!" } }, "staff_time_card": { @@ -1218,11 +1228,11 @@ }, "total_spend": { "label": "Total Spend", - "badge": "↓ 8% vs last week" + "badge": "\u2193 8% vs last week" }, "fill_rate": { "label": "Fill Rate", - "badge": "↑ 2% improvement" + "badge": "\u2191 2% improvement" }, "avg_fill_time": { "label": "Avg Fill Time", @@ -1364,9 +1374,9 @@ "target_prefix": "Target: ", "target_hours": "$hours hrs", "target_percent": "$percent%", - "met": "✓ Met", - "close": "→ Close", - "miss": "✗ Miss" + "met": "\u2713 Met", + "close": "\u2192 Close", + "miss": "\u2717 Miss" }, "additional_metrics_title": "ADDITIONAL METRICS", "additional_metrics": { diff --git a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json index e96442da..968bf050 100644 --- a/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json +++ b/apps/mobile/packages/core_localization/lib/src/l10n/es.i18n.json @@ -13,139 +13,139 @@ "staff_authentication": { "get_started_page": { "title_part1": "Trabaja, Crece, ", - "title_part2": "Elévate", - "subtitle": "Construye tu carrera en hostelería con \nflexibilidad y libertad.", + "title_part2": "El\u00e9vate", + "subtitle": "Construye tu carrera en hosteler\u00eda con \nflexibilidad y libertad.", "sign_up_button": "Registrarse", - "log_in_button": "Iniciar sesión" + "log_in_button": "Iniciar sesi\u00f3n" }, "phone_verification_page": { - "validation_error": "Por favor, ingresa un número de teléfono válido de 10 dígitos", - "send_code_button": "Enviar código", - "enter_code_title": "Ingresa el código de verificación", - "code_sent_message": "Enviamos un código de 6 dígitos a ", - "code_sent_instruction": ". Ingrésalo a continuación para verificar tu cuenta." + "validation_error": "Por favor, ingresa un n\u00famero de tel\u00e9fono v\u00e1lido de 10 d\u00edgitos", + "send_code_button": "Enviar c\u00f3digo", + "enter_code_title": "Ingresa el c\u00f3digo de verificaci\u00f3n", + "code_sent_message": "Enviamos un c\u00f3digo de 6 d\u00edgitos a ", + "code_sent_instruction": ". Ingr\u00e9salo a continuaci\u00f3n para verificar tu cuenta." }, "phone_input": { - "title": "Verifica tu número de teléfono", - "subtitle": "Te enviaremos un código de verificación para comenzar.", - "label": "Número de teléfono", - "hint": "Ingresa tu número" + "title": "Verifica tu n\u00famero de tel\u00e9fono", + "subtitle": "Te enviaremos un c\u00f3digo de verificaci\u00f3n para comenzar.", + "label": "N\u00famero de tel\u00e9fono", + "hint": "Ingresa tu n\u00famero" }, "otp_verification": { - "did_not_get_code": "¿No recibiste el código?", + "did_not_get_code": "\u00bfNo recibiste el c\u00f3digo?", "resend_in": "Reenviar en $seconds s", - "resend_code": "Reenviar código" + "resend_code": "Reenviar c\u00f3digo" }, "profile_setup_page": { "step_indicator": "Paso $current de $total", - "error_occurred": "Ocurrió un error", - "complete_setup_button": "Completar configuración", + "error_occurred": "Ocurri\u00f3 un error", + "complete_setup_button": "Completar configuraci\u00f3n", "steps": { - "basic": "Información básica", - "location": "Ubicación", + "basic": "Informaci\u00f3n b\u00e1sica", + "location": "Ubicaci\u00f3n", "experience": "Experiencia" }, "basic_info": { - "title": "Conozcámonos", - "subtitle": "Cuéntanos un poco sobre ti", + "title": "Conozc\u00e1monos", + "subtitle": "Cu\u00e9ntanos un poco sobre ti", "full_name_label": "Nombre completo *", - "full_name_hint": "Juan Pérez", - "bio_label": "Biografía corta", - "bio_hint": "Profesional experimentado en hostelería..." + "full_name_hint": "Juan P\u00e9rez", + "bio_label": "Biograf\u00eda corta", + "bio_hint": "Profesional experimentado en hosteler\u00eda..." }, "location": { - "title": "¿Dónde quieres trabajar?", + "title": "\u00bfD\u00f3nde quieres trabajar?", "subtitle": "Agrega tus ubicaciones de trabajo preferidas", "full_name_label": "Nombre completo", - "add_location_label": "Agregar ubicación *", - "add_location_hint": "Ciudad o código postal", + "add_location_label": "Agregar ubicaci\u00f3n *", + "add_location_hint": "Ciudad o c\u00f3digo postal", "add_button": "Agregar", - "max_distance": "Distancia máxima: $distance millas", + "max_distance": "Distancia m\u00e1xima: $distance millas", "min_dist_label": "5 mi", "max_dist_label": "50 mi" }, "experience": { - "title": "¿Cuáles son tus habilidades?", + "title": "\u00bfCu\u00e1les son tus habilidades?", "subtitle": "Selecciona todas las que correspondan", "skills_label": "Habilidades *", "industries_label": "Industrias preferidas", "skills": { "food_service": "Servicio de comida", - "bartending": "Preparación de bebidas", - "warehouse": "Almacén", + "bartending": "Preparaci\u00f3n de bebidas", + "warehouse": "Almac\u00e9n", "retail": "Venta minorista", "events": "Eventos", "customer_service": "Servicio al cliente", "cleaning": "Limpieza", "security": "Seguridad", - "driving": "Conducción", + "driving": "Conducci\u00f3n", "cooking": "Cocina", "cashier": "Cajero", "server": "Mesero", "barista": "Barista", - "host_hostess": "Anfitrión", + "host_hostess": "Anfitri\u00f3n", "busser": "Ayudante de mesero" }, "industries": { - "hospitality": "Hostelería", + "hospitality": "Hosteler\u00eda", "food_service": "Servicio de comida", - "warehouse": "Almacén", + "warehouse": "Almac\u00e9n", "events": "Eventos", "retail": "Venta minorista", - "healthcare": "Atención médica" + "healthcare": "Atenci\u00f3n m\u00e9dica" } } }, "common": { - "trouble_question": "¿Tienes problemas? ", + "trouble_question": "\u00bfTienes problemas? ", "contact_support": "Contactar a soporte" } }, "client_authentication": { "get_started_page": { "title": "Toma el control de tus\nturnos y eventos", - "subtitle": "Optimiza tus operaciones con potentes herramientas para gestionar horarios, realizar un seguimiento del rendimiento y mantener a tu equipo en la misma página, todo en un solo lugar", - "sign_in_button": "Iniciar sesión", + "subtitle": "Optimiza tus operaciones con potentes herramientas para gestionar horarios, realizar un seguimiento del rendimiento y mantener a tu equipo en la misma p\u00e1gina, todo en un solo lugar", + "sign_in_button": "Iniciar sesi\u00f3n", "create_account_button": "Crear cuenta" }, "sign_in_page": { "title": "Bienvenido de nuevo", - "subtitle": "Inicia sesión para gestionar tus turnos y trabajadores", - "email_label": "Correo electrónico", - "email_hint": "Ingresa tu correo electrónico", - "password_label": "Contraseña", - "password_hint": "Ingresa tu contraseña", - "forgot_password": "¿Olvidaste tu contraseña?", - "sign_in_button": "Iniciar sesión", + "subtitle": "Inicia sesi\u00f3n para gestionar tus turnos y trabajadores", + "email_label": "Correo electr\u00f3nico", + "email_hint": "Ingresa tu correo electr\u00f3nico", + "password_label": "Contrase\u00f1a", + "password_hint": "Ingresa tu contrase\u00f1a", + "forgot_password": "\u00bfOlvidaste tu contrase\u00f1a?", + "sign_in_button": "Iniciar sesi\u00f3n", "or_divider": "o", - "social_apple": "Iniciar sesión con Apple", - "social_google": "Iniciar sesión con Google", - "no_account": "¿No tienes una cuenta? ", - "sign_up_link": "Regístrate" + "social_apple": "Iniciar sesi\u00f3n con Apple", + "social_google": "Iniciar sesi\u00f3n con Google", + "no_account": "\u00bfNo tienes una cuenta? ", + "sign_up_link": "Reg\u00edstrate" }, "sign_up_page": { "title": "Crear cuenta", "subtitle": "Comienza con Krow para tu negocio", "company_label": "Nombre de la empresa", "company_hint": "Ingresa el nombre de la empresa", - "email_label": "Correo electrónico", - "email_hint": "Ingresa tu correo electrónico", - "password_label": "Contraseña", - "password_hint": "Crea una contraseña", - "confirm_password_label": "Confirmar contraseña", - "confirm_password_hint": "Confirma tu contraseña", + "email_label": "Correo electr\u00f3nico", + "email_hint": "Ingresa tu correo electr\u00f3nico", + "password_label": "Contrase\u00f1a", + "password_hint": "Crea una contrase\u00f1a", + "confirm_password_label": "Confirmar contrase\u00f1a", + "confirm_password_hint": "Confirma tu contrase\u00f1a", "create_account_button": "Crear cuenta", "or_divider": "o", - "social_apple": "Regístrate con Apple", - "social_google": "Regístrate con Google", - "has_account": "¿Ya tienes una cuenta? ", - "sign_in_link": "Iniciar sesión" + "social_apple": "Reg\u00edstrate con Apple", + "social_google": "Reg\u00edstrate con Google", + "has_account": "\u00bfYa tienes una cuenta? ", + "sign_in_link": "Iniciar sesi\u00f3n" } }, "client_home": { "dashboard": { "welcome_back": "Bienvenido de nuevo", - "edit_mode_active": "Modo Edición Activo", + "edit_mode_active": "Modo Edici\u00f3n Activo", "drag_instruction": "Arrastra para reordenar, cambia la visibilidad", "reset": "Restablecer", "todays_coverage": "COBERTURA DE HOY", @@ -155,24 +155,24 @@ "metric_open": "Abierto", "spending": { "this_week": "Esta Semana", - "next_7_days": "Próximos 7 Días", + "next_7_days": "Pr\u00f3ximos 7 D\u00edas", "shifts_count": "$count turnos", "scheduled_count": "$count programados" }, "view_all": "Ver todo", "insight_lightbulb": "Ahorra $amount/mes", - "insight_tip": "Reserva con 48h de antelación para mejores tarifas" + "insight_tip": "Reserva con 48h de antelaci\u00f3n para mejores tarifas" }, "widgets": { - "actions": "Acciones Rápidas", + "actions": "Acciones R\u00e1pidas", "reorder": "Reordenar", "coverage": "Cobertura de Hoy", - "spending": "Información de Gastos", + "spending": "Informaci\u00f3n de Gastos", "live_activity": "Actividad en Vivo" }, "actions": { - "rapid": "RÁPIDO", - "rapid_subtitle": "Urgente mismo día", + "rapid": "R\u00c1PIDO", + "rapid_subtitle": "Urgente mismo d\u00eda", "create_order": "Crear Orden", "create_order_subtitle": "Programar turnos", "hubs": "Hubs", @@ -189,10 +189,10 @@ "review_subtitle": "Revisa y edita los detalles antes de publicar", "date_label": "Fecha *", "date_hint": "mm/dd/aaaa", - "location_label": "Ubicación *", - "location_hint": "Dirección del negocio", + "location_label": "Ubicaci\u00f3n *", + "location_hint": "Direcci\u00f3n del negocio", "positions_title": "Posiciones", - "add_position": "Añadir Posición", + "add_position": "A\u00f1adir Posici\u00f3n", "role_label": "Rol *", "role_hint": "Seleccionar rol", "start_time": "Hora de Inicio *", @@ -207,50 +207,50 @@ "title": "Perfil", "edit_profile": "Editar Perfil", "hubs": "Hubs", - "log_out": "Cerrar sesión", - "quick_links": "Enlaces rápidos", + "log_out": "Cerrar sesi\u00f3n", + "quick_links": "Enlaces r\u00e1pidos", "clock_in_hubs": "Hubs de Marcaje", - "billing_payments": "Facturación y Pagos" + "billing_payments": "Facturaci\u00f3n y Pagos" } }, "client_hubs": { "title": "Hubs", "subtitle": "Gestionar ubicaciones de marcaje", - "add_hub": "Añadir Hub", + "add_hub": "A\u00f1adir Hub", "empty_state": { - "title": "No hay hubs aún", + "title": "No hay hubs a\u00fan", "description": "Crea estaciones de marcaje para tus ubicaciones", - "button": "Añade tu primer Hub" + "button": "A\u00f1ade tu primer Hub" }, "about_hubs": { "title": "Sobre los Hubs", - "description": "Los Hubs son estaciones de marcaje en tus ubicaciones. Asigna etiquetas NFC a cada hub para que los trabajadores puedan marcar entrada/salida rápidamente usando sus teléfonos." + "description": "Los Hubs son estaciones de marcaje en tus ubicaciones. Asigna etiquetas NFC a cada hub para que los trabajadores puedan marcar entrada/salida r\u00e1pidamente usando sus tel\u00e9fonos." }, "hub_card": { "tag_label": "Etiqueta: $id" }, "add_hub_dialog": { - "title": "Añadir Nuevo Hub", + "title": "A\u00f1adir Nuevo Hub", "name_label": "Nombre del Hub *", - "name_hint": "ej., Cocina Principal, Recepción", - "location_label": "Nombre de la Ubicación", + "name_hint": "ej., Cocina Principal, Recepci\u00f3n", + "location_label": "Nombre de la Ubicaci\u00f3n", "location_hint": "ej., Restaurante Centro", - "address_label": "Dirección", - "address_hint": "Dirección completa", + "address_label": "Direcci\u00f3n", + "address_hint": "Direcci\u00f3n completa", "create_button": "Crear Hub" }, "nfc_dialog": { "title": "Identificar Etiqueta NFC", - "instruction": "Acerque su teléfono a la etiqueta NFC para identificarla", + "instruction": "Acerque su tel\u00e9fono a la etiqueta NFC para identificarla", "scan_button": "Escanear Etiqueta NFC", "tag_identified": "Etiqueta Identificada", "assign_button": "Asignar Etiqueta" }, "delete_dialog": { - "title": "Confirmar eliminación de Hub", - "message": "¿Estás seguro de que quieres eliminar \"$hubName\"?", - "undo_warning": "Esta acción no se puede deshacer.", - "dependency_warning": "Ten en cuenta que si hay turnos/órdenes asignados a este hub no deberíamos poder eliminarlo.", + "title": "Confirmar eliminaci\u00f3n de Hub", + "message": "\u00bfEst\u00e1s seguro de que quieres eliminar \"$hubName\"?", + "undo_warning": "Esta acci\u00f3n no se puede deshacer.", + "dependency_warning": "Ten en cuenta que si hay turnos/\u00f3rdenes asignados a este hub no deber\u00edamos poder eliminarlo.", "cancel": "Cancelar", "delete": "Eliminar" }, @@ -259,16 +259,16 @@ "subtitle": "Actualizar detalles del hub", "name_label": "Nombre del Hub", "name_hint": "Ingresar nombre del hub", - "address_label": "Dirección", - "address_hint": "Ingresar dirección", + "address_label": "Direcci\u00f3n", + "address_hint": "Ingresar direcci\u00f3n", "save_button": "Guardar Cambios", - "success": "¡Hub actualizado exitosamente!" + "success": "\u00a1Hub actualizado exitosamente!" }, "hub_details": { "title": "Detalles del Hub", "edit_button": "Editar", "name_label": "Nombre del Hub", - "address_label": "Dirección", + "address_label": "Direcci\u00f3n", "nfc_label": "Etiqueta NFC", "nfc_not_assigned": "No asignada" } @@ -277,21 +277,21 @@ "title": "Crear Orden", "section_title": "TIPO DE ORDEN", "types": { - "rapid": "RÁPIDO", - "rapid_desc": "Cobertura URGENTE mismo día", - "one_time": "Única Vez", - "one_time_desc": "Evento Único o Petición de Turno", + "rapid": "R\u00c1PIDO", + "rapid_desc": "Cobertura URGENTE mismo d\u00eda", + "one_time": "\u00danica Vez", + "one_time_desc": "Evento \u00danico o Petici\u00f3n de Turno", "recurring": "Recurrente", "recurring_desc": "Cobertura Continua Semanal / Mensual", "permanent": "Permanente", - "permanent_desc": "Colocación de Personal a Largo Plazo" + "permanent_desc": "Colocaci\u00f3n de Personal a Largo Plazo" }, "rapid": { - "title": "Orden RÁPIDA", + "title": "Orden R\u00c1PIDA", "subtitle": "Personal de emergencia en minutos", "urgent_badge": "URGENTE", - "tell_us": "Dinos qué necesitas", - "need_staff": "¿Necesitas personal urgentemente?", + "tell_us": "Dinos qu\u00e9 necesitas", + "need_staff": "\u00bfNecesitas personal urgentemente?", "type_or_speak": "Escribe o habla lo que necesitas. Yo me encargo del resto", "example": "Ejemplo: ", "hint": "Escribe o habla... (ej., \"Necesito 5 cocineros YA hasta las 5am\")", @@ -299,35 +299,35 @@ "listening": "Escuchando...", "send": "Enviar Mensaje", "sending": "Enviando...", - "success_title": "¡Solicitud Enviada!", + "success_title": "\u00a1Solicitud Enviada!", "success_message": "Estamos encontrando trabajadores disponibles para ti ahora mismo. Te notificaremos cuando acepten.", - "back_to_orders": "Volver a Órdenes" + "back_to_orders": "Volver a \u00d3rdenes" }, "one_time": { - "title": "Orden Única Vez", - "subtitle": "Evento único o petición de turno", + "title": "Orden \u00danica Vez", + "subtitle": "Evento \u00fanico o petici\u00f3n de turno", "create_your_order": "Crea Tu Orden", "date_label": "Fecha", "date_hint": "Seleccionar fecha", - "location_label": "Ubicación", - "location_hint": "Ingresar dirección", + "location_label": "Ubicaci\u00f3n", + "location_hint": "Ingresar direcci\u00f3n", "positions_title": "Posiciones", - "add_position": "Añadir Posición", - "position_number": "Posición $number", + "add_position": "A\u00f1adir Posici\u00f3n", + "position_number": "Posici\u00f3n $number", "remove": "Eliminar", "select_role": "Seleccionar rol", "start_label": "Inicio", "end_label": "Fin", "workers_label": "Trabajadores", "lunch_break_label": "Descanso para Almuerzo", - "different_location": "Usar ubicación diferente para esta posición", - "different_location_title": "Ubicación Diferente", - "different_location_hint": "Ingresar dirección diferente", + "different_location": "Usar ubicaci\u00f3n diferente para esta posici\u00f3n", + "different_location_title": "Ubicaci\u00f3n Diferente", + "different_location_hint": "Ingresar direcci\u00f3n diferente", "create_order": "Crear Orden", "creating": "Creando...", - "success_title": "¡Orden Creada!", - "success_message": "Tu solicitud de turno ha sido publicada. Los trabajadores comenzarán a postularse pronto.", - "back_to_orders": "Volver a Órdenes", + "success_title": "\u00a1Orden Creada!", + "success_message": "Tu solicitud de turno ha sido publicada. Los trabajadores comenzar\u00e1n a postularse pronto.", + "back_to_orders": "Volver a \u00d3rdenes", "no_break": "Sin descanso", "paid_break": "min (Pagado)", "unpaid_break": "min (No pagado)" @@ -339,26 +339,26 @@ }, "permanent": { "title": "Orden Permanente", - "subtitle": "Colocación de personal a largo plazo", + "subtitle": "Colocaci\u00f3n de personal a largo plazo", "placeholder": "Flujo de Orden Permanente (Trabajo en Progreso)" } }, "client_main": { "tabs": { "coverage": "Cobertura", - "billing": "Facturación", + "billing": "Facturaci\u00f3n", "home": "Inicio", - "orders": "Órdenes", + "orders": "\u00d3rdenes", "reports": "Reportes" } }, "client_view_orders": { - "title": "Órdenes", + "title": "\u00d3rdenes", "post_button": "Publicar", "post_order": "Publicar una Orden", - "no_orders": "No hay órdenes para $date", + "no_orders": "No hay \u00f3rdenes para $date", "tabs": { - "up_next": "Próximos", + "up_next": "Pr\u00f3ximos", "active": "Activos", "completed": "Completados" }, @@ -369,7 +369,7 @@ "in_progress": "EN PROGRESO", "completed": "COMPLETADO", "cancelled": "CANCELADO", - "get_direction": "Obtener dirección", + "get_direction": "Obtener direcci\u00f3n", "total": "Total", "hrs": "Hrs", "workers": "$count trabajadores", @@ -378,42 +378,42 @@ "coverage": "Cobertura", "workers_label": "$filled/$needed Trabajadores", "confirmed_workers": "Trabajadores Confirmados", - "no_workers": "Ningún trabajador confirmado aún.", + "no_workers": "Ning\u00fan trabajador confirmado a\u00fan.", "today": "Hoy", - "tomorrow": "Mañana", + "tomorrow": "Ma\u00f1ana", "workers_needed": "$count Trabajadores Necesarios", "all_confirmed": "Todos los trabajadores confirmados", "confirmed_workers_title": "TRABAJADORES CONFIRMADOS", "message_all": "Mensaje a todos", - "show_more_workers": "Mostrar $count trabajadores más", + "show_more_workers": "Mostrar $count trabajadores m\u00e1s", "checked_in": "Registrado", "call_dialog": { "title": "Llamar", - "message": "¿Quieres llamar a $phone?" + "message": "\u00bfQuieres llamar a $phone?" } } }, "client_billing": { - "title": "Facturación", - "current_period": "Período Actual", + "title": "Facturaci\u00f3n", + "current_period": "Per\u00edodo Actual", "saved_amount": "$amount ahorrado", - "awaiting_approval": "Esperando Aprobación", - "payment_method": "Método de Pago", - "add_payment": "Añadir", + "awaiting_approval": "Esperando Aprobaci\u00f3n", + "payment_method": "M\u00e9todo de Pago", + "add_payment": "A\u00f1adir", "default_badge": "Predeterminado", "expires": "Expira $date", - "period_breakdown": "Desglose de este Período", + "period_breakdown": "Desglose de este Per\u00edodo", "week": "Semana", "month": "Mes", "total": "Total", "hours": "$count horas", - "rate_optimization_title": "Optimización de Tarifas", + "rate_optimization_title": "Optimizaci\u00f3n de Tarifas", "rate_optimization_body": "Ahorra $amount/mes cambiando 3 turnos", "view_details": "Ver Detalles", "invoice_history": "Historial de Facturas", "view_all": "Ver todo", "export_button": "Exportar Todas las Facturas", - "pending_badge": "PENDIENTE APROBACIÓN", + "pending_badge": "PENDIENTE APROBACI\u00d3N", "paid_badge": "PAGADO" }, "staff": { @@ -433,9 +433,9 @@ }, "banners": { "complete_profile_title": "Completa tu Perfil", - "complete_profile_subtitle": "Verifícate para ver más turnos", + "complete_profile_subtitle": "Verif\u00edcate para ver m\u00e1s turnos", "availability_title": "Disponibilidad", - "availability_subtitle": "Actualiza tu disponibilidad para la próxima semana" + "availability_subtitle": "Actualiza tu disponibilidad para la pr\u00f3xima semana" }, "quick_actions": { "find_shifts": "Buscar Turnos", @@ -446,14 +446,14 @@ "sections": { "todays_shift": "Turno de Hoy", "scheduled_count": "$count programados", - "tomorrow": "Mañana", + "tomorrow": "Ma\u00f1ana", "recommended_for_you": "Recomendado para Ti", "view_all": "Ver todo" }, "empty_states": { "no_shifts_today": "No hay turnos programados para hoy", - "find_shifts_cta": "Buscar turnos →", - "no_shifts_tomorrow": "No hay turnos para mañana", + "find_shifts_cta": "Buscar turnos \u2192", + "no_shifts_tomorrow": "No hay turnos para ma\u00f1ana", "no_recommended_shifts": "No hay turnos recomendados" }, "pending_payment": { @@ -462,8 +462,8 @@ "amount": "$amount" }, "recommended_card": { - "act_now": "• ACTÚA AHORA", - "one_day": "Un Día", + "act_now": "\u2022 ACT\u00daA AHORA", + "one_day": "Un D\u00eda", "today": "Hoy", "applied_for": "Postulado para $title", "time_range": "$start - $end" @@ -473,7 +473,7 @@ "view_all": "Ver todo", "hours_label": "horas", "items": { - "sick_days": "Días de Enfermedad", + "sick_days": "D\u00edas de Enfermedad", "vacation": "Vacaciones", "holidays": "Festivos" } @@ -481,20 +481,20 @@ "auto_match": { "title": "Auto-Match", "finding_shifts": "Buscando turnos para ti", - "get_matched": "Sé emparejado automáticamente", + "get_matched": "S\u00e9 emparejado autom\u00e1ticamente", "matching_based_on": "Emparejamiento basado en:", "chips": { - "location": "Ubicación", + "location": "Ubicaci\u00f3n", "availability": "Disponibilidad", "skills": "Habilidades" } }, "improve": { - "title": "Mejórate a ti mismo", + "title": "Mej\u00f3rate a ti mismo", "items": { "training": { - "title": "Sección de Entrenamiento", - "description": "Mejora tus habilidades y obtén certificaciones.", + "title": "Secci\u00f3n de Entrenamiento", + "description": "Mejora tus habilidades y obt\u00e9n certificaciones.", "page": "/krow-university" }, "podcast": { @@ -505,7 +505,7 @@ } }, "more_ways": { - "title": "Más Formas de Usar Krow", + "title": "M\u00e1s Formas de Usar Krow", "items": { "benefits": { "title": "Beneficios de Krow", @@ -521,21 +521,21 @@ "profile": { "header": { "title": "Perfil", - "sign_out": "CERRAR SESIÓN" + "sign_out": "CERRAR SESI\u00d3N" }, "reliability_stats": { "shifts": "Turnos", - "rating": "Calificación", + "rating": "Calificaci\u00f3n", "on_time": "A Tiempo", "no_shows": "Faltas", "cancellations": "Cancel." }, "reliability_score": { - "title": "Puntuación de Confiabilidad", - "description": "Mantén tu puntuación por encima del 45% para continuar aceptando turnos." + "title": "Puntuaci\u00f3n de Confiabilidad", + "description": "Mant\u00e9n tu puntuaci\u00f3n por encima del 45% para continuar aceptando turnos." }, "sections": { - "onboarding": "INCORPORACIÓN", + "onboarding": "INCORPORACI\u00d3N", "compliance": "CUMPLIMIENTO", "level_up": "MEJORAR NIVEL", "finance": "FINANZAS", @@ -543,10 +543,10 @@ "settings": "AJUSTES" }, "menu_items": { - "personal_info": "Información Personal", + "personal_info": "Informaci\u00f3n Personal", "emergency_contact": "Contacto de Emergencia", "emergency_contact_page": { - "save_success": "Contactos de emergencia guardados con éxito", + "save_success": "Contactos de emergencia guardados con \u00e9xito", "save_continue": "Guardar y Continuar" }, "experience": "Experiencia", @@ -556,7 +556,7 @@ "tax_forms": "Formularios Fiscales", "krow_university": "Krow University", "trainings": "Capacitaciones", - "leaderboard": "Tabla de Clasificación", + "leaderboard": "Tabla de Clasificaci\u00f3n", "bank_account": "Cuenta Bancaria", "payments": "Pagos", "timecard": "Tarjeta de Tiempo", @@ -570,14 +570,14 @@ "linked_accounts": "Cuentas Vinculadas", "add_account": "Agregar Cuenta Bancaria", "secure_title": "Seguro y Cifrado", - "secure_subtitle": "Su información bancaria está cifrada y almacenada de forma segura. Nunca compartimos sus detalles.", + "secure_subtitle": "Su informaci\u00f3n bancaria est\u00e1 cifrada y almacenada de forma segura. Nunca compartimos sus detalles.", "add_new_account": "Agregar Nueva Cuenta", "bank_name": "Nombre del Banco", "bank_hint": "Ingrese nombre del banco", - "routing_number": "Número de Ruta", - "routing_hint": "9 dígitos", - "account_number": "Número de Cuenta", - "account_hint": "Ingrese número de cuenta", + "routing_number": "N\u00famero de Ruta", + "routing_hint": "9 d\u00edgitos", + "account_number": "N\u00famero de Cuenta", + "account_hint": "Ingrese n\u00famero de cuenta", "account_type": "Tipo de Cuenta", "checking": "CORRIENTE", "savings": "AHORROS", @@ -585,40 +585,40 @@ "save": "Guardar", "primary": "Principal", "account_ending": "Termina en $last4", - "account_added_success": "¡Cuenta bancaria agregada exitosamente!" + "account_added_success": "\u00a1Cuenta bancaria agregada exitosamente!" }, "logout": { - "button": "Cerrar Sesión" + "button": "Cerrar Sesi\u00f3n" } }, "onboarding": { "personal_info": { - "title": "Información Personal", + "title": "Informaci\u00f3n Personal", "change_photo_hint": "Toca para cambiar foto", "full_name_label": "Nombre Completo", - "email_label": "Correo Electrónico", - "phone_label": "Número de Teléfono", + "email_label": "Correo Electr\u00f3nico", + "phone_label": "N\u00famero de Tel\u00e9fono", "phone_hint": "+1 (555) 000-0000", - "bio_label": "Biografía", - "bio_hint": "Cuéntales a los clientes sobre ti...", + "bio_label": "Biograf\u00eda", + "bio_hint": "Cu\u00e9ntales a los clientes sobre ti...", "languages_label": "Idiomas", - "languages_hint": "Inglés, Español, Francés...", + "languages_hint": "Ingl\u00e9s, Espa\u00f1ol, Franc\u00e9s...", "locations_label": "Ubicaciones Preferidas", "locations_hint": "Centro, Midtown, Brooklyn...", "locations_summary_none": "No configurado", "save_button": "Guardar Cambios", - "save_success": "Información personal guardada exitosamente", + "save_success": "Informaci\u00f3n personal guardada exitosamente", "preferred_locations": { "title": "Ubicaciones Preferidas", - "description": "Elige hasta 5 ubicaciones en los EE.UU. donde prefieres trabajar. Priorizaremos turnos cerca de estas áreas.", - "search_hint": "Buscar una ciudad o área...", + "description": "Elige hasta 5 ubicaciones en los EE.UU. donde prefieres trabajar. Priorizaremos turnos cerca de estas \u00e1reas.", + "search_hint": "Buscar una ciudad o \u00e1rea...", "added_label": "TUS UBICACIONES", - "max_reached": "Has alcanzado el máximo de 5 ubicaciones", - "min_hint": "Agrega al menos 1 ubicación preferida", + "max_reached": "Has alcanzado el m\u00e1ximo de 5 ubicaciones", + "min_hint": "Agrega al menos 1 ubicaci\u00f3n preferida", "save_button": "Guardar Ubicaciones", "save_success": "Ubicaciones preferidas guardadas", - "remove_tooltip": "Eliminar ubicación", - "empty_state": "Aún no has agregado ubicaciones.\nBusca arriba para agregar tus áreas de trabajo preferidas." + "remove_tooltip": "Eliminar ubicaci\u00f3n", + "empty_state": "A\u00fan no has agregado ubicaciones.\nBusca arriba para agregar tus \u00e1reas de trabajo preferidas." } }, "experience": { @@ -626,14 +626,14 @@ "industries_title": "Industrias", "industries_subtitle": "Seleccione las industrias en las que tiene experiencia", "skills_title": "Habilidades", - "skills_subtitle": "Seleccione sus habilidades o añada personalizadas", + "skills_subtitle": "Seleccione sus habilidades o a\u00f1ada personalizadas", "custom_skills_title": "Habilidades personalizadas:", - "custom_skill_hint": "Añadir habilidad...", + "custom_skill_hint": "A\u00f1adir habilidad...", "save_button": "Guardar y continuar", "industries": { - "hospitality": "Hotelería", + "hospitality": "Hoteler\u00eda", "food_service": "Servicio de alimentos", - "warehouse": "Almacén", + "warehouse": "Almac\u00e9n", "events": "Eventos", "retail": "Venta al por menor", "healthcare": "Cuidado de la salud", @@ -643,8 +643,8 @@ "food_service": "Servicio de alimentos", "bartending": "Bartending", "event_setup": "Montaje de eventos", - "hospitality": "Hotelería", - "warehouse": "Almacén", + "hospitality": "Hoteler\u00eda", + "warehouse": "Almac\u00e9n", "customer_service": "Servicio al cliente", "cleaning": "Limpieza", "security": "Seguridad", @@ -653,7 +653,7 @@ "cashier": "Cajero", "server": "Mesero", "barista": "Barista", - "host_hostess": "Anfitrión/Anfitriona", + "host_hostess": "Anfitri\u00f3n/Anfitriona", "busser": "Ayudante de mesero", "driving": "Conducir" } @@ -664,9 +664,9 @@ "your_activity": "Su actividad", "selected_shift_badge": "TURNO SELECCIONADO", "today_shift_badge": "TURNO DE HOY", - "early_title": "¡Ha llegado temprano!", + "early_title": "\u00a1Ha llegado temprano!", "check_in_at": "Entrada disponible a las $time", - "shift_completed": "¡Turno completado!", + "shift_completed": "\u00a1Turno completado!", "great_work": "Buen trabajo hoy", "no_shifts_today": "No hay turnos confirmados para hoy", "accept_shift_cta": "Acepte un turno para registrar su entrada", @@ -677,13 +677,13 @@ "scanned_title": "NFC escaneado", "ready_to_scan": "Listo para escanear", "processing": "Verificando etiqueta...", - "scan_instruction": "Mantenga su teléfono cerca de la etiqueta NFC en el lugar para registrarse.", - "please_wait": "Espere un momento, estamos verificando su ubicación.", + "scan_instruction": "Mantenga su tel\u00e9fono cerca de la etiqueta NFC en el lugar para registrarse.", + "please_wait": "Espere un momento, estamos verificando su ubicaci\u00f3n.", "tap_to_scan": "Tocar para escanear (Simulado)" }, "commute": { - "enable_title": "¿Activar seguimiento de viaje?", - "enable_desc": "Comparta su ubicación 1 hora antes del turno para que su gerente sepa que está en camino.", + "enable_title": "\u00bfActivar seguimiento de viaje?", + "enable_desc": "Comparta su ubicaci\u00f3n 1 hora antes del turno para que su gerente sepa que est\u00e1 en camino.", "not_now": "Ahora no", "enable": "Activar", "on_my_way": "En camino", @@ -693,10 +693,10 @@ "distance_to_site": "Distancia al sitio", "estimated_arrival": "Llegada estimada", "eta_label": "$min min", - "locked_desc": "La mayoría de las funciones de la aplicación están bloqueadas mientras el modo de viaje está activo. Podrá registrar su entrada una vez que llegue.", + "locked_desc": "La mayor\u00eda de las funciones de la aplicaci\u00f3n est\u00e1n bloqueadas mientras el modo de viaje est\u00e1 activo. Podr\u00e1 registrar su entrada una vez que llegue.", "turn_off": "Desactivar modo de viaje", - "arrived_title": "¡Has llegado! 🎉", - "arrived_desc": "Estás en el lugar del turno. ¿Listo para registrar tu entrada?" + "arrived_title": "\u00a1Has llegado! \ud83c\udf89", + "arrived_desc": "Est\u00e1s en el lugar del turno. \u00bfListo para registrar tu entrada?" }, "swipe": { "checking_out": "Registrando salida...", @@ -705,58 +705,58 @@ "nfc_checkin": "NFC Entrada", "swipe_checkout": "Deslizar para registrar salida", "swipe_checkin": "Deslizar para registrar entrada", - "checkout_complete": "¡Salida registrada!", - "checkin_complete": "¡Entrada registrada!" + "checkout_complete": "\u00a1Salida registrada!", + "checkin_complete": "\u00a1Entrada registrada!" }, "lunch_break": { - "title": "¿Tomaste un\nalmuerzo?", + "title": "\u00bfTomaste un\nalmuerzo?", "no": "No", - "yes": "Sí", - "when_title": "¿Cuándo almorzaste?", + "yes": "S\u00ed", + "when_title": "\u00bfCu\u00e1ndo almorzaste?", "start": "Inicio", "end": "Fin", - "why_no_lunch": "¿Por qué no almorzaste?", + "why_no_lunch": "\u00bfPor qu\u00e9 no almorzaste?", "reasons": [ "Flujos de trabajo impredecibles", - "Mala gestión del tiempo", + "Mala gesti\u00f3n del tiempo", "Falta de cobertura o poco personal", - "No hay área de almuerzo", + "No hay \u00e1rea de almuerzo", "Otro (especifique)" ], "additional_notes": "Notas adicionales", - "notes_placeholder": "Añade cualquier detalle...", + "notes_placeholder": "A\u00f1ade cualquier detalle...", "next": "Siguiente", "submit": "Enviar", - "success_title": "¡Descanso registrado!", + "success_title": "\u00a1Descanso registrado!", "close": "Cerrar" } }, "availability": { "title": "Mi disponibilidad", - "quick_set_title": "Establecer disponibilidad rápida", + "quick_set_title": "Establecer disponibilidad r\u00e1pida", "all_week": "Toda la semana", - "weekdays": "Días laborables", + "weekdays": "D\u00edas laborables", "weekends": "Fines de semana", "clear_all": "Borrar todo", - "available_status": "Está disponible", + "available_status": "Est\u00e1 disponible", "not_available_status": "No disponible", "auto_match_title": "Auto-Match usa su disponibilidad", - "auto_match_description": "Cuando esté activado, solo se le asignarán turnos durante sus horarios disponibles." + "auto_match_description": "Cuando est\u00e9 activado, solo se le asignar\u00e1n turnos durante sus horarios disponibles." } }, "staff_compliance": { "tax_forms": { "w4": { "title": "Formulario W-4", - "subtitle": "Certificado de Retención del Empleado", - "submitted_title": "¡Formulario W-4 enviado!", - "submitted_desc": "Su certificado de retención ha sido enviado a su empleador.", + "subtitle": "Certificado de Retenci\u00f3n del Empleado", + "submitted_title": "\u00a1Formulario W-4 enviado!", + "submitted_desc": "Su certificado de retenci\u00f3n ha sido enviado a su empleador.", "back_to_docs": "Volver a Documentos", "step_label": "Paso $current de $total", "steps": { - "personal": "Información Personal", - "filing": "Estado Civil para Efectos de la Declaración", - "multiple_jobs": "Múltiples Trabajos", + "personal": "Informaci\u00f3n Personal", + "filing": "Estado Civil para Efectos de la Declaraci\u00f3n", + "multiple_jobs": "M\u00faltiples Trabajos", "dependents": "Dependientes", "adjustments": "Otros Ajustes", "review": "Revisar y Firmar" @@ -764,56 +764,56 @@ "fields": { "first_name": "Nombre *", "last_name": "Apellido *", - "ssn": "Número de Seguro Social *", - "address": "Dirección *", - "city_state_zip": "Ciudad, Estado, Código Postal", + "ssn": "N\u00famero de Seguro Social *", + "address": "Direcci\u00f3n *", + "city_state_zip": "Ciudad, Estado, C\u00f3digo Postal", "placeholder_john": "Juan", - "placeholder_smith": "Pérez", + "placeholder_smith": "P\u00e9rez", "placeholder_ssn": "XXX-XX-XXXX", "placeholder_address": "Calle Principal 123", - "placeholder_csz": "Ciudad de México, CDMX 01000", - "filing_info": "Su estado civil determina su deducción estándar y tasas de impuestos.", - "single": "Soltero o Casado que presenta la declaración por separado", - "married": "Casado que presenta una declaración conjunta o Cónyuge sobreviviente calificado", + "placeholder_csz": "Ciudad de M\u00e9xico, CDMX 01000", + "filing_info": "Su estado civil determina su deducci\u00f3n est\u00e1ndar y tasas de impuestos.", + "single": "Soltero o Casado que presenta la declaraci\u00f3n por separado", + "married": "Casado que presenta una declaraci\u00f3n conjunta o C\u00f3nyuge sobreviviente calificado", "head": "Jefe de familia", - "head_desc": "Marque solo si es soltero y paga más de la mitad de los costos de mantenimiento de un hogar", - "multiple_jobs_title": "¿Cuándo completar este paso?", - "multiple_jobs_desc": "Complete este paso solo si tiene más de un trabajo a la vez, o si está casado y presenta una declaración conjunta y su cónyuge también trabaja.", - "multiple_jobs_check": "Tengo múltiples trabajos o mi cónyuge trabaja", + "head_desc": "Marque solo si es soltero y paga m\u00e1s de la mitad de los costos de mantenimiento de un hogar", + "multiple_jobs_title": "\u00bfCu\u00e1ndo completar este paso?", + "multiple_jobs_desc": "Complete este paso solo si tiene m\u00e1s de un trabajo a la vez, o si est\u00e1 casado y presenta una declaraci\u00f3n conjunta y su c\u00f3nyuge tambi\u00e9n trabaja.", + "multiple_jobs_check": "Tengo m\u00faltiples trabajos o mi c\u00f3nyuge trabaja", "two_jobs_desc": "Marque esta casilla si solo hay dos trabajos en total", "multiple_jobs_not_apply": "Si esto no se aplica, puede continuar al siguiente paso", - "dependents_info": "Si su ingreso total será de $ 200,000 o menos ($ 400,000 si está casado y presenta una declaración conjunta), puede reclamar créditos por dependientes.", - "children_under_17": "Hijos calificados menores de 17 años", + "dependents_info": "Si su ingreso total ser\u00e1 de $ 200,000 o menos ($ 400,000 si est\u00e1 casado y presenta una declaraci\u00f3n conjunta), puede reclamar cr\u00e9ditos por dependientes.", + "children_under_17": "Hijos calificados menores de 17 a\u00f1os", "children_each": "$ 2,000 cada uno", "other_dependents": "Otros dependientes", "other_each": "$ 500 cada uno", - "total_credits": "Créditos totales (Paso 3)", + "total_credits": "Cr\u00e9ditos totales (Paso 3)", "adjustments_info": "Estos ajustes son opcionales. Puede omitirlos si no se aplican.", "other_income": "4(a) Otros ingresos (no provenientes de trabajos)", - "other_income_desc": "Incluya intereses, dividendos, ingresos de jubilación", + "other_income_desc": "Incluya intereses, dividendos, ingresos de jubilaci\u00f3n", "deductions": "4(b) Deducciones", - "deductions_desc": "Si espera reclamar deducciones distintas de la deducción estándar", - "extra_withholding": "4(c) Retención adicional", - "extra_withholding_desc": "Cualquier impuesto adicional que desee que se le retenga en cada período de pago", + "deductions_desc": "Si espera reclamar deducciones distintas de la deducci\u00f3n est\u00e1ndar", + "extra_withholding": "4(c) Retenci\u00f3n adicional", + "extra_withholding_desc": "Cualquier impuesto adicional que desee que se le retenga en cada per\u00edodo de pago", "summary_title": "Su Resumen de W-4", "summary_name": "Nombre", "summary_ssn": "SSN", "summary_filing": "Estado Civil", - "summary_credits": "Créditos", - "perjury_declaration": "Bajo pena de perjurio, declaro que este certificado, según mi leal saber y entender, es verdadero, correcto y completo.", + "summary_credits": "Cr\u00e9ditos", + "perjury_declaration": "Bajo pena de perjurio, declaro que este certificado, seg\u00fan mi leal saber y entender, es verdadero, correcto y completo.", "signature_label": "Firma (escriba su nombre completo) *", "signature_hint": "Escriba su nombre completo", "date_label": "Fecha", "status_single": "Soltero/a", "status_married": "Casado/a", "status_head": "Cabeza de familia", - "back": "Atrás", + "back": "Atr\u00e1s", "continue": "Continuar", "submit": "Enviar Formulario", "step_counter": "Paso {current} de {total}", "hints": { "first_name": "Juan", - "last_name": "Pérez", + "last_name": "P\u00e9rez", "ssn": "XXX-XX-XXXX", "zero": "$ 0", "email": "juan.perez@ejemplo.com", @@ -823,22 +823,22 @@ }, "i9": { "title": "Formulario I-9", - "subtitle": "Verificación de Elegibilidad de Empleo", - "submitted_title": "¡Formulario I-9 enviado!", - "submitted_desc": "Su verificación de elegibilidad de empleo ha sido enviada.", - "back": "Atrás", + "subtitle": "Verificaci\u00f3n de Elegibilidad de Empleo", + "submitted_title": "\u00a1Formulario I-9 enviado!", + "submitted_desc": "Su verificaci\u00f3n de elegibilidad de empleo ha sido enviada.", + "back": "Atr\u00e1s", "continue": "Continuar", "submit": "Enviar Formulario", "step_label": "Paso $current de $total", "steps": { - "personal": "Información Personal", + "personal": "Informaci\u00f3n Personal", "personal_sub": "Nombre y detalles de contacto", - "address": "Dirección", - "address_sub": "Su dirección actual", - "citizenship": "Estado de Ciudadanía", - "citizenship_sub": "Verificación de autorización de trabajo", + "address": "Direcci\u00f3n", + "address_sub": "Su direcci\u00f3n actual", + "citizenship": "Estado de Ciudadan\u00eda", + "citizenship_sub": "Verificaci\u00f3n de autorizaci\u00f3n de trabajo", "review": "Revisar y Firmar", - "review_sub": "Confirme su información" + "review_sub": "Confirme su informaci\u00f3n" }, "fields": { "first_name": "Nombre *", @@ -847,41 +847,41 @@ "other_last_names": "Otros apellidos", "maiden_name": "Apellido de soltera (si hay)", "dob": "Fecha de Nacimiento *", - "ssn": "Número de Seguro Social *", - "email": "Correo electrónico", - "phone": "Número de teléfono", - "address_long": "Dirección (Número y nombre de la calle) *", - "apt": "Núm. de apartamento", + "ssn": "N\u00famero de Seguro Social *", + "email": "Correo electr\u00f3nico", + "phone": "N\u00famero de tel\u00e9fono", + "address_long": "Direcci\u00f3n (N\u00famero y nombre de la calle) *", + "apt": "N\u00fam. de apartamento", "city": "Ciudad o Pueblo *", "state": "Estado *", - "zip": "Código Postal *", + "zip": "C\u00f3digo Postal *", "attestation": "Doy fe, bajo pena de perjurio, de que soy (marque una de las siguientes casillas):", "citizen": "1. Ciudadano de los Estados Unidos", "noncitizen": "2. Nacional no ciudadano de los Estados Unidos", "permanent_resident": "3. Residente permanente legal", - "uscis_number_label": "Número USCIS", + "uscis_number_label": "N\u00famero USCIS", "alien": "4. Un extranjero autorizado para trabajar", - "admission_number": "Número USCIS/Admisión", - "passport": "Número de pasaporte extranjero", - "country": "País de emisión", + "admission_number": "N\u00famero USCIS/Admisi\u00f3n", + "passport": "N\u00famero de pasaporte extranjero", + "country": "Pa\u00eds de emisi\u00f3n", "summary_title": "Resumen", "summary_name": "Nombre", - "summary_address": "Dirección", + "summary_address": "Direcci\u00f3n", "summary_ssn": "SSN", - "summary_citizenship": "Ciudadanía", + "summary_citizenship": "Ciudadan\u00eda", "status_us_citizen": "Ciudadano de los EE. UU.", "status_noncitizen": "Nacional no ciudadano", "status_permanent_resident": "Residente permanente", "status_alien": "Extranjero autorizado para trabajar", "status_unknown": "Desconocido", - "preparer": "Utilicé un preparador o traductor", - "warning": "Soy consciente de que la ley federal prevé penas de prisión y/o multas por declaraciones falsas o uso de documentos falsos en relación con la cumplimentación de este formulario.", + "preparer": "Utilic\u00e9 un preparador o traductor", + "warning": "Soy consciente de que la ley federal prev\u00e9 penas de prisi\u00f3n y/o multas por declaraciones falsas o uso de documentos falsos en relaci\u00f3n con la cumplimentaci\u00f3n de este formulario.", "signature_label": "Firma (escriba su nombre completo) *", "signature_hint": "Escriba su nombre completo", "date_label": "Fecha", "hints": { "first_name": "Juan", - "last_name": "Pérez", + "last_name": "P\u00e9rez", "middle_initial": "J", "dob": "MM/DD/YYYY", "ssn": "XXX-XX-XXXX", @@ -900,7 +900,7 @@ "staff_documents": { "title": "Documentos", "verification_card": { - "title": "Verificación de Documentos", + "title": "Verificaci\u00f3n de Documentos", "progress": "$completed/$total Completado" }, "list": { @@ -925,16 +925,16 @@ "active": "Cumplimiento Activo" }, "card": { - "expires_in_days": "Expira en $days días - Renovar ahora", + "expires_in_days": "Expira en $days d\u00edas - Renovar ahora", "expired": "Expirado - Renovar ahora", "verified": "Verificado", "expiring_soon": "Expira Pronto", "exp": "Exp: $date", "upload_button": "Subir Certificado", - "edit_expiry": "Editar Fecha de Expiración", + "edit_expiry": "Editar Fecha de Expiraci\u00f3n", "remove": "Eliminar Certificado", "renew": "Renovar", - "opened_snackbar": "Certificado abierto en nueva pestaña" + "opened_snackbar": "Certificado abierto en nueva pesta\u00f1a" }, "add_more": { "title": "Agregar Otro Certificado", @@ -942,7 +942,7 @@ }, "upload_modal": { "title": "Subir Certificado", - "expiry_label": "Fecha de Expiración (Opcional)", + "expiry_label": "Fecha de Expiraci\u00f3n (Opcional)", "select_date": "Seleccionar fecha", "upload_file": "Subir Archivo", "drag_drop": "Arrastra y suelta o haz clic para subir", @@ -951,8 +951,8 @@ "save": "Guardar Certificado" }, "delete_modal": { - "title": "¿Eliminar Certificado?", - "message": "Esta acción no se puede deshacer.", + "title": "\u00bfEliminar Certificado?", + "message": "Esta acci\u00f3n no se puede deshacer.", "cancel": "Cancelar", "confirm": "Eliminar" } @@ -961,22 +961,22 @@ "title": "Vestimenta", "info_card": { "title": "Tu Vestuario", - "description": "Selecciona los artículos de vestimenta que posees. Esto nos ayuda a asignarte turnos que se ajusten a tu vestuario." + "description": "Selecciona los art\u00edculos de vestimenta que posees. Esto nos ayuda a asignarte turnos que se ajusten a tu vestuario." }, "status": { "required": "REQUERIDO", - "add_photo": "Añadir Foto", - "added": "Añadido", - "pending": "⏳ Verificación pendiente" + "add_photo": "A\u00f1adir Foto", + "added": "A\u00f1adido", + "pending": "\u23f3 Verificaci\u00f3n pendiente" }, - "attestation": "Certifico que poseo estos artículos y los usaré en mis turnos. Entiendo que los artículos están pendientes de verificación por el gerente en mi primer turno.", + "attestation": "Certifico que poseo estos art\u00edculos y los usar\u00e9 en mis turnos. Entiendo que los art\u00edculos est\u00e1n pendientes de verificaci\u00f3n por el gerente en mi primer turno.", "actions": { "save": "Guardar Vestimenta" }, "validation": { - "select_required": "✓ Seleccionar todos los artículos requeridos", - "upload_required": "✓ Subir fotos de artículos requeridos", - "accept_attestation": "✓ Aceptar certificación" + "select_required": "\u2713 Seleccionar todos los art\u00edculos requeridos", + "upload_required": "\u2713 Subir fotos de art\u00edculos requeridos", + "accept_attestation": "\u2713 Aceptar certificaci\u00f3n" } }, "staff_shifts": { @@ -994,17 +994,17 @@ }, "filter": { "all": "Todos los Empleos", - "one_day": "Un Día", - "multi_day": "Multidía", + "one_day": "Un D\u00eda", + "multi_day": "Multid\u00eda", "long_term": "Largo Plazo" }, "status": { "confirmed": "CONFIRMADO", - "act_now": "ACTÚA AHORA", + "act_now": "ACT\u00daA AHORA", "swap_requested": "INTERCAMBIO SOLICITADO", "completed": "COMPLETADO", - "no_show": "NO ASISTIÓ", - "pending_warning": "Por favor confirma la asignación" + "no_show": "NO ASISTI\u00d3", + "pending_warning": "Por favor confirma la asignaci\u00f3n" }, "action": { "decline": "Rechazar", @@ -1013,7 +1013,7 @@ }, "details": { "additional": "DETALLES ADICIONALES", - "days": "$days Días", + "days": "$days D\u00edas", "exp_total": "(total est. \\$$amount)", "pending_time": "Pendiente hace $time" }, @@ -1028,12 +1028,12 @@ "start_time": "HORA DE INICIO", "end_time": "HORA DE FIN", "base_rate": "Tarifa base", - "duration": "Duración", + "duration": "Duraci\u00f3n", "est_total": "Total est.", "hours_label": "$count horas", - "location": "UBICACIÓN", + "location": "UBICACI\u00d3N", "tbd": "TBD", - "get_direction": "Obtener dirección", + "get_direction": "Obtener direcci\u00f3n", "break_title": "DESCANSO", "paid": "Pagado", "unpaid": "No pagado", @@ -1041,7 +1041,7 @@ "hourly_rate": "Tarifa por hora", "hours": "Horas", "open_in_maps": "Abrir en Mapas", - "job_description": "DESCRIPCIÓN DEL TRABAJO", + "job_description": "DESCRIPCI\u00d3N DEL TRABAJO", "cancel_shift": "CANCELAR TURNO", "clock_in": "ENTRADA", "decline": "RECHAZAR", @@ -1049,22 +1049,22 @@ "apply_now": "SOLICITAR AHORA", "book_dialog": { "title": "Reservar turno", - "message": "¿Desea reservar este turno al instante?" + "message": "\u00bfDesea reservar este turno al instante?" }, "decline_dialog": { "title": "Rechazar turno", - "message": "¿Está seguro de que desea rechazar este turno? Se ocultará de sus trabajos disponibles." + "message": "\u00bfEst\u00e1 seguro de que desea rechazar este turno? Se ocultar\u00e1 de sus trabajos disponibles." }, "cancel_dialog": { "title": "Cancelar turno", - "message": "¿Está seguro de que desea cancelar este turno?" + "message": "\u00bfEst\u00e1 seguro de que desea cancelar este turno?" }, "applying_dialog": { "title": "Solicitando" } }, "card": { - "just_now": "Recién", + "just_now": "Reci\u00e9n", "assigned": "Asignado hace $time", "accept_shift": "Aceptar turno", "decline_shift": "Rechazar turno" @@ -1072,31 +1072,41 @@ "my_shifts_tab": { "confirm_dialog": { "title": "Aceptar Turno", - "message": "¿Estás seguro de que quieres aceptar este turno?", - "success": "¡Turno confirmado!" + "message": "\u00bfEst\u00e1s seguro de que quieres aceptar este turno?", + "success": "\u00a1Turno confirmado!" }, "decline_dialog": { "title": "Rechazar Turno", - "message": "¿Estás seguro de que quieres rechazar este turno? Esta acción no se puede deshacer.", + "message": "\u00bfEst\u00e1s seguro de que quieres rechazar este turno? Esta acci\u00f3n no se puede deshacer.", "success": "Turno rechazado." }, "sections": { - "awaiting": "Esperando Confirmación", + "awaiting": "Esperando Confirmaci\u00f3n", "cancelled": "Turnos Cancelados", "confirmed": "Turnos Confirmados" }, "empty": { "title": "Sin turnos esta semana", - "subtitle": "Intenta buscar nuevos trabajos en la pestaña Buscar" + "subtitle": "Intenta buscar nuevos trabajos en la pesta\u00f1a Buscar" }, "date": { "today": "Hoy", - "tomorrow": "Mañana" + "tomorrow": "Ma\u00f1ana" }, "card": { "cancelled": "CANCELADO", - "compensation": "• Compensación de 4h" + "compensation": "\u2022 Compensaci\u00f3n de 4h" } + }, + "find_shifts": { + "search_hint": "Buscar trabajos, ubicaci\u00f3n...", + "filter_all": "Todos", + "filter_one_day": "Un d\u00eda", + "filter_multi_day": "Varios d\u00edas", + "filter_long_term": "Largo plazo", + "no_jobs_title": "No hay trabajos disponibles", + "no_jobs_subtitle": "Vuelve m\u00e1s tarde", + "application_submitted": "\u00a1Solicitud de turno enviada!" } }, "staff_time_card": { @@ -1116,34 +1126,34 @@ }, "errors": { "auth": { - "invalid_credentials": "El correo electrónico o la contraseña que ingresaste es incorrecta.", - "account_exists": "Ya existe una cuenta con este correo electrónico. Intenta iniciar sesión.", - "session_expired": "Tu sesión ha expirado. Por favor, inicia sesión de nuevo.", - "user_not_found": "No pudimos encontrar tu cuenta. Por favor, verifica tu correo electrónico e intenta de nuevo.", - "unauthorized_app": "Esta cuenta no está autorizada para esta aplicación.", - "weak_password": "Por favor, elige una contraseña más segura con al menos 8 caracteres.", + "invalid_credentials": "El correo electr\u00f3nico o la contrase\u00f1a que ingresaste es incorrecta.", + "account_exists": "Ya existe una cuenta con este correo electr\u00f3nico. Intenta iniciar sesi\u00f3n.", + "session_expired": "Tu sesi\u00f3n ha expirado. Por favor, inicia sesi\u00f3n de nuevo.", + "user_not_found": "No pudimos encontrar tu cuenta. Por favor, verifica tu correo electr\u00f3nico e intenta de nuevo.", + "unauthorized_app": "Esta cuenta no est\u00e1 autorizada para esta aplicaci\u00f3n.", + "weak_password": "Por favor, elige una contrase\u00f1a m\u00e1s segura con al menos 8 caracteres.", "sign_up_failed": "No pudimos crear tu cuenta. Por favor, intenta de nuevo.", - "sign_in_failed": "No pudimos iniciar sesión. Por favor, intenta de nuevo.", - "not_authenticated": "Por favor, inicia sesión para continuar.", - "passwords_dont_match": "Las contraseñas no coinciden", - "password_mismatch": "Este correo ya está registrado. Por favor, usa la contraseña correcta o toca 'Olvidé mi contraseña' para restablecerla.", - "google_only_account": "Este correo está registrado con Google. Por favor, usa 'Olvidé mi contraseña' para establecer una contraseña, luego intenta registrarte de nuevo con la misma información." + "sign_in_failed": "No pudimos iniciar sesi\u00f3n. Por favor, intenta de nuevo.", + "not_authenticated": "Por favor, inicia sesi\u00f3n para continuar.", + "passwords_dont_match": "Las contrase\u00f1as no coinciden", + "password_mismatch": "Este correo ya est\u00e1 registrado. Por favor, usa la contrase\u00f1a correcta o toca 'Olvid\u00e9 mi contrase\u00f1a' para restablecerla.", + "google_only_account": "Este correo est\u00e1 registrado con Google. Por favor, usa 'Olvid\u00e9 mi contrase\u00f1a' para establecer una contrase\u00f1a, luego intenta registrarte de nuevo con la misma informaci\u00f3n." }, "hub": { - "has_orders": "Este hub tiene órdenes activas y no puede ser eliminado.", + "has_orders": "Este hub tiene \u00f3rdenes activas y no puede ser eliminado.", "not_found": "El hub que buscas no existe.", "creation_failed": "No pudimos crear el hub. Por favor, intenta de nuevo." }, "order": { - "missing_hub": "Por favor, selecciona una ubicación para tu orden.", + "missing_hub": "Por favor, selecciona una ubicaci\u00f3n para tu orden.", "missing_vendor": "Por favor, selecciona un proveedor para tu orden.", "creation_failed": "No pudimos crear tu orden. Por favor, intenta de nuevo.", "shift_creation_failed": "No pudimos programar el turno. Por favor, intenta de nuevo.", - "missing_business": "No se pudo cargar tu perfil de empresa. Por favor, inicia sesión de nuevo." + "missing_business": "No se pudo cargar tu perfil de empresa. Por favor, inicia sesi\u00f3n de nuevo." }, "profile": { - "staff_not_found": "No se pudo cargar tu perfil. Por favor, inicia sesión de nuevo.", - "business_not_found": "No se pudo cargar tu perfil de empresa. Por favor, inicia sesión de nuevo.", + "staff_not_found": "No se pudo cargar tu perfil. Por favor, inicia sesi\u00f3n de nuevo.", + "business_not_found": "No se pudo cargar tu perfil de empresa. Por favor, inicia sesi\u00f3n de nuevo.", "update_failed": "No pudimos actualizar tu perfil. Por favor, intenta de nuevo." }, "shift": { @@ -1152,10 +1162,10 @@ "no_active_shift": "No tienes un turno activo para registrar salida." }, "generic": { - "unknown": "Algo salió mal. Por favor, intenta de nuevo.", - "no_connection": "Sin conexión a internet. Por favor, verifica tu red e intenta de nuevo.", - "server_error": "Error del servidor. Inténtalo de nuevo más tarde.", - "service_unavailable": "El servicio no está disponible actualmente." + "unknown": "Algo sali\u00f3 mal. Por favor, intenta de nuevo.", + "no_connection": "Sin conexi\u00f3n a internet. Por favor, verifica tu red e intenta de nuevo.", + "server_error": "Error del servidor. Int\u00e9ntalo de nuevo m\u00e1s tarde.", + "service_unavailable": "El servicio no est\u00e1 disponible actualmente." } }, "staff_privacy_security": { @@ -1167,13 +1177,13 @@ "subtitle": "Deja que los clientes vean tu perfil" }, "terms_of_service": { - "title": "Términos de Servicio" + "title": "T\u00e9rminos de Servicio" }, "privacy_policy": { - "title": "Política de Privacidad" + "title": "Pol\u00edtica de Privacidad" }, "success": { - "profile_visibility_updated": "¡Visibilidad del perfil actualizada exitosamente!" + "profile_visibility_updated": "\u00a1Visibilidad del perfil actualizada exitosamente!" } }, "staff_faqs": { @@ -1184,19 +1194,19 @@ }, "success": { "hub": { - "created": "¡Hub creado exitosamente!", - "updated": "¡Hub actualizado exitosamente!", - "deleted": "¡Hub eliminado exitosamente!", - "nfc_assigned": "¡Etiqueta NFC asignada exitosamente!" + "created": "\u00a1Hub creado exitosamente!", + "updated": "\u00a1Hub actualizado exitosamente!", + "deleted": "\u00a1Hub eliminado exitosamente!", + "nfc_assigned": "\u00a1Etiqueta NFC asignada exitosamente!" }, "order": { - "created": "¡Orden creada exitosamente!" + "created": "\u00a1Orden creada exitosamente!" }, "profile": { - "updated": "¡Perfil actualizado con éxito!" + "updated": "\u00a1Perfil actualizado con \u00e9xito!" }, "availability": { - "updated": "Disponibilidad actualizada con éxito" + "updated": "Disponibilidad actualizada con \u00e9xito" } }, "client_reports": { @@ -1210,7 +1220,7 @@ "metrics": { "total_hrs": { "label": "Total de Horas", - "badge": "Este período" + "badge": "Este per\u00edodo" }, "ot_hours": { "label": "Horas Extra", @@ -1218,11 +1228,11 @@ }, "total_spend": { "label": "Gasto Total", - "badge": "↓ 8% vs semana pasada" + "badge": "\u2193 8% vs semana pasada" }, "fill_rate": { "label": "Tasa de Cobertura", - "badge": "↑ 2% de mejora" + "badge": "\u2191 2% de mejora" }, "avg_fill_time": { "label": "Tiempo Promedio de Llenado", @@ -1234,15 +1244,15 @@ } }, "quick_reports": { - "title": "Informes Rápidos", + "title": "Informes R\u00e1pidos", "export_all": "Exportar Todo", - "two_click_export": "Exportación en 2 clics", + "two_click_export": "Exportaci\u00f3n en 2 clics", "cards": { "daily_ops": "Informe de Ops Diarias", "spend": "Informe de Gastos", "coverage": "Informe de Cobertura", "no_show": "Informe de Faltas", - "forecast": "Informe de Previsión", + "forecast": "Informe de Previsi\u00f3n", "performance": "Informe de Rendimiento" } }, @@ -1281,45 +1291,45 @@ "completed": "Completado" }, "placeholders": { - "export_message": "Exportando Informe de Ops Diarias (Marcador de posición)" + "export_message": "Exportando Informe de Ops Diarias (Marcador de posici\u00f3n)" } }, "spend_report": { "title": "Informe de Gastos", - "subtitle": "Análisis y desglose de costos", + "subtitle": "An\u00e1lisis y desglose de costos", "summary": { "total_spend": "Gasto Total", "avg_daily": "Promedio Diario", "this_week": "Esta semana", - "per_day": "Por día" + "per_day": "Por d\u00eda" }, "chart_title": "Tendencia de Gasto Diario", "charts": { "mon": "Lun", "tue": "Mar", - "wed": "Mié", + "wed": "Mi\u00e9", "thu": "Jue", "fri": "Vie", - "sat": "Sáb", + "sat": "S\u00e1b", "sun": "Dom" }, "spend_by_industry": "Gasto por Industria", "industries": { - "hospitality": "Hostelería", + "hospitality": "Hosteler\u00eda", "events": "Eventos", "retail": "Venta minorista" }, "percent_total": "$percent% del total", "no_industry_data": "No hay datos de la industria disponibles", "placeholders": { - "export_message": "Exportando Informe de Gastos (Marcador de posición)" + "export_message": "Exportando Informe de Gastos (Marcador de posici\u00f3n)" } }, "forecast_report": { - "title": "Informe de Previsión", - "subtitle": "Proyección próximas 4 semanas", + "title": "Informe de Previsi\u00f3n", + "subtitle": "Proyecci\u00f3n pr\u00f3ximas 4 semanas", "metrics": { - "four_week_forecast": "Previsión 4 Semanas", + "four_week_forecast": "Previsi\u00f3n 4 Semanas", "avg_weekly": "Promedio Semanal", "total_shifts": "Total de Turnos", "total_hours": "Total de Horas" @@ -1330,7 +1340,7 @@ "scheduled": "Programado", "worker_hours": "Horas de trabajo" }, - "chart_title": "Previsión de Gastos", + "chart_title": "Previsi\u00f3n de Gastos", "weekly_breakdown": { "title": "DESGLOSE SEMANAL", "week": "Semana $index", @@ -1343,14 +1353,14 @@ }, "empty_state": "No hay proyecciones disponibles", "placeholders": { - "export_message": "Exportando Informe de Previsión (Marcador de posición)" + "export_message": "Exportando Informe de Previsi\u00f3n (Marcador de posici\u00f3n)" } }, "performance_report": { "title": "Informe de Rendimiento", - "subtitle": "Métricas clave y comparativas", + "subtitle": "M\u00e9tricas clave y comparativas", "overall_score": { - "title": "Puntuación de Rendimiento General", + "title": "Puntuaci\u00f3n de Rendimiento General", "excellent": "Excelente", "good": "Bueno", "needs_work": "Necesita Mejorar" @@ -1358,25 +1368,25 @@ "kpis_title": "INDICADORES CLAVE DE RENDIMIENTO (KPI)", "kpis": { "fill_rate": "Tasa de Llenado", - "completion_rate": "Tasa de Finalización", + "completion_rate": "Tasa de Finalizaci\u00f3n", "on_time_rate": "Tasa de Puntualidad", "avg_fill_time": "Tiempo Promedio de Llenado", "target_prefix": "Objetivo: ", "target_hours": "$hours hrs", "target_percent": "$percent%", - "met": "✓ Cumplido", - "close": "→ Cerca", - "miss": "✗ Fallido" + "met": "\u2713 Cumplido", + "close": "\u2192 Cerca", + "miss": "\u2717 Fallido" }, - "additional_metrics_title": "MÉTRICAS ADICIONALES", + "additional_metrics_title": "M\u00c9TRICAS ADICIONALES", "additional_metrics": { "total_shifts": "Total de Turnos", "no_show_rate": "Tasa de Faltas", "worker_pool": "Grupo de Trabajadores", - "avg_rating": "Calificación Promedio" + "avg_rating": "Calificaci\u00f3n Promedio" }, "placeholders": { - "export_message": "Exportando Informe de Rendimiento (Marcador de posición)" + "export_message": "Exportando Informe de Rendimiento (Marcador de posici\u00f3n)" } }, "no_show_report": { @@ -1389,15 +1399,15 @@ }, "workers_list_title": "TRABAJADORES CON FALTAS", "no_show_count": "$count falta(s)", - "latest_incident": "Último incidente", + "latest_incident": "\u00daltimo incidente", "risks": { "high": "Riesgo Alto", "medium": "Riesgo Medio", "low": "Riesgo Bajo" }, - "empty_state": "No hay trabajadores señalados por faltas", + "empty_state": "No hay trabajadores se\u00f1alados por faltas", "placeholders": { - "export_message": "Exportando Informe de Faltas (Marcador de posición)" + "export_message": "Exportando Informe de Faltas (Marcador de posici\u00f3n)" } }, "coverage_report": { @@ -1408,7 +1418,7 @@ "full": "Completa", "needs_help": "Necesita Ayuda" }, - "next_7_days": "PRÓXIMOS 7 DÍAS", + "next_7_days": "PR\u00d3XIMOS 7 D\u00cdAS", "empty_state": "No hay turnos programados", "shift_item": { "confirmed_workers": "$confirmed/$needed trabajadores confirmados", @@ -1417,7 +1427,7 @@ "fully_staffed": "Totalmente cubierto" }, "placeholders": { - "export_message": "Exportando Informe de Cobertura (Marcador de posición)" + "export_message": "Exportando Informe de Cobertura (Marcador de posici\u00f3n)" } } } diff --git a/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart b/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart index c09de9c3..02c89528 100644 --- a/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart +++ b/apps/mobile/packages/data_connect/lib/src/connectors/shifts/data/repositories/shifts_connector_repository_impl.dart @@ -314,109 +314,181 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { final String targetRoleId = roleId ?? ''; if (targetRoleId.isEmpty) throw Exception('Missing role id.'); - final QueryResult - roleResult = await _service.connector - .getShiftRoleById(shiftId: shiftId, roleId: targetRoleId) - .execute(); - final dc.GetShiftRoleByIdShiftRole? role = roleResult.data.shiftRole; - if (role == null) throw Exception('Shift role not found'); - + // 1. Fetch the initial shift to determine order type final QueryResult shiftResult = await _service.connector .getShiftById(id: shiftId) .execute(); - final dc.GetShiftByIdShift? shift = shiftResult.data.shift; - if (shift == null) throw Exception('Shift not found'); + final dc.GetShiftByIdShift? initialShift = shiftResult.data.shift; + if (initialShift == null) throw Exception('Shift not found'); - // Validate daily limit - final DateTime? shiftDate = _service.toDateTime(shift.date); - if (shiftDate != null) { - final DateTime dayStartUtc = DateTime.utc( - shiftDate.year, - shiftDate.month, - shiftDate.day, - ); - final DateTime dayEndUtc = dayStartUtc - .add(const Duration(days: 1)) - .subtract(const Duration(microseconds: 1)); + final dc.EnumValue orderTypeEnum = + initialShift.order.orderType; + final bool isMultiDay = + orderTypeEnum is dc.Known && + (orderTypeEnum.value == dc.OrderType.RECURRING || + orderTypeEnum.value == dc.OrderType.PERMANENT); + final List<_TargetShiftRole> targets = []; + if (isMultiDay) { + // 2. Fetch all shifts for this order to apply to all of them for the same role final QueryResult< - dc.VaidateDayStaffApplicationData, - dc.VaidateDayStaffApplicationVariables + dc.ListShiftRolesByBusinessAndOrderData, + dc.ListShiftRolesByBusinessAndOrderVariables > - validationResponse = await _service.connector - .vaidateDayStaffApplication(staffId: staffId) - .dayStart(_service.toTimestamp(dayStartUtc)) - .dayEnd(_service.toTimestamp(dayEndUtc)) - .execute(); - - if (validationResponse.data.applications.isNotEmpty) { - throw Exception('The user already has a shift that day.'); - } - } - - // Check for existing application - final QueryResult< - dc.GetApplicationByStaffShiftAndRoleData, - dc.GetApplicationByStaffShiftAndRoleVariables - > - existingAppRes = await _service.connector - .getApplicationByStaffShiftAndRole( - staffId: staffId, - shiftId: shiftId, - roleId: targetRoleId, - ) - .execute(); - if (existingAppRes.data.applications.isNotEmpty) { - throw Exception('Application already exists.'); - } - - if ((role.assigned ?? 0) >= role.count) { - throw Exception('This shift is full.'); - } - - final int currentAssigned = role.assigned ?? 0; - final int currentFilled = shift.filled ?? 0; - - String? createdAppId; - try { - final OperationResult< - dc.CreateApplicationData, - dc.CreateApplicationVariables - > - createRes = await _service.connector - .createApplication( - shiftId: shiftId, - staffId: staffId, - roleId: targetRoleId, - status: dc.ApplicationStatus.CONFIRMED, // Matches existing logic - origin: dc.ApplicationOrigin.STAFF, + allRolesRes = await _service.connector + .listShiftRolesByBusinessAndOrder( + businessId: initialShift.order.businessId, + orderId: initialShift.orderId, ) .execute(); - createdAppId = createRes.data.application_insert.id; - - await _service.connector - .updateShiftRole(shiftId: shiftId, roleId: targetRoleId) - .assigned(currentAssigned + 1) - .execute(); - - await _service.connector - .updateShift(id: shiftId) - .filled(currentFilled + 1) - .execute(); - } catch (e) { - // Simple rollback attempt (not guaranteed) - if (createdAppId != null) { - await _service.connector - .deleteApplication(id: createdAppId) - .execute(); + for (final role in allRolesRes.data.shiftRoles) { + if (role.roleId == targetRoleId) { + targets.add( + _TargetShiftRole( + shiftId: role.shiftId, + roleId: role.roleId, + count: role.count, + assigned: role.assigned ?? 0, + shiftFilled: role.shift.filled ?? 0, + date: _service.toDateTime(role.shift.date), + ), + ); + } } - rethrow; + } else { + // Single shift application + final QueryResult + roleResult = await _service.connector + .getShiftRoleById(shiftId: shiftId, roleId: targetRoleId) + .execute(); + final dc.GetShiftRoleByIdShiftRole? role = roleResult.data.shiftRole; + if (role == null) throw Exception('Shift role not found'); + + targets.add( + _TargetShiftRole( + shiftId: shiftId, + roleId: targetRoleId, + count: role.count, + assigned: role.assigned ?? 0, + shiftFilled: initialShift.filled ?? 0, + date: _service.toDateTime(initialShift.date), + ), + ); + } + + if (targets.isEmpty) { + throw Exception('No valid shifts found to apply for.'); + } + + int appliedCount = 0; + final List errors = []; + + for (final target in targets) { + try { + await _applyToSingleShiftRole(target: target, staffId: staffId); + appliedCount++; + } catch (e) { + // For multi-shift apply, we might want to continue even if some fail due to conflicts + if (targets.length == 1) rethrow; + errors.add('Shift on ${target.date}: ${e.toString()}'); + } + } + + if (appliedCount == 0 && targets.length > 1) { + throw Exception('Failed to apply for any shifts: ${errors.join(", ")}'); } }); } + Future _applyToSingleShiftRole({ + required _TargetShiftRole target, + required String staffId, + }) async { + // Validate daily limit + if (target.date != null) { + final DateTime dayStartUtc = DateTime.utc( + target.date!.year, + target.date!.month, + target.date!.day, + ); + final DateTime dayEndUtc = dayStartUtc + .add(const Duration(days: 1)) + .subtract(const Duration(microseconds: 1)); + + final QueryResult< + dc.VaidateDayStaffApplicationData, + dc.VaidateDayStaffApplicationVariables + > + validationResponse = await _service.connector + .vaidateDayStaffApplication(staffId: staffId) + .dayStart(_service.toTimestamp(dayStartUtc)) + .dayEnd(_service.toTimestamp(dayEndUtc)) + .execute(); + + if (validationResponse.data.applications.isNotEmpty) { + throw Exception('The user already has a shift that day.'); + } + } + + // Check for existing application + final QueryResult< + dc.GetApplicationByStaffShiftAndRoleData, + dc.GetApplicationByStaffShiftAndRoleVariables + > + existingAppRes = await _service.connector + .getApplicationByStaffShiftAndRole( + staffId: staffId, + shiftId: target.shiftId, + roleId: target.roleId, + ) + .execute(); + + if (existingAppRes.data.applications.isNotEmpty) { + throw Exception('Application already exists.'); + } + + if (target.assigned >= target.count) { + throw Exception('This shift is full.'); + } + + String? createdAppId; + try { + final OperationResult< + dc.CreateApplicationData, + dc.CreateApplicationVariables + > + createRes = await _service.connector + .createApplication( + shiftId: target.shiftId, + staffId: staffId, + roleId: target.roleId, + status: dc.ApplicationStatus.CONFIRMED, + origin: dc.ApplicationOrigin.STAFF, + ) + .execute(); + + createdAppId = createRes.data.application_insert.id; + + await _service.connector + .updateShiftRole(shiftId: target.shiftId, roleId: target.roleId) + .assigned(target.assigned + 1) + .execute(); + + await _service.connector + .updateShift(id: target.shiftId) + .filled(target.shiftFilled + 1) + .execute(); + } catch (e) { + // Simple rollback attempt (not guaranteed) + if (createdAppId != null) { + await _service.connector.deleteApplication(id: createdAppId).execute(); + } + rethrow; + } + } + @override Future acceptShift({required String shiftId, required String staffId}) { return _updateApplicationStatus( @@ -704,3 +776,21 @@ class ShiftsConnectorRepositoryImpl implements ShiftsConnectorRepository { return schedules; } } + +class _TargetShiftRole { + final String shiftId; + final String roleId; + final int count; + final int assigned; + final int shiftFilled; + final DateTime? date; + + _TargetShiftRole({ + required this.shiftId, + required this.roleId, + required this.count, + required this.assigned, + required this.shiftFilled, + this.date, + }); +} diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart index 8e3c6a46..d97938db 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart @@ -1,6 +1,7 @@ import 'package:design_system/design_system.dart'; import 'package:flutter/material.dart'; import 'package:krow_domain/krow_domain.dart'; +import 'package:core_localization/core_localization.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../blocs/shifts/shifts_bloc.dart'; @@ -233,7 +234,11 @@ class _FindShiftsTabState extends State { setState(() => _searchQuery = v), decoration: InputDecoration( border: InputBorder.none, - hintText: "Search jobs, location...", + hintText: context + .t + .staff_shifts + .find_shifts + .search_hint, hintStyle: UiTypography.body2r.textPlaceholder, ), ), @@ -267,13 +272,25 @@ class _FindShiftsTabState extends State { scrollDirection: Axis.horizontal, child: Row( children: [ - _buildFilterTab('all', 'All Jobs'), + _buildFilterTab( + 'all', + context.t.staff_shifts.find_shifts.filter_all, + ), const SizedBox(width: UiConstants.space2), - _buildFilterTab('one-day', 'One Day'), + _buildFilterTab( + 'one-day', + context.t.staff_shifts.find_shifts.filter_one_day, + ), const SizedBox(width: UiConstants.space2), - _buildFilterTab('multi-day', 'Multi-Day'), + _buildFilterTab( + 'multi-day', + context.t.staff_shifts.find_shifts.filter_multi_day, + ), const SizedBox(width: UiConstants.space2), - _buildFilterTab('long-term', 'Long Term'), + _buildFilterTab( + 'long-term', + context.t.staff_shifts.find_shifts.filter_long_term, + ), ], ), ), @@ -283,10 +300,10 @@ class _FindShiftsTabState extends State { Expanded( child: filteredJobs.isEmpty - ? const EmptyStateView( + ? EmptyStateView( icon: UiIcons.search, - title: "No jobs available", - subtitle: "Check back later", + title: context.t.staff_shifts.find_shifts.no_jobs_title, + subtitle: context.t.staff_shifts.find_shifts.no_jobs_subtitle, ) : SingleChildScrollView( padding: const EdgeInsets.symmetric( @@ -308,8 +325,11 @@ class _FindShiftsTabState extends State { ); UiSnackbar.show( context, - message: - "Shift application submitted!", // Todo: Localization + message: context + .t + .staff_shifts + .find_shifts + .application_submitted, type: UiSnackbarType.success, ); }, diff --git a/backend/dataconnect/connector/shiftRole/queries.gql b/backend/dataconnect/connector/shiftRole/queries.gql index 7b525502..37a06b67 100644 --- a/backend/dataconnect/connector/shiftRole/queries.gql +++ b/backend/dataconnect/connector/shiftRole/queries.gql @@ -401,6 +401,7 @@ query listShiftRolesByBusinessAndOrder( orderId location locationAddress + filled order{ vendorId @@ -425,6 +426,29 @@ query listShiftRolesByBusinessAndOrder( } } +query listShiftRolesByOrderAndRole( + $orderId: UUID! + $roleId: UUID! +) @auth(level: USER) { + shiftRoles( + where: { + shift: { orderId: { eq: $orderId } } + roleId: { eq: $roleId } + } + ) { + id + shiftId + roleId + count + assigned + shift { + id + filled + date + } + } +} + #reorder get list by businessId query listShiftRolesByBusinessDateRangeCompletedOrders( $businessId: UUID!