From abe4ce86d48193192d543fee881dabccebf863fa Mon Sep 17 00:00:00 2001 From: Lucas Santana Date: Sun, 2 Feb 2025 08:20:33 -0300 Subject: [PATCH] feat: integra sistema de idiomas com banco de dados MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Integra completamente com a tabela languages - Adiciona suporte para ícones de bandeira e instruções - Remove LANGUAGE_OPTIONS hard coded - Usa DEFAULT_LANGUAGE do type - Melhora validações e UX do seletor de idiomas - Atualiza CHANGELOG.md para versão 1.4.0 --- src/components/story/StoryGenerator.tsx | 68 +++++++++++-------- src/hooks/useLanguages.ts | 44 ++++++++++++ .../student-dashboard/CreateStoryPage.tsx | 11 +-- src/types/language.ts | 38 +++++++++++ supabase/contexts/constraints.md | 7 ++ supabase/contexts/full_schema.md | 21 ++++-- supabase/contexts/policies.md | 2 + 7 files changed, 146 insertions(+), 45 deletions(-) create mode 100644 src/hooks/useLanguages.ts create mode 100644 src/types/language.ts diff --git a/src/components/story/StoryGenerator.tsx b/src/components/story/StoryGenerator.tsx index 5b37710..5983f37 100644 --- a/src/components/story/StoryGenerator.tsx +++ b/src/components/story/StoryGenerator.tsx @@ -5,6 +5,7 @@ import { useSession } from '../../hooks/useSession'; import { useStoryCategories } from '../../hooks/useStoryCategories'; import { Wand2, ArrowLeft, Globe } from 'lucide-react'; import { useStudentTracking } from '../../hooks/useStudentTracking'; +import { useLanguages } from '../../hooks/useLanguages'; interface Category { id: string; @@ -44,12 +45,6 @@ interface StoryGeneratorProps { setChoices: React.Dispatch>; } -const LANGUAGE_OPTIONS = [ - { value: 'pt-BR', label: 'Português (Brasil)' }, - { value: 'en-US', label: 'Inglês (EUA)' }, - { value: 'es-ES', label: 'Espanhol (Espanha)' } -] as const; - export function StoryGenerator({ initialContext = '', onContextChange, @@ -62,7 +57,8 @@ export function StoryGenerator({ choices, setChoices }: StoryGeneratorProps) { - const { themes, subjects, characters, settings, isLoading } = useStoryCategories(); + const { themes, subjects, characters, settings, isLoading: isCategoriesLoading } = useStoryCategories(); + const { languages, supportedLanguages, isLoading: isLanguagesLoading } = useLanguages(); // Definir steps com os dados obtidos const steps: StoryStep[] = [ @@ -173,7 +169,8 @@ export function StoryGenerator({ const handleLanguageSelect = (language: string) => { console.log('Selecionando idioma:', language); - if (!LANGUAGE_OPTIONS.some(opt => opt.value === language)) { + const selectedLanguage = languages.find(lang => lang.code === language); + if (!selectedLanguage) { setError('Idioma inválido selecionado'); return; } @@ -252,7 +249,7 @@ export function StoryGenerator({ } // Validar idioma - if (!choices.language_type || !LANGUAGE_OPTIONS.some(opt => opt.value === choices.language_type)) { + if (!choices.language_type || !languages.some(lang => lang.code === choices.language_type)) { setError('Idioma não selecionado ou inválido'); return; } @@ -473,7 +470,7 @@ export function StoryGenerator({ } }; - if (isLoading) { + if (isCategoriesLoading || isLanguagesLoading) { return (
@@ -506,27 +503,38 @@ export function StoryGenerator({ {currentStep.isLanguageStep ? (
- {LANGUAGE_OPTIONS.map((option) => ( -
- - ))} + + ); + })}
) : currentStep.isContextStep ? (
diff --git a/src/hooks/useLanguages.ts b/src/hooks/useLanguages.ts new file mode 100644 index 0000000..795733e --- /dev/null +++ b/src/hooks/useLanguages.ts @@ -0,0 +1,44 @@ +import { useQuery } from '@tanstack/react-query'; +import { supabase } from '../lib/supabase'; +import type { Language, SupportedLanguageCode, LanguageOption } from '../types/language'; + +interface UseLanguagesReturn { + languages: Language[]; + isLoading: boolean; + error: Error | null; + getLanguageLabel: (code: SupportedLanguageCode) => string; + supportedLanguages: LanguageOption[]; +} + +export function useLanguages(): UseLanguagesReturn { + const { data: languages = [], isLoading, error } = useQuery({ + queryKey: ['languages'], + queryFn: async () => { + const { data, error } = await supabase + .from('languages') + .select('*') + .order('name'); + + if (error) throw error; + return data; + } + }); + + const supportedLanguages: LanguageOption[] = languages.map(lang => ({ + value: lang.code as SupportedLanguageCode, + label: lang.name + })); + + const getLanguageLabel = (code: SupportedLanguageCode): string => { + const language = languages.find(lang => lang.code === code); + return language?.name || code; + }; + + return { + languages, + isLoading, + error: error as Error | null, + getLanguageLabel, + supportedLanguages + }; +} \ No newline at end of file diff --git a/src/pages/student-dashboard/CreateStoryPage.tsx b/src/pages/student-dashboard/CreateStoryPage.tsx index 90840ba..0b2dc28 100644 --- a/src/pages/student-dashboard/CreateStoryPage.tsx +++ b/src/pages/student-dashboard/CreateStoryPage.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { ArrowLeft, Sparkles, Globe } from 'lucide-react'; +import { ArrowLeft, Sparkles } from 'lucide-react'; import { useNavigate } from 'react-router-dom'; import { StoryGenerator } from '../../components/story/StoryGenerator'; import { useSession } from '../../hooks/useSession'; @@ -9,12 +9,7 @@ import { useUppercasePreference } from '../../hooks/useUppercasePreference'; import { useSpeechRecognition } from '@/features/voice-commands/hooks/useSpeechRecognition'; import { VoiceCommandButton } from '@/features/voice-commands/components/VoiceCommandButton'; import type { StoryChoices } from '@/components/story/StoryGenerator'; - -const LANGUAGE_OPTIONS = [ - { value: 'pt-BR', label: 'Português (Brasil)' }, - { value: 'en-US', label: 'Inglês (EUA)' }, - { value: 'es-ES', label: 'Espanhol (Espanha)' } -] as const; +import { DEFAULT_LANGUAGE } from '@/types/language'; export function CreateStoryPage() { const navigate = useNavigate(); @@ -45,7 +40,7 @@ export function CreateStoryPage() { character_id: null, setting_id: null, context: '', - language_type: 'pt-BR' + language_type: DEFAULT_LANGUAGE }); // Manipuladores para gravação de voz diff --git a/src/types/language.ts b/src/types/language.ts new file mode 100644 index 0000000..544d9da --- /dev/null +++ b/src/types/language.ts @@ -0,0 +1,38 @@ +import { supabase } from "@/lib/supabase"; + +export interface Language { + id: string; + name: string; + code: string; + instructions: string | null; + flag_icon: string | null; + created_at: string; + updated_at: string; +} + +// Busca as opções de idioma do banco de dados +export const getLanguageOptions = async () => { + const { data: languages, error } = await supabase + .from('languages') + .select('code, name') + .order('name'); + + if (error) { + console.error('Erro ao buscar idiomas:', error); + return []; + } + + return languages.map(lang => ({ + value: lang.code, + label: lang.name + })); +}; + +export interface LanguageOption { + value: string; + label: string; +} + +export type SupportedLanguageCode = string; + +export const DEFAULT_LANGUAGE = 'pt-BR'; \ No newline at end of file diff --git a/supabase/contexts/constraints.md b/supabase/contexts/constraints.md index 46933c3..cecd670 100644 --- a/supabase/contexts/constraints.md +++ b/supabase/contexts/constraints.md @@ -87,6 +87,8 @@ | public | student_phonics_achievements | student_id | FOREIGN KEY | | public | phonics_word_audio | id | PRIMARY KEY | | public | phonics_word_audio | word | UNIQUE | +| public | languages | code | UNIQUE | +| public | languages | id | PRIMARY KEY | | public | story_recordings | story_id | FOREIGN KEY | | public | story_recordings | story_id | FOREIGN KEY | | public | story_recordings | | CHECK | @@ -291,6 +293,11 @@ | public | phonics_word_audio | | CHECK | | public | phonics_word_audio | | CHECK | | public | phonics_word_audio | | CHECK | +| public | languages | | CHECK | +| public | languages | | CHECK | +| public | languages | | CHECK | +| public | languages | | CHECK | +| public | languages | | CHECK | | public | story_recordings | | CHECK | | public | story_recordings | | CHECK | | public | story_recordings | | CHECK | diff --git a/supabase/contexts/full_schema.md b/supabase/contexts/full_schema.md index c233c6d..a951c32 100644 --- a/supabase/contexts/full_schema.md +++ b/supabase/contexts/full_schema.md @@ -72,8 +72,8 @@ | realtime | subscription | claims | jsonb | NO | | | realtime | messages | inserted_at | timestamp without time zone | NO | now() | | public | phonics_categories | level | integer | NO | | -| storage | objects | bucket_id | text | YES | | | public | story_characters | slug | text | NO | | +| storage | objects | bucket_id | text | YES | | | public | students | birth_date | date | YES | | | auth | refresh_tokens | revoked | boolean | YES | | | storage | s3_multipart_uploads | owner_id | text | YES | | @@ -89,6 +89,7 @@ | public | phonics_words | created_at | timestamp with time zone | YES | CURRENT_TIMESTAMP | | public | teacher_invites | expires_at | timestamp with time zone | NO | | | auth | identities | provider_id | text | NO | | +| public | languages | name | character varying | NO | | | net | _http_response | headers | jsonb | YES | | | auth | mfa_challenges | otp_code | text | YES | | | public | stories | character_id | uuid | YES | | @@ -259,8 +260,8 @@ | public | story_settings | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) | | public | story_subjects | description | text | NO | | | public | stories | content | jsonb | NO | | -| auth | flow_state | provider_access_token | text | YES | | | vault | secrets | secret | text | NO | | +| auth | flow_state | provider_access_token | text | YES | | | public | interests | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) | | public | teachers | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) | | storage | s3_multipart_uploads | created_at | timestamp with time zone | NO | now() | @@ -325,12 +326,13 @@ | extensions | pg_stat_statements | blk_read_time | double precision | YES | | | public | phonics_exercises | title | character varying | NO | | | auth | saml_providers | id | uuid | NO | | +| public | languages | flag_icon | character varying | YES | | | auth | sessions | user_agent | text | YES | | | public | story_recordings | processed_at | timestamp with time zone | YES | | | storage | s3_multipart_uploads_parts | bucket_id | text | NO | | | pgsodium | decrypted_key | name | text | YES | | -| public | phonics_words | word | character varying | NO | | | public | teachers | name | text | NO | | +| public | phonics_words | word | character varying | NO | | | auth | sso_providers | created_at | timestamp with time zone | YES | | | storage | buckets | file_size_limit | bigint | YES | | | auth | sso_domains | sso_provider_id | uuid | NO | | @@ -406,6 +408,7 @@ | storage | s3_multipart_uploads_parts | key | text | NO | | | public | story_details | updated_at | timestamp with time zone | YES | | | auth | mfa_challenges | factor_id | uuid | NO | | +| public | languages | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) | | pgsodium | masking_rule | attrelid | oid | YES | | | public | students | name | text | NO | | | storage | s3_multipart_uploads_parts | owner_id | text | YES | | @@ -415,8 +418,8 @@ | public | story_details | subject_icon | text | YES | | | auth | schema_migrations | version | character varying | NO | | | public | phonics_word_audio | audio_path | text | NO | | -| auth | one_time_tokens | token_hash | text | NO | | | public | story_settings | slug | text | NO | | +| auth | one_time_tokens | token_hash | text | NO | | | public | story_recordings | transcription | text | YES | | | storage | objects | metadata | jsonb | YES | | | public | story_characters | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) | @@ -436,8 +439,8 @@ | public | story_details | content | jsonb | YES | | | public | teachers | class_ids | ARRAY | YES | | | extensions | pg_stat_statements | shared_blks_hit | bigint | YES | | -| public | teacher_classes | class_id | uuid | NO | | | public | story_generations | id | uuid | NO | uuid_generate_v4() | +| public | teacher_classes | class_id | uuid | NO | | | auth | flow_state | auth_code_issued_at | timestamp with time zone | YES | | | public | media_types | id | uuid | NO | uuid_generate_v4() | | public | students | avatar_url | text | YES | | @@ -473,8 +476,8 @@ | auth | mfa_factors | user_id | uuid | NO | | | public | phonics_word_audio | id | uuid | NO | uuid_generate_v4() | | public | students | guardian_phone | text | YES | | -| pgsodium | masking_rule | relname | name | YES | | | storage | s3_multipart_uploads | key | text | NO | | +| pgsodium | masking_rule | relname | name | YES | | | auth | sessions | ip | inet | YES | | | auth | refresh_tokens | updated_at | timestamp with time zone | YES | | | public | story_recordings | pronunciation_score | integer | YES | | @@ -486,8 +489,8 @@ | pgsodium | key | key_context | bytea | YES | '\x7067736f6469756d'::bytea | | public | story_settings | icon | text | NO | | | public | story_generations | created_at | timestamp with time zone | NO | timezone('utc'::text, now()) | -| storage | objects | name | text | YES | | | public | story_characters | title | text | NO | | +| storage | objects | name | text | YES | | | public | student_achievements | id | uuid | NO | uuid_generate_v4() | | public | story_exercise_words | syllable_pattern | text | YES | | | auth | flow_state | code_challenge | text | NO | | @@ -501,6 +504,7 @@ | public | student_phonics_achievements | student_id | uuid | YES | | | public | teachers | phone | text | YES | | | vault | decrypted_secrets | name | text | YES | | +| public | languages | code | character varying | NO | | | auth | instances | id | uuid | NO | | | public | story_pages | text | text | NO | | | pgsodium | key | associated_data | text | YES | 'associated'::text | @@ -551,6 +555,7 @@ | pgsodium | decrypted_key | decrypted_raw_key | bytea | YES | | | auth | instances | created_at | timestamp with time zone | YES | | | storage | migrations | hash | character varying | NO | | +| public | languages | updated_at | timestamp with time zone | NO | timezone('utc'::text, now()) | | public | story_recordings | error_message | text | YES | | | public | story_pages | id | uuid | NO | uuid_generate_v4() | | public | student_phonics_attempts | created_at | timestamp with time zone | YES | CURRENT_TIMESTAMP | @@ -582,6 +587,7 @@ | public | story_pages | page_number | integer | NO | | | extensions | pg_stat_statements | temp_blk_read_time | double precision | YES | | | net | _http_response | status_code | integer | YES | | +| public | languages | instructions | text | YES | | | public | phonics_exercises | description | text | YES | | | auth | saml_relay_states | request_id | text | NO | | | storage | s3_multipart_uploads_parts | part_number | integer | NO | | @@ -603,6 +609,7 @@ | pgsodium | masking_rule | key_id_column | text | YES | | | storage | s3_multipart_uploads_parts | id | uuid | NO | gen_random_uuid() | | auth | identities | user_id | uuid | NO | | +| public | languages | id | uuid | NO | uuid_generate_v4() | | public | story_pages | image_path | text | YES | | | auth | saml_providers | metadata_url | text | YES | | | auth | instances | raw_base_config | text | YES | | diff --git a/supabase/contexts/policies.md b/supabase/contexts/policies.md index efec9bd..4a7661d 100644 --- a/supabase/contexts/policies.md +++ b/supabase/contexts/policies.md @@ -9,6 +9,8 @@ | 65876 | public | interests | Students can insert their own interests | a | | (auth.uid() = student_id) | | 65877 | public | interests | Students can update their own interests | w | (auth.uid() = student_id) | (auth.uid() = student_id) | | 65875 | public | interests | Students can view their own interests | r | (auth.uid() = student_id) | | +| 104599 | public | languages | Allow insert/update for admins only | * | ((auth.jwt() ->> 'role'::text) = 'admin'::text) | ((auth.jwt() ->> 'role'::text) = 'admin'::text) | +| 104598 | public | languages | Allow read access for all authenticated users | r | true | | | 79931 | public | phonics_categories | Permitir leitura de categorias fonéticas para usuários autent | r | true | | | 79932 | public | phonics_exercise_types | Permitir leitura de tipos de exercícios fonéticos para usuár | r | true | | | 79934 | public | phonics_exercise_words | Permitir leitura de relações exercício-palavra para usuário | r | true | |