diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..099e738 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "Bash(npx sass:*)", + "Bash(npx md-to-pdf:*)", + "WebFetch(domain:support.google.com)", + "WebFetch(domain:feedops.com)", + "WebFetch(domain:help.kliken.com)", + "WebFetch(domain:www.storegrowers.com)", + "WebFetch(domain:platform.openai.com)", + "WebFetch(domain:openai.com)" + ] + } +} diff --git a/.htaccess b/.htaccess index 2178078..f273f58 100644 --- a/.htaccess +++ b/.htaccess @@ -8,10 +8,14 @@ RewriteRule ^(.*)$ https://%1/$1 [R=301,L] RewriteCond %{SERVER_PORT} !=443 RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=permanent] -RewriteCond %{REQUEST_URI} !^(.*)/libraries/(.*) [NC] -RewriteCond %{REQUEST_URI} !^(.*)/temp/(.*) [NC] -RewriteCond %{REQUEST_URI} !^(.*)/layout/(.*) [NC] -RewriteCond %{REQUEST_URI} !^(.*)/upload/(.*) [NC] -RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L] +# Statyczne zasoby - pomijaj +RewriteCond %{REQUEST_URI} ^/(libraries|layout|upload|temp)/ [NC] +RewriteRule ^ - [L] -RewriteRule ^logowanie$ index.php?module=users&action=login_form [L] \ No newline at end of file +# Istniejące pliki/katalogi - pomijaj +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] + +# Wszystko inne → index.php +RewriteRule ^(.*)$ index.php [L,QSA] diff --git a/.vscode/ftp-kr.sync.cache.json b/.vscode/ftp-kr.sync.cache.json index 6e32d03..1eb8b0a 100644 --- a/.vscode/ftp-kr.sync.cache.json +++ b/.vscode/ftp-kr.sync.cache.json @@ -161,8 +161,8 @@ "products": { "main_view.php": { "type": "-", - "size": 19004, - "lmtime": 1769727759481, + "size": 19064, + "lmtime": 1770756800564, "modified": false }, "product_history.php": { diff --git a/autoload/controls/class.Clients.php b/autoload/controls/class.Clients.php new file mode 100644 index 0000000..9f041fb --- /dev/null +++ b/autoload/controls/class.Clients.php @@ -0,0 +1,70 @@ + $name, + 'google_ads_customer_id' => $google_ads_customer_id ?: null, + 'google_ads_start_date' => $google_ads_start_date ?: null, + ]; + + if ( $id ) + { + \factory\Clients::update( $id, $data ); + \S::alert( 'Klient został zaktualizowany.' ); + } + else + { + \factory\Clients::create( $data ); + \S::alert( 'Klient został dodany.' ); + } + + header( 'Location: /clients' ); + exit; + } + + static public function delete() + { + $id = \S::get( 'id' ); + + if ( $id ) + { + \factory\Clients::delete( $id ); + } + + echo json_encode( [ 'success' => true ] ); + exit; + } + + static public function get() + { + $id = \S::get( 'id' ); + $client = \factory\Clients::get( $id ); + + echo json_encode( $client ?: [] ); + exit; + } +} diff --git a/autoload/controls/class.Cron.php b/autoload/controls/class.Cron.php index 1d7279c..06d60bc 100644 --- a/autoload/controls/class.Cron.php +++ b/autoload/controls/class.Cron.php @@ -516,6 +516,160 @@ class Cron exit; } + // =========================== + // KAMPANIE - Google Ads API + // =========================== + + static public function cron_campaigns() + { + global $mdb; + + $api = new \services\GoogleAdsApi(); + + if ( !$api -> is_configured() ) + { + echo json_encode( [ 'result' => 'Google Ads API nie jest skonfigurowane. Uzupelnij dane w Ustawieniach.' ] ); + exit; + } + + // Pobierz klientów z ustawionym Google Ads Customer ID + $clients = $mdb -> select( 'clients', '*', [ + 'google_ads_customer_id[!]' => null + ] ); + + if ( empty( $clients ) ) + { + echo json_encode( [ 'result' => 'Brak klientow z ustawionym Google Ads Customer ID.' ] ); + exit; + } + + $today = date( 'Y-m-d' ); + $processed = 0; + $errors = []; + + foreach ( $clients as $client ) + { + $customer_id = $client['google_ads_customer_id']; + + // Pobierz dane 30-dniowe + $campaigns_30 = $api -> get_campaigns_30_days( $customer_id ); + if ( $campaigns_30 === false ) + { + $last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' ); + $errors[] = 'Blad API dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err; + continue; + } + + // Pobierz dane all-time + $campaigns_all_time = $api -> get_campaigns_all_time( $customer_id ); + $all_time_map = []; + if ( is_array( $campaigns_all_time ) ) + { + foreach ( $campaigns_all_time as $cat ) + { + $all_time_map[ $cat['campaign_id'] ] = $cat['roas_all_time']; + } + } + + foreach ( $campaigns_30 as $campaign ) + { + // Upsert kampanii + if ( !$mdb -> count( 'campaigns', [ 'AND' => [ + 'client_id' => $client['id'], + 'campaign_id' => $campaign['campaign_id'] + ] ] ) ) + { + $mdb -> insert( 'campaigns', [ + 'client_id' => $client['id'], + 'campaign_id' => $campaign['campaign_id'], + 'campaign_name' => $campaign['campaign_name'] + ] ); + $db_campaign_id = $mdb -> id(); + } + else + { + $db_campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [ + 'client_id' => $client['id'], + 'campaign_id' => $campaign['campaign_id'] + ] ] ); + + // Aktualizuj nazwe kampanii jesli sie zmienila + $mdb -> update( 'campaigns', [ + 'campaign_name' => $campaign['campaign_name'] + ], [ 'id' => $db_campaign_id ] ); + } + + // Budowanie strategii biddingu + $bidding_strategy = self::format_bidding_strategy( + $campaign['bidding_strategy'], + $campaign['target_roas'] ?? 0 + ); + + // Dane historii + $history_data = [ + 'roas_30_days' => $campaign['roas_30_days'], + 'roas_all_time' => $all_time_map[ $campaign['campaign_id'] ] ?? 0, + 'budget' => $campaign['budget'], + 'money_spent' => $campaign['money_spent'], + 'conversion_value' => $campaign['conversion_value'], + 'bidding_strategy' => $bidding_strategy, + ]; + + // Upsert do campaigns_history + if ( $mdb -> count( 'campaigns_history', [ 'AND' => [ + 'campaign_id' => $db_campaign_id, + 'date_add' => $today + ] ] ) ) + { + $mdb -> update( 'campaigns_history', $history_data, [ 'AND' => [ + 'campaign_id' => $db_campaign_id, + 'date_add' => $today + ] ] ); + } + else + { + $history_data['campaign_id'] = $db_campaign_id; + $history_data['date_add'] = $today; + $mdb -> insert( 'campaigns_history', $history_data ); + } + + $processed++; + } + } + + echo json_encode( [ + 'result' => 'Synchronizacja zakonczona. Przetworzono kampanii: ' . $processed . '.', + 'errors' => $errors + ] ); + exit; + } + + static private function format_bidding_strategy( $strategy_type, $target_roas = 0 ) + { + $map = [ + 'MAXIMIZE_CONVERSIONS' => 'Maksymalizacja liczby konwersji', + 'MAXIMIZE_CONVERSION_VALUE' => 'Maksymalizacja wartosci konwersji', + 'TARGET_ROAS' => 'Docelowy ROAS', + 'TARGET_CPA' => 'Docelowy CPA', + 'MANUAL_CPC' => 'Reczny CPC', + 'MANUAL_CPM' => 'Reczny CPM', + 'TARGET_IMPRESSION_SHARE' => 'Docelowy udzial w wyswietleniach', + ]; + + $label = $map[ $strategy_type ] ?? $strategy_type ?? 'brak'; + + if ( $target_roas > 0 ) + { + $label .= ' | docelowy ROAS: ' . round( $target_roas * 100 ) . '%'; + } + + return $label; + } + + // =========================== + // FRAZY - history 30 + // =========================== + static public function cron_phrase_history_30_save( $phrase_id, $date_from, $date_to ) { global $mdb; diff --git a/autoload/controls/class.Products.php b/autoload/controls/class.Products.php index 4f76e12..cc77640 100644 --- a/autoload/controls/class.Products.php +++ b/autoload/controls/class.Products.php @@ -69,8 +69,82 @@ class Products $product_title = \factory\Products::get_product_data( $product_id, 'title' ); $product_description = \factory\Products::get_product_data( $product_id, 'description' ); + $google_product_category = \factory\Products::get_product_data( $product_id, 'google_product_category' ); + $product_url = \factory\Products::get_product_data( $product_id, 'product_url' ); - echo json_encode( [ 'status' => 'ok', 'product_details' => [ 'title' => $product_title, 'description' => $product_description ] ] ); + echo json_encode( [ 'status' => 'ok', 'product_details' => [ + 'title' => $product_title, + 'description' => $product_description, + 'google_product_category' => $google_product_category, + 'product_url' => $product_url + ] ] ); + exit; + } + + static public function ai_suggest() + { + $product_id = \S::get( 'product_id' ); + $field = \S::get( 'field' ); + + if ( !\services\OpenAiApi::is_configured() ) + { + echo json_encode( [ 'status' => 'error', 'message' => 'Klucz API OpenAI nie jest skonfigurowany. Przejdź do Ustawień.' ] ); + exit; + } + + $product = \factory\Products::get_product_full_context( $product_id ); + if ( !$product ) + { + echo json_encode( [ 'status' => 'error', 'message' => 'Nie znaleziono produktu.' ] ); + exit; + } + + // Pobierz treść strony produktu jeśli podano URL + $product_url = \S::get( 'product_url' ); + $page_content = ''; + if ( $product_url && filter_var( $product_url, FILTER_VALIDATE_URL ) ) + { + $page_content = \services\OpenAiApi::fetch_page_content( $product_url ); + } + + $context = [ + 'original_name' => $product['name'], + 'current_title' => \factory\Products::get_product_data( $product_id, 'title' ), + 'current_description' => \factory\Products::get_product_data( $product_id, 'description' ), + 'current_category' => \factory\Products::get_product_data( $product_id, 'google_product_category' ), + 'offer_id' => $product['offer_id'], + 'impressions_30' => $product['impressions_30'] ?? 0, + 'clicks_30' => $product['clicks_30'] ?? 0, + 'ctr' => $product['ctr'] ?? 0, + 'cost' => $product['cost'] ?? 0, + 'conversions' => $product['conversions'] ?? 0, + 'conversions_value' => $product['conversions_value'] ?? 0, + 'roas' => $product['roas'] ?? 0, + 'custom_label_4' => \factory\Products::get_product_data( $product_id, 'custom_label_4' ), + 'page_content' => $page_content, + ]; + + switch ( $field ) + { + case 'title': + $result = \services\OpenAiApi::suggest_title( $context ); + break; + case 'description': + $result = \services\OpenAiApi::suggest_description( $context ); + break; + case 'category': + $result = \services\OpenAiApi::suggest_category( $context ); + break; + default: + $result = [ 'status' => 'error', 'message' => 'Nieznane pole: ' . $field ]; + } + + if ( $product_url && !$page_content ) + $result['warning'] = 'Nie udało się pobrać treści ze strony produktu (strona może blokować dostęp). Sugestia oparta tylko na nazwie produktu.'; + elseif ( $page_content ) + $result['page_fetched'] = true; + + echo json_encode( $result ); exit; } @@ -191,7 +265,7 @@ class Products '', '', '', - 'Usuń' + '' ]; } @@ -386,15 +460,21 @@ class Products $custom_title = \S::get( 'custom_title' ); $custom_description = \S::get( 'custom_description' ); $google_product_category = \S::get( 'google_product_category' ); + $product_url = \S::get( 'product_url' ); - if ( $product_id and $custom_title ) - \factory\Products::set_product_data( $product_id, 'title', $custom_title ); + if ( $product_id ) + { + if ( $custom_title ) + \factory\Products::set_product_data( $product_id, 'title', $custom_title ); - if ( $product_id and $custom_description ) - \factory\Products::set_product_data( $product_id, 'description', $custom_description ); + if ( $custom_description ) + \factory\Products::set_product_data( $product_id, 'description', $custom_description ); - if ( $product_id and $google_product_category ) - \factory\Products::set_product_data( $product_id, 'google_product_category', $google_product_category ); + if ( $google_product_category ) + \factory\Products::set_product_data( $product_id, 'google_product_category', $google_product_category ); + + \factory\Products::set_product_data( $product_id, 'product_url', $product_url ?: '' ); + } \factory\Products::add_product_comment( $product_id, 'Zmiana tytułu i opisu produktu.' ); diff --git a/autoload/controls/class.Users.php b/autoload/controls/class.Users.php index 3e6d01d..dd2ee36 100644 --- a/autoload/controls/class.Users.php +++ b/autoload/controls/class.Users.php @@ -56,7 +56,7 @@ class Users \S::set_session( 'user', $user ); \S::alert( 'Ustawienia zostały zapisane.' ); } - header( 'Location: /users/settings/' ); + header( 'Location: /settings' ); exit; } @@ -66,7 +66,8 @@ class Users if ( !$user ) { - return \Tpl::view( 'users/login-form' ); + header( 'Location: /login' ); + exit; } return \view\Users::settings( @@ -74,6 +75,40 @@ class Users ); } + public static function settings_save_google_ads() + { + $fields = [ + 'google_ads_developer_token', + 'google_ads_client_id', + 'google_ads_client_secret', + 'google_ads_refresh_token', + 'google_ads_manager_account_id', + ]; + + foreach ( $fields as $field ) + { + \services\GoogleAdsApi::set_setting( $field, \S::get( $field ) ); + } + + // wyczyść cached token przy zmianie credentials + \services\GoogleAdsApi::set_setting( 'google_ads_access_token', null ); + \services\GoogleAdsApi::set_setting( 'google_ads_access_token_expires', null ); + + \S::alert( 'Ustawienia Google Ads zostały zapisane.' ); + header( 'Location: /settings' ); + exit; + } + + public static function settings_save_openai() + { + \services\GoogleAdsApi::set_setting( 'openai_api_key', \S::get( 'openai_api_key' ) ); + \services\GoogleAdsApi::set_setting( 'openai_model', \S::get( 'openai_model' ) ); + + \S::alert( 'Ustawienia OpenAI zostały zapisane.' ); + header( 'Location: /settings' ); + exit; + } + public static function login() { if ( $user = \factory\Users::login( diff --git a/autoload/factory/class.Clients.php b/autoload/factory/class.Clients.php new file mode 100644 index 0000000..6c188f5 --- /dev/null +++ b/autoload/factory/class.Clients.php @@ -0,0 +1,36 @@ + select( 'clients', '*', [ 'ORDER' => [ 'name' => 'ASC' ] ] ); + } + + static public function get( $id ) + { + global $mdb; + return $mdb -> get( 'clients', '*', [ 'id' => $id ] ); + } + + static public function create( $data ) + { + global $mdb; + $mdb -> insert( 'clients', $data ); + return $mdb -> id(); + } + + static public function update( $id, $data ) + { + global $mdb; + return $mdb -> update( 'clients', $data, [ 'id' => $id ] ); + } + + static public function delete( $id ) + { + global $mdb; + return $mdb -> delete( 'clients', [ 'id' => $id ] ); + } +} diff --git a/autoload/factory/class.Products.php b/autoload/factory/class.Products.php index 567be87..2c2cbb2 100644 --- a/autoload/factory/class.Products.php +++ b/autoload/factory/class.Products.php @@ -108,6 +108,20 @@ class Products return $mdb -> query( 'SELECT COUNT(0) FROM products_temp AS pt INNER JOIN products AS p ON p.id = pt.product_id WHERE client_id = \'' . $client_id . '\'' ) -> fetchColumn(); } + static public function get_product_full_context( $product_id ) + { + global $mdb; + return $mdb -> query( + 'SELECT p.id, p.offer_id, p.name, p.min_roas, + pt.impressions, pt.impressions_30, pt.clicks, pt.clicks_30, + pt.ctr, pt.cost, pt.cpc, pt.conversions, pt.conversions_value, pt.roas + FROM products AS p + LEFT JOIN products_temp AS pt ON pt.product_id = p.id + WHERE p.id = :pid', + [ ':pid' => $product_id ] + ) -> fetch( \PDO::FETCH_ASSOC ); + } + static public function get_product_data( $product_id, $field ) { global $mdb; diff --git a/autoload/services/class.GoogleAdsApi.php b/autoload/services/class.GoogleAdsApi.php new file mode 100644 index 0000000..22cdcd1 --- /dev/null +++ b/autoload/services/class.GoogleAdsApi.php @@ -0,0 +1,274 @@ + developer_token = self::get_setting( 'google_ads_developer_token' ); + $this -> client_id = self::get_setting( 'google_ads_client_id' ); + $this -> client_secret = self::get_setting( 'google_ads_client_secret' ); + $this -> refresh_token = self::get_setting( 'google_ads_refresh_token' ); + $this -> manager_account_id = self::get_setting( 'google_ads_manager_account_id' ); + } + + // --- Settings CRUD --- + + public static function get_setting( $key ) + { + global $mdb; + return $mdb -> get( 'settings', 'setting_value', [ 'setting_key' => $key ] ); + } + + public static function set_setting( $key, $value ) + { + global $mdb; + + if ( $mdb -> count( 'settings', [ 'setting_key' => $key ] ) ) + { + $mdb -> update( 'settings', [ 'setting_value' => $value ], [ 'setting_key' => $key ] ); + } + else + { + $mdb -> insert( 'settings', [ 'setting_key' => $key, 'setting_value' => $value ] ); + } + } + + // --- Konfiguracja --- + + public function is_configured() + { + return !empty( $this -> developer_token ) + && !empty( $this -> client_id ) + && !empty( $this -> client_secret ) + && !empty( $this -> refresh_token ); + } + + // --- OAuth2 --- + + private function get_access_token() + { + $cached_token = self::get_setting( 'google_ads_access_token' ); + $cached_expires = (int) self::get_setting( 'google_ads_access_token_expires' ); + + if ( $cached_token && $cached_expires > time() ) + { + $this -> access_token = $cached_token; + return $this -> access_token; + } + + return $this -> refresh_access_token(); + } + + private function refresh_access_token() + { + $ch = curl_init( self::$TOKEN_URL ); + curl_setopt_array( $ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query( [ + 'client_id' => $this -> client_id, + 'client_secret' => $this -> client_secret, + 'refresh_token' => $this -> refresh_token, + 'grant_type' => 'refresh_token' + ] ), + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_TIMEOUT => 30, + ] ); + + $response = curl_exec( $ch ); + $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + $error = curl_error( $ch ); + curl_close( $ch ); + + if ( $http_code !== 200 || !$response ) + { + self::set_setting( 'google_ads_last_error', 'Token refresh failed: HTTP ' . $http_code . ' | ' . $error . ' | ' . $response ); + return false; + } + + $data = json_decode( $response, true ); + + if ( !isset( $data['access_token'] ) ) + { + self::set_setting( 'google_ads_last_error', 'Token refresh: brak access_token w odpowiedzi' ); + return false; + } + + $this -> access_token = $data['access_token']; + $expires_at = time() + ( $data['expires_in'] ?? 3600 ) - 60; + + self::set_setting( 'google_ads_access_token', $this -> access_token ); + self::set_setting( 'google_ads_access_token_expires', $expires_at ); + self::set_setting( 'google_ads_last_error', null ); + + return $this -> access_token; + } + + // --- Google Ads API --- + + public function search_stream( $customer_id, $gaql_query ) + { + $access_token = $this -> get_access_token(); + if ( !$access_token ) return false; + + $customer_id = str_replace( '-', '', $customer_id ); + + $url = self::$ADS_BASE_URL . '/' . self::$API_VERSION + . '/customers/' . $customer_id . '/googleAds:searchStream'; + + $headers = [ + 'Authorization: Bearer ' . $access_token, + 'developer-token: ' . $this -> developer_token, + 'Content-Type: application/json', + ]; + + if ( !empty( $this -> manager_account_id ) ) + { + $headers[] = 'login-customer-id: ' . str_replace( '-', '', $this -> manager_account_id ); + } + + $ch = curl_init( $url ); + curl_setopt_array( $ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => json_encode( [ 'query' => $gaql_query ] ), + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_TIMEOUT => 120, + ] ); + + $response = curl_exec( $ch ); + $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + $error = curl_error( $ch ); + curl_close( $ch ); + + if ( $http_code !== 200 || !$response ) + { + self::set_setting( 'google_ads_last_error', 'searchStream failed: HTTP ' . $http_code . ' | ' . $error . ' | ' . substr( $response, 0, 500 ) ); + return false; + } + + $data = json_decode( $response, true ); + + // searchStream zwraca tablicę batch'y, każdy z kluczem 'results' + $results = []; + if ( is_array( $data ) ) + { + foreach ( $data as $batch ) + { + if ( isset( $batch['results'] ) && is_array( $batch['results'] ) ) + { + $results = array_merge( $results, $batch['results'] ); + } + } + } + + self::set_setting( 'google_ads_last_error', null ); + + return $results; + } + + // --- Kampanie: dane 30-dniowe --- + + public function get_campaigns_30_days( $customer_id ) + { + $gaql = "SELECT " + . "campaign.id, " + . "campaign.name, " + . "campaign.bidding_strategy_type, " + . "campaign.target_roas.target_roas, " + . "campaign_budget.amount_micros, " + . "metrics.cost_micros, " + . "metrics.conversions_value " + . "FROM campaign " + . "WHERE campaign.status = 'ENABLED' " + . "AND segments.date DURING LAST_30_DAYS"; + + $results = $this -> search_stream( $customer_id, $gaql ); + if ( $results === false ) return false; + + // Agregacja po campaign.id (API zwraca wiersz per dzień per kampania) + $campaigns = []; + foreach ( $results as $row ) + { + $cid = $row['campaign']['id'] ?? null; + if ( !$cid ) continue; + + if ( !isset( $campaigns[ $cid ] ) ) + { + $campaigns[ $cid ] = [ + 'campaign_id' => $cid, + 'campaign_name' => $row['campaign']['name'] ?? '', + 'bidding_strategy' => $row['campaign']['biddingStrategyType'] ?? 'UNKNOWN', + 'target_roas' => isset( $row['campaign']['targetRoas']['targetRoas'] ) + ? (float) $row['campaign']['targetRoas']['targetRoas'] + : 0, + 'budget' => isset( $row['campaignBudget']['amountMicros'] ) + ? (float) $row['campaignBudget']['amountMicros'] / 1000000 + : 0, + 'cost_total' => 0, + 'conversion_value' => 0, + ]; + } + + $campaigns[ $cid ]['cost_total'] += (float) ( $row['metrics']['costMicros'] ?? 0 ); + $campaigns[ $cid ]['conversion_value'] += (float) ( $row['metrics']['conversionsValue'] ?? 0 ); + } + + // Przeliczenie micros i ROAS + foreach ( $campaigns as &$c ) + { + $c['money_spent'] = $c['cost_total'] / 1000000; + $c['roas_30_days'] = ( $c['money_spent'] > 0 ) + ? round( ( $c['conversion_value'] / $c['money_spent'] ) * 100, 2 ) + : 0; + unset( $c['cost_total'] ); + } + + return array_values( $campaigns ); + } + + // --- Kampanie: dane all-time --- + + public function get_campaigns_all_time( $customer_id ) + { + $gaql = "SELECT " + . "campaign.id, " + . "metrics.cost_micros, " + . "metrics.conversions_value " + . "FROM campaign " + . "WHERE campaign.status = 'ENABLED'"; + + $results = $this -> search_stream( $customer_id, $gaql ); + if ( $results === false ) return false; + + $campaigns = []; + foreach ( $results as $row ) + { + $cid = $row['campaign']['id'] ?? null; + if ( !$cid ) continue; + + $cost = (float) ( $row['metrics']['costMicros'] ?? 0 ) / 1000000; + $value = (float) ( $row['metrics']['conversionsValue'] ?? 0 ); + + $campaigns[] = [ + 'campaign_id' => $cid, + 'roas_all_time' => ( $cost > 0 ) ? round( ( $value / $cost ) * 100, 2 ) : 0, + ]; + } + + return $campaigns; + } +} diff --git a/autoload/services/class.OpenAiApi.php b/autoload/services/class.OpenAiApi.php new file mode 100644 index 0000000..636becd --- /dev/null +++ b/autoload/services/class.OpenAiApi.php @@ -0,0 +1,282 @@ + true, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', + CURLOPT_HTTPHEADER => [ + 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language: pl-PL,pl;q=0.9,en;q=0.8', + ], + CURLOPT_SSL_VERIFYPEER => false + ] ); + + $html = curl_exec( $ch ); + $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + curl_close( $ch ); + + if ( $http_code !== 200 || !$html ) return ''; + + // Usuń skrypty, style, komentarze + $html = preg_replace( '/]*>.*?<\/script>/si', '', $html ); + $html = preg_replace( '/]*>.*?<\/style>/si', '', $html ); + $html = preg_replace( '//s', '', $html ); + $html = preg_replace( '/]*>.*?<\/nav>/si', '', $html ); + $html = preg_replace( '/]*>.*?<\/footer>/si', '', $html ); + $html = preg_replace( '/]*>.*?<\/header>/si', '', $html ); + + // Zamień tagi na spacje i wyciągnij tekst + $text = strip_tags( $html ); + $text = html_entity_decode( $text, ENT_QUOTES, 'UTF-8' ); + $text = preg_replace( '/\s+/', ' ', $text ); + $text = trim( $text ); + + // Ogranicz do ~3000 znaków + if ( mb_strlen( $text ) > 3000 ) + $text = mb_substr( $text, 0, 3000 ) . '...'; + + return $text; + } + + static private function call_api( $system_prompt, $user_prompt, $max_tokens = 500 ) + { + $api_key = GoogleAdsApi::get_setting( 'openai_api_key' ); + $model = GoogleAdsApi::get_setting( 'openai_model' ) ?: 'gpt-5-mini'; + + // GPT-5.x wymaga max_completion_tokens, starsze modele używają max_tokens + $tokens_key = ( strpos( $model, 'gpt-5' ) === 0 ) ? 'max_completion_tokens' : 'max_tokens'; + + $payload = [ + 'model' => $model, + 'messages' => [ + [ 'role' => 'system', 'content' => $system_prompt ], + [ 'role' => 'user', 'content' => $user_prompt ] + ], + 'temperature' => 0.7, + $tokens_key => $max_tokens + ]; + + $ch = curl_init( self::$api_url ); + curl_setopt_array( $ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_HTTPHEADER => [ + 'Content-Type: application/json', + 'Authorization: Bearer ' . $api_key + ], + CURLOPT_POSTFIELDS => json_encode( $payload ), + CURLOPT_TIMEOUT => 30 + ] ); + + $response = curl_exec( $ch ); + $http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); + curl_close( $ch ); + + if ( $http_code !== 200 ) + { + $error = json_decode( $response, true ); + return [ 'status' => 'error', 'message' => $error['error']['message'] ?? 'Błąd API OpenAI (HTTP ' . $http_code . ')' ]; + } + + $data = json_decode( $response, true ); + $content = trim( $data['choices'][0]['message']['content'] ?? '' ); + + return [ 'status' => 'ok', 'suggestion' => $content ]; + } + + static private function build_context_text( $context ) + { + $lines = []; + $lines[] = 'Nazwa produktu: ' . ( $context['original_name'] ?? '—' ); + + if ( !empty( $context['current_title'] ) ) + $lines[] = 'Obecny tytuł (custom): ' . $context['current_title']; + + if ( !empty( $context['current_description'] ) ) + $lines[] = 'Obecny opis: ' . $context['current_description']; + + if ( !empty( $context['current_category'] ) ) + $lines[] = 'Obecna kategoria Google: ' . $context['current_category']; + + if ( !empty( $context['offer_id'] ) ) + $lines[] = 'ID oferty: ' . $context['offer_id']; + + if ( !empty( $context['custom_label_4'] ) ) + $lines[] = 'Status produktu: ' . $context['custom_label_4']; + + $lines[] = ''; + $lines[] = 'Metryki reklamowe (ostatnie 30 dni):'; + $lines[] = '- Wyświetlenia: ' . ( $context['impressions_30'] ?? 0 ); + $lines[] = '- Kliknięcia: ' . ( $context['clicks_30'] ?? 0 ); + $lines[] = '- CTR: ' . ( $context['ctr'] ?? 0 ) . '%'; + $lines[] = '- Koszt: ' . ( $context['cost'] ?? 0 ) . ' PLN'; + $lines[] = '- Konwersje: ' . ( $context['conversions'] ?? 0 ); + $lines[] = '- Wartość konwersji: ' . ( $context['conversions_value'] ?? 0 ) . ' PLN'; + $lines[] = '- ROAS: ' . ( $context['roas'] ?? 0 ) . '%'; + + if ( !empty( $context['page_content'] ) ) + { + $lines[] = ''; + $lines[] = 'Treść ze strony produktu (użyj tych informacji do stworzenia dokładniejszego opisu):'; + $lines[] = $context['page_content']; + } + + return implode( "\n", $lines ); + } + + static public function suggest_title( $context ) + { + $context_text = self::build_context_text( $context ); + + $prompt = 'Zaproponuj zoptymalizowany tytuł produktu dla Google Merchant Center. + +WYMAGANIA: +- Max 150 znaków, najważniejsze info w pierwszych 70 znakach +- Struktura: [Marka jeśli jest w nazwie] [Typ produktu] [Kluczowe cechy z nazwy] [Dla kogo/okazja jeśli wynika z nazwy] +- Pisownia zdaniowa lub tytułowa, naturalny język +- Tytuł musi brzmieć jak wpis w katalogu produktowym + +BEZWZGLĘDNY ZAKAZ (odrzucenie przez Google): +- Słowa promocyjne: bestseller, hit, idealny, najlepszy, polecamy, okazja, nowość, TOP +- Wykrzykniki (!), emotikony, symbole dekoracyjne +- WIELKIE LITERY (wyjątek: LED, USB, TV) +- Wezwania do działania, informacje o cenie/dostawie +- Cechy wymyślone — opisuj TYLKO to co wynika z oryginalnej nazwy lub treści strony produktu +- Jeśli podano treść ze strony produktu, wykorzystaj ją do wzbogacenia tytułu o rzeczywiste cechy (marka, materiał, kolor, rozmiar itp.) + +' . $context_text . ' + +Zwróć TYLKO tytuł, bez cudzysłowów, bez wyjaśnień.'; + + return self::call_api( self::$system_prompt, $prompt ); + } + + static public function suggest_description( $context ) + { + $context_text = self::build_context_text( $context ); + $has_page = !empty( $context['page_content'] ); + + $length_guide = $has_page + ? '- Napisz rozbudowany, szczegółowy opis: ok. 1000 znaków (800-1200) +- Wykorzystaj szczegóły ze strony produktu: skład zestawu, materiały, wymiary, kolory, przeznaczenie +- Każdy akapit/punkt powinien wnosić NOWĄ informację — NIE powtarzaj tych samych elementów' + : '- Napisz opis o długości ok. 1000 znaków (800-1200) — jeśli brak szczegółów, opisz ogólnie zastosowanie i grupę docelową +- Opisuj TYLKO to co wynika z oryginalnej nazwy — nie wymyślaj konkretnych parametrów'; + + $prompt = 'Napisz zoptymalizowany opis produktu dla Google Merchant Center. + +WYMAGANIA: +' . $length_guide . ' +- Najważniejsze info na początku (pierwsze 160 znaków widoczne w wynikach) +- Rzeczowo opisz: co to jest, z czego się składa, do czego służy, dla kogo +- Ton neutralny, informacyjny — jak w specyfikacji produktowej +- Każdy akapit musi zawierać INNĄ informację niż poprzedni — NIGDY nie powtarzaj tych samych elementów + +FORMATOWANIE — Google Merchant Center obsługuje podstawowe HTML: +- Używaj
do oddzielania akapitów/sekcji +- Używaj pogrubienia dla kluczowych cech (np. nazwy elementów zestawu, materiał) +- Używaj
  • ...
gdy wymieniasz elementy zestawu lub listę cech +- NIE używaj innych tagów HTML (h1, p, div, span, img, a, table itp.) +- NIE używaj Markdown — tylko dozwolone tagi HTML + +BEZWZGLĘDNY ZAKAZ (odrzucenie przez Google): +- Słowa promocyjne: bestseller, hit, okazja, najlepszy, polecamy, nowość, idealny +- Wykrzykniki (!), emotikony, WIELKIE LITERY +- Wezwania do działania: kup teraz, zamów, sprawdź, dodaj do koszyka +- Informacje o cenie, dostawie, promocjach, nazwie sklepu +- Opisy akcesoriów/produktów nie wchodzących w skład oferty +- Cechy wymyślone — opisuj TYLKO to co wynika z nazwy lub treści strony produktu + +' . $context_text . ' + +Zwróć TYLKO opis w formacie HTML (używając dozwolonych tagów), bez cudzysłowów, bez wyjaśnień.'; + + $tokens = $has_page ? 1500 : 1000; + return self::call_api( self::$system_prompt, $prompt, $tokens ); + } + + static public function suggest_category( $context, $categories = [] ) + { + $context_text = self::build_context_text( $context ); + + $cats_text = ''; + if ( !empty( $categories ) ) + { + $cats_list = array_slice( $categories, 0, 50 ); + $cats_text = "\n\nDostępne kategorie Google Product Taxonomy (format: id - tekst):\n"; + foreach ( $cats_list as $cat ) + { + $cats_text .= $cat['id'] . ' - ' . $cat['text'] . "\n"; + } + } + + $prompt = 'Dopasuj produkt do najlepszej kategorii Google Product Taxonomy. +Wybierz najbardziej szczegółową (najgłębszą) kategorię pasującą do produktu. + +' . $context_text . $cats_text . ' + +Zwróć TYLKO ID kategorii (liczbę), bez wyjaśnień.'; + + return self::call_api( self::$system_prompt, $prompt ); + } +} diff --git a/autoload/view/class.Clients.php b/autoload/view/class.Clients.php new file mode 100644 index 0000000..28f70d8 --- /dev/null +++ b/autoload/view/class.Clients.php @@ -0,0 +1,12 @@ + $clients, + ] ); + } +} diff --git a/autoload/view/class.Site.php b/autoload/view/class.Site.php index c52ce7b..285bc55 100644 --- a/autoload/view/class.Site.php +++ b/autoload/view/class.Site.php @@ -4,24 +4,17 @@ class Site { public static function show() { - global $user; - - $class = '\controls\\'; - - $results = explode( '_', \S::get( 'module' ) ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $class .= ucfirst( $row ); - - $action = \S::get( 'action' ); + global $user, $current_module; $tpl = new \Tpl; $tpl -> content = \controls\Site::route(); - if ( !$user ) + if ( !$user ) return $tpl -> render( 'site/layout-unlogged' ); else { $tpl -> user = $user; + $tpl -> current_module = $current_module; if ( $alert = \S::get_session( 'alert' ) ) { $tpl -> alert = $alert; diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 0000000..45dbefb --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,280 @@ +# adsPRO - System Zarządzania Reklamami Google ADS & Facebook ADS + +## Opis projektu +adsPRO to narzędzie webowe (PHP) do zarządzania i automatyzacji kampanii reklamowych Google ADS (priorytet) oraz Facebook ADS (planowane). System umożliwia monitorowanie kampanii, zarządzanie produktami, analizę wydajności (ROAS, CTR, CPC) oraz automatyczne etykietowanie produktów (bestsellery, zombie itp.). + +**URL:** https://adspro.projectpro.pl +**Hosting:** Hostido (shared hosting) + +## Stack technologiczny +- **PHP 8.x** - czyste PHP z własną strukturą MVC (bez frameworka) +- **MySQL/MariaDB** - baza danych (Medoo ORM) +- **Google ADS API** - pobieranie danych kampanii i produktów (CRON) +- **Facebook ADS API** - planowane w przyszłości +- **CRON** - automatyczna synchronizacja danych +- **SCSS** - stylowanie (kompilacja do CSS) +- **jQuery 3.6** - interaktywność frontend +- **DataTables 2.1.7** - tabele z sortowaniem, filtrowaniem, paginacją +- **Highcharts** - wykresy wydajności +- **Select2** - zaawansowane selecty +- **Font Awesome** - ikony + +## Struktura katalogów (nowa) + +``` +public_html/ +├── index.php # Front controller + nowy router +├── .htaccess # Rewrite rules +├── .env # Konfiguracja (przyszłość - migracja z config.php) +├── config.php # Konfiguracja DB (obecna) +├── ajax.php # Ajax handler +├── api.php # API handler (Google ADS webhook) +├── cron.php # CRON handler +├── robots.txt +├── layout/ +│ ├── favicon.png +│ ├── style.scss # Główne style (SCSS) +│ └── style.css # Skompilowane style +├── libraries/ # Biblioteki zewnętrzne +│ ├── medoo/ +│ ├── phpmailer/ +│ ├── select2/ +│ ├── jquery-confirm/ +│ ├── functions.js # Globalne funkcje JS +│ └── framework/ # Framework UI (skin, pluginy) +├── autoload/ # Kod PHP (MVC) +│ ├── class.S.php # Helper: sesje, requesty, narzędzia +│ ├── class.Tpl.php # Template engine +│ ├── class.Cache.php # Cache +│ ├── class.DbModel.php # Bazowy model DB +│ ├── class.Html.php # HTML helper +│ ├── controls/ # Kontrolery +│ │ ├── class.Site.php # Router (do przebudowy) +│ │ ├── class.Users.php # Logowanie, ustawienia +│ │ ├── class.Dashboard.php # NOWY - Dashboard główny +│ │ ├── class.Campaigns.php # Kampanie Google ADS +│ │ ├── class.Products.php # Produkty +│ │ ├── class.Allegro.php # Import Allegro +│ │ ├── class.Reports.php # NOWY - Raporty i analityka +│ │ ├── class.Api.php # Endpointy API +│ │ └── class.Cron.php # CRON joby +│ ├── factory/ # Modele danych (DB queries) +│ │ ├── class.Users.php +│ │ ├── class.Campaigns.php +│ │ ├── class.Products.php +│ │ └── class.Cron.php +│ └── view/ # View helpers +│ ├── class.Site.php # Renderer layoutu +│ ├── class.Users.php +│ └── class.Cron.php +├── templates/ # Szablony PHP +│ ├── site/ +│ │ ├── layout-logged.php # Layout z sidebar (PRZEBUDOWA) +│ │ └── layout-unlogged.php # Layout logowania (PRZEBUDOWA) +│ ├── auth/ +│ │ └── login.php # NOWY ekran logowania +│ ├── dashboard/ +│ │ └── index.php # NOWY dashboard +│ ├── campaigns/ +│ │ └── main_view.php # Widok kampanii +│ ├── products/ +│ │ ├── main_view.php # Lista produktów +│ │ └── product_history.php # Historia produktu +│ ├── allegro/ +│ │ └── main_view.php # Import Allegro +│ ├── reports/ # NOWE +│ │ └── index.php # Raporty +│ ├── users/ +│ │ ├── login-form.php # Stary login (do usunięcia) +│ │ └── settings.php # Ustawienia użytkownika +│ └── html/ # Komponenty HTML +│ ├── button.php +│ ├── input.php +│ ├── select.php +│ └── ... +├── tools/ +│ └── google-taxonomy.php +├── tmp/ +└── docs/ + └── PLAN.md +``` + +## Nowy system routingu + +### Zasada działania +Zamiast obecnego `?module=X&action=Y` → czyste URLe obsługiwane przez `.htaccess` + nowy router w `class.Site.php`. + +### Mapa URL + +| URL | Kontroler | Metoda | Opis | +|-----|-----------|--------|------| +| `/login` | Users | login_form | Ekran logowania | +| `/logout` | Users | logout | Wylogowanie | +| `/` | Dashboard | index | Dashboard główny | +| `/campaigns` | Campaigns | main_view | Lista kampanii | +| `/campaigns/history/{id}` | Campaigns | history | Historia kampanii | +| `/products` | Products | main_view | Lista produktów | +| `/products/history/{id}` | Products | product_history | Historia produktu | +| `/allegro` | Allegro | main_view | Import Allegro | +| `/reports` | Reports | index | Raporty | +| `/settings` | Users | settings | Ustawienia konta | +| `/api/*` | Api | * | Endpointy API | +| `/cron/*` | Cron | * | CRON joby | + +### Nowy .htaccess +```apache +RewriteEngine On +RewriteBase / + +# Statyczne zasoby - pomijaj +RewriteCond %{REQUEST_URI} ^/(libraries|layout|upload|temp)/ [NC] +RewriteRule ^ - [L] + +# Wszystko inne → index.php +RewriteCond %{REQUEST_FILENAME} !-f +RewriteCond %{REQUEST_FILENAME} !-d +RewriteRule ^(.*)$ index.php [L,QSA] +``` + +### Nowy router (class.Site.php) +```php +// Parsowanie URL z $_SERVER['REQUEST_URI'] +// Mapowanie: /segment1/segment2/segment3 → kontroler/akcja/parametry +// Fallback na dashboard dla zalogowanych, login dla niezalogowanych +``` + +## Główne funkcje + +### 1. Nowy ekran logowania +- Nowoczesny design: podzielony ekran (lewa strona - branding/grafika, prawa - formularz) +- Logo "adsPRO" z subtitlem +- Pola: email + hasło +- Checkbox "Zapamiętaj mnie" +- Walidacja AJAX +- Animacje przejścia +- Responsywność (mobile: tylko formularz) + +### 2. Nowy layout z menu bocznym (sidebar) +- **Sidebar (lewa strona, 260px):** + - Logo "adsPRO" na górze + - Menu nawigacyjne z ikonami Font Awesome: + - 📊 Dashboard (`/`) + - 📢 Kampanie (`/campaigns`) + - 📦 Produkty (`/products`) + - 📥 Allegro import (`/allegro`) + - 📈 Raporty (`/reports`) + - ⚙️ Ustawienia (`/settings`) + - Aktywny element podświetlony + - Możliwość zwijania sidebar (collapsed → same ikony, 60px) + - Na dole: info o zalogowanym użytkowniku + przycisk wylogowania +- **Top bar (nad contentem):** + - Przycisk hamburger (toggle sidebar) + - Breadcrumbs (ścieżka nawigacji) + - Szybkie akcje / notyfikacje (przyszłość) +- **Content area:** + - Pełna szerokość minus sidebar + - Padding 25px + - Tło #F4F6F9 (jaśniejsze od obecnego) + +### 3. Dashboard (NOWY) +- Kafelki podsumowujące (karty): + - Łączna liczba kampanii + - Łączna liczba produktów + - Średni ROAS (30 dni) + - Łączne wydatki (30 dni) +- Wykres trendu ROAS (ostatnie 30 dni) +- Lista ostatnio zmodyfikowanych kampanii +- Produkty wymagające uwagi (niski ROAS, zombie) + +### 4. Zarządzanie kampaniami Google ADS +- Wybór klienta (select) +- Lista kampanii z metrykami (DataTables) +- Historia kampanii z wykresem Highcharts +- Metryki: ROAS, budżet, wydatki, wartość konwersji, strategia bidding +- Usuwanie kampanii i wpisów historii +- Komentarze do kampanii + +### 5. Zarządzanie produktami +- Wybór klienta +- Konfiguracja min. ROAS dla bestsellerów +- Tabela produktów z metrykami: + - Wyświetlenia, kliknięcia, CTR, koszt, CPC + - Konwersje, wartość konwersji, ROAS + - Custom labels (bestseller/zombie/deleted/pla/paused) +- Edycja inline (min_roas, custom_label) +- Edycja produktu w modalu (tytuł, opis, kategoria Google) +- Historia produktu z wykresem +- Bulk delete zaznaczonych produktów + +### 6. Import Allegro +- Upload pliku CSV +- Automatyczne mapowanie ofert +- Raport importu (dodane, zaktualizowane) + +### 7. Raporty (NOWY - przyszłość) +- Raport wydajności kampanii +- Raport produktów (bestsellery vs zombie) +- Eksport do Excel +- Porównanie okresów + +### 8. Ustawienia +- Dane konta (email) +- Zmiana hasła +- Konfiguracja Pushover (powiadomienia) +- Klucze API (przyszłość: Google ADS, Facebook ADS) + +### 9. CRON - synchronizacja danych +- `cron_products` - synchronizacja produktów z Google ADS +- `cron_products_history_30` - historia 30-dniowa produktów +- `cron_xml` - generowanie XML +- `cron_phrases` - synchronizacja fraz +- `cron_phrases_history_30` - historia 30-dniowa fraz + +## Plan implementacji + +| Etap | Zakres | Priorytet | Pliki | +|------|--------|-----------|-------| +| **1. Nowy routing** | Przebudowa routera, nowy .htaccess, parsowanie czystych URL | 🔴 Wysoki | `.htaccess`, `index.php`, `controls/class.Site.php` | +| **2. Nowy layout (sidebar)** | Layout z bocznym menu, top bar, responsywność | 🔴 Wysoki | `templates/site/layout-logged.php`, `layout/style.scss` | +| **3. Nowy ekran logowania** | Nowoczesny split-screen login, nowy layout-unlogged | 🔴 Wysoki | `templates/site/layout-unlogged.php`, `templates/auth/login.php`, `layout/style.scss` | +| **4. Dashboard** | Nowa strona startowa z podsumowaniem | 🟡 Średni | `controls/class.Dashboard.php`, `templates/dashboard/index.php` | +| **5. Migracja kampanii** | Dostosowanie widoku kampanii do nowego routingu | 🟡 Średni | `controls/class.Campaigns.php`, `templates/campaigns/*` | +| **6. Migracja produktów** | Dostosowanie widoku produktów do nowego routingu | 🟡 Średni | `controls/class.Products.php`, `templates/products/*` | +| **7. Migracja Allegro** | Dostosowanie importu Allegro | 🟢 Niski | `controls/class.Allegro.php`, `templates/allegro/*` | +| **8. Moduł raportów** | Nowy moduł analityczny | 🟢 Niski | `controls/class.Reports.php`, `templates/reports/*` | +| **9. Facebook ADS** | Integracja z Facebook ADS API | 🔵 Przyszłość | Nowe kontrolery, factory, szablony | + +## Kolorystyka i design + +### Paleta kolorów +- **Primary (akcent):** `#6690F4` (niebieski - obecny) +- **Sidebar tło:** `#1E2A3A` (ciemny granat) +- **Sidebar tekst:** `#A8B7C7` (jasny szary) +- **Sidebar active:** `#6690F4` (primary) +- **Content tło:** `#F4F6F9` (jasnoszary) +- **Karty:** `#FFFFFF` +- **Tekst:** `#4E5E6A` (obecny) +- **Success:** `#57B951` +- **Danger:** `#CC0000` +- **Warning:** `#FF8C00` + +### Typografia +- Font: Open Sans (obecny - zachowany) +- Rozmiar bazowy: 14px (sidebar), 15px (content) + +## Bezpieczeństwo +- Hasła hashowane MD5 (obecne) → **TODO: migracja na bcrypt** +- Sesje PHP + cookie "zapamiętaj mnie" +- Prepared statements (Medoo ORM) +- htmlspecialchars() w szablonach +- **TODO: CSRF tokeny w formularzach** +- **TODO: migracja config.php → .env (z .htaccess deny)** + +## Przyszłe rozszerzenia +- Facebook ADS API - zarządzanie kampaniami FB +- System powiadomień (Pushover + in-app) +- Wielojęzyczność (PL/EN) +- Role użytkowników (admin, manager, viewer) +- Automatyczne reguły (np. "jeśli ROAS < X → zmień label na zombie") +- Integracja z Google Merchant Center +- API REST do integracji z innymi systemami diff --git a/docs/google_ads_api_design_doc.doc b/docs/google_ads_api_design_doc.doc new file mode 100644 index 0000000..c93b7ce --- /dev/null +++ b/docs/google_ads_api_design_doc.doc @@ -0,0 +1,153 @@ + + + + + + + +

adsPRO — Google Ads API Tool Design Documentation

+ +

Company: Project-Pro
+Tool Name: adsPRO
+Date: February 2026
+Version: 1.0

+ +
+ +

1. Tool Overview

+ +

adsPRO is an internal advertising management platform built for our agency to centralize and automate the management of Google Ads campaigns across multiple client accounts. The tool provides a unified dashboard for monitoring campaign performance, analyzing key metrics (ROAS, cost, conversion value), and managing campaign settings — eliminating the need to switch between multiple Google Ads accounts manually.

+ +

2. Purpose and Business Use Case

+ +

Our agency manages Google Ads campaigns for multiple clients. Currently, campaign data is collected manually or via Google Apps Script, which is fragile and hard to maintain. adsPRO replaces this workflow with a direct, reliable integration with the Google Ads API.

+ +

Key goals:

+
    +
  • Automatically retrieve campaign performance data for all managed client accounts
  • +
  • Display historical trends (ROAS, budget, spend, conversions) in a centralized dashboard
  • +
  • Enable campaign management operations (budget adjustments, bidding strategy changes, campaign status updates) directly from the platform
  • +
  • Reduce manual work and improve response time for campaign optimization
  • +
+ +

3. Google Ads API Usage

+ +

3.1 Data Retrieval (Read Operations)

+ +

The tool uses the GoogleAdsService.SearchStream endpoint to fetch campaign data using GAQL (Google Ads Query Language).

+ +

Data retrieved:

+
    +
  • Campaign name, ID, and status
  • +
  • Bidding strategy type and target ROAS
  • +
  • Campaign budget (daily)
  • +
  • Cost (spend) over the last 30 days
  • +
  • Conversion value over the last 30 days
  • +
  • All-time ROAS calculation
  • +
+ +

GAQL queries used:

+ +
SELECT campaign.id, campaign.name, campaign.bidding_strategy_type,
+       campaign.target_roas.target_roas, campaign_budget.amount_micros,
+       metrics.cost_micros, metrics.conversions_value
+FROM campaign
+WHERE campaign.status = 'ENABLED'
+  AND segments.date DURING LAST_30_DAYS
+ +
SELECT campaign.id, metrics.cost_micros, metrics.conversions_value
+FROM campaign
+WHERE campaign.status = 'ENABLED'
+ +

3.2 Campaign Management (Write Operations)

+ +

The tool will also support campaign management operations through the Google Ads API:

+ +
    +
  • Budget adjustments — updating campaign_budget.amount_micros via CampaignBudgetService.MutateCampaignBudgets
  • +
  • Bidding strategy changes — modifying bidding strategy type and target ROAS via CampaignService.MutateCampaigns
  • +
  • Campaign status updates — enabling/pausing campaigns via CampaignService.MutateCampaigns
  • +
+ +

All write operations are initiated manually by authorized agency staff through the adsPRO interface. No automated modifications are made without human approval.

+ +

4. API Request Frequency

+ +
    +
  • Automated data retrieval: Once per day via server-side CRON job (scheduled at 06:00 CET)
  • +
  • Campaign management operations: On-demand, initiated manually by agency staff (estimated 10–50 requests per day)
  • +
  • Number of client accounts: Currently under 20, expected to grow to ~50
  • +
  • Estimated total daily API calls: Under 200 requests per day
  • +
+ +

5. Authentication and Authorization

+ +
    +
  • OAuth 2.0 authentication with offline access (refresh token flow)
  • +
  • Credentials stored securely in a server-side database (not exposed to end users)
  • +
  • Access token is refreshed automatically when expired
  • +
  • Optional Manager Account (MCC) support for centralized access to client accounts
  • +
+ +

6. Architecture

+ +
[CRON - daily at 06:00]
+        |
+        v
+[adsPRO Server (PHP)]
+        |
+        v
+[Google Ads REST API v18]
+   - SearchStream (read campaign data)
+   - MutateCampaigns (manage campaigns)
+   - MutateCampaignBudgets (adjust budgets)
+        |
+        v
+[MySQL Database]
+   - campaigns table (current state)
+   - campaigns_history table (daily snapshots)
+        |
+        v
+[adsPRO Dashboard]
+   - Campaign performance charts
+   - ROAS tracking over time
+   - Campaign management interface
+ +

7. Data Handling and Privacy

+ +
    +
  • All data is stored on our own private server (shared hosting with SSL)
  • +
  • Data is accessible only to authenticated agency staff (login required)
  • +
  • No campaign data is shared with third parties
  • +
  • No personally identifiable information (PII) is collected from end users
  • +
  • API credentials are stored server-side and never exposed in client-facing code
  • +
+ +

8. Users

+ +

This is an internal agency tool. It is not a publicly available application. Access is restricted to authorized staff members of our agency (currently 3 users). There is no self-registration — accounts are created by the administrator.

+ +

9. Compliance

+ +
    +
  • The tool complies with the Google Ads API Terms of Service
  • +
  • The tool complies with the Google API Services User Data Policy
  • +
  • No data is sold or shared with third parties
  • +
  • The tool does not perform automated campaign modifications without human oversight
  • +
+ + + diff --git a/docs/google_ads_api_design_doc.md b/docs/google_ads_api_design_doc.md new file mode 100644 index 0000000..0ea2409 --- /dev/null +++ b/docs/google_ads_api_design_doc.md @@ -0,0 +1,122 @@ +# adsPRO — Google Ads API Tool Design Documentation + +**Company:** Project-Pro +**Tool Name:** adsPRO +**Date:** February 2026 +**Version:** 1.0 + +--- + +## 1. Tool Overview + +adsPRO is an internal advertising management platform built for our agency to centralize and automate the management of Google Ads campaigns across multiple client accounts. The tool provides a unified dashboard for monitoring campaign performance, analyzing key metrics (ROAS, cost, conversion value), and managing campaign settings — eliminating the need to switch between multiple Google Ads accounts manually. + +## 2. Purpose and Business Use Case + +Our agency manages Google Ads campaigns for multiple clients. Currently, campaign data is collected manually or via Google Apps Script, which is fragile and hard to maintain. adsPRO replaces this workflow with a direct, reliable integration with the Google Ads API. + +**Key goals:** +- Automatically retrieve campaign performance data for all managed client accounts +- Display historical trends (ROAS, budget, spend, conversions) in a centralized dashboard +- Enable campaign management operations (budget adjustments, bidding strategy changes, campaign status updates) directly from the platform +- Reduce manual work and improve response time for campaign optimization + +## 3. Google Ads API Usage + +### 3.1 Data Retrieval (Read Operations) + +The tool uses the **GoogleAdsService.SearchStream** endpoint to fetch campaign data using GAQL (Google Ads Query Language). + +**Data retrieved:** +- Campaign name, ID, and status +- Bidding strategy type and target ROAS +- Campaign budget (daily) +- Cost (spend) over the last 30 days +- Conversion value over the last 30 days +- All-time ROAS calculation + +**GAQL queries used:** + +``` +SELECT campaign.id, campaign.name, campaign.bidding_strategy_type, + campaign.target_roas.target_roas, campaign_budget.amount_micros, + metrics.cost_micros, metrics.conversions_value +FROM campaign +WHERE campaign.status = 'ENABLED' + AND segments.date DURING LAST_30_DAYS +``` + +``` +SELECT campaign.id, metrics.cost_micros, metrics.conversions_value +FROM campaign +WHERE campaign.status = 'ENABLED' +``` + +### 3.2 Campaign Management (Write Operations) + +The tool will also support campaign management operations through the Google Ads API: + +- **Budget adjustments** — updating `campaign_budget.amount_micros` via `CampaignBudgetService.MutateCampaignBudgets` +- **Bidding strategy changes** — modifying bidding strategy type and target ROAS via `CampaignService.MutateCampaigns` +- **Campaign status updates** — enabling/pausing campaigns via `CampaignService.MutateCampaigns` + +All write operations are initiated manually by authorized agency staff through the adsPRO interface. No automated modifications are made without human approval. + +## 4. API Request Frequency + +- **Automated data retrieval:** Once per day via server-side CRON job (scheduled at 06:00 CET) +- **Campaign management operations:** On-demand, initiated manually by agency staff (estimated 10–50 requests per day) +- **Number of client accounts:** Currently under 20, expected to grow to ~50 +- **Estimated total daily API calls:** Under 200 requests per day + +## 5. Authentication and Authorization + +- OAuth 2.0 authentication with offline access (refresh token flow) +- Credentials stored securely in a server-side database (not exposed to end users) +- Access token is refreshed automatically when expired +- Optional Manager Account (MCC) support for centralized access to client accounts + +## 6. Architecture + +``` +[CRON - daily at 06:00] + | + v +[adsPRO Server (PHP)] + | + v +[Google Ads REST API v18] + - SearchStream (read campaign data) + - MutateCampaigns (manage campaigns) + - MutateCampaignBudgets (adjust budgets) + | + v +[MySQL Database] + - campaigns table (current state) + - campaigns_history table (daily snapshots) + | + v +[adsPRO Dashboard] + - Campaign performance charts + - ROAS tracking over time + - Campaign management interface +``` + +## 7. Data Handling and Privacy + +- All data is stored on our own private server (shared hosting with SSL) +- Data is accessible only to authenticated agency staff (login required) +- No campaign data is shared with third parties +- No personally identifiable information (PII) is collected from end users +- API credentials are stored server-side and never exposed in client-facing code + +## 8. Users + +This is an **internal agency tool**. It is not a publicly available application. Access is restricted to authorized staff members of our agency (currently 3 users). There is no self-registration — accounts are created by the administrator. + +## 9. Compliance + +- The tool complies with the Google Ads API Terms of Service +- The tool complies with the Google API Services User Data Policy +- No data is sold or shared with third parties +- The tool does not perform automated campaign modifications without human oversight diff --git a/index.php b/index.php index 67470c2..6286a49 100644 --- a/index.php +++ b/index.php @@ -30,6 +30,53 @@ $mdb = new medoo([ 'charset' => 'utf8' ]); +// --- Nowy router --- +$request_uri = $_SERVER['REQUEST_URI']; +$uri = parse_url($request_uri, PHP_URL_PATH); +$uri = trim($uri, '/'); +$segments = $uri ? explode('/', $uri, 3) : []; + +// Aliasy czystych URL na moduł/akcję +$route_aliases = [ + 'login' => ['users', 'login_form'], + 'logowanie' => ['users', 'login_form'], + 'logout' => ['users', 'logout'], + 'settings' => ['users', 'settings'], + 'settings/save' => ['users', 'settings_save'], + 'settings/save_google_ads' => ['users', 'settings_save_google_ads'], + 'settings/save_openai' => ['users', 'settings_save_openai'], + 'products/ai_suggest' => ['products', 'ai_suggest'], + 'clients/save' => ['clients', 'save'], +]; + +$path = implode('/', $segments); +$path_first = $segments[0] ?? ''; + +if (isset($route_aliases[$path])) { + $_GET['module'] = $route_aliases[$path][0]; + $_GET['action'] = $route_aliases[$path][1]; +} elseif (isset($route_aliases[$path_first])) { + $_GET['module'] = $route_aliases[$path_first][0]; + $_GET['action'] = $route_aliases[$path_first][1]; +} elseif (count($segments) >= 2) { + $_GET['module'] = $segments[0]; + $_GET['action'] = $segments[1]; + if (isset($segments[2])) { + parse_str($segments[2], $extra); + $_GET = array_merge($_GET, $extra); + } +} elseif (count($segments) === 1 && $segments[0] !== '') { + $_GET['module'] = $segments[0]; + $_GET['action'] = 'main_view'; +} else { + $_GET['module'] = 'campaigns'; + $_GET['action'] = 'main_view'; +} + +// Aktualny moduł do podświetlenia w sidebar +$current_module = $_GET['module'] ?? ''; + +// --- Autoryzacja --- $domain = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] ); $cookie_name = str_replace( '.', '-', $domain ); @@ -46,32 +93,25 @@ if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) ) } $user = \S::get_session('user'); -if ( - !$user -and - !( - in_array( $_SERVER['REQUEST_URI'], [ '/logowanie', '/users/login/' ] ) - or - strpos( $_SERVER['REQUEST_URI'], '/api/campaigns_data_save/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/api/phrases_data_save/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/api/products_data_save/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/cron/cron_products/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/cron/cron_products_history_30/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/cron/cron_xml/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/cron/cron_phrases/' ) !== false - or - strpos( $_SERVER['REQUEST_URI'], '/cron/cron_phrases_history_30/' ) !== false - ) -) + +// Whitelist - strony dostępne bez logowania +$public_paths = ['login', 'logowanie', 'users/login', 'users/login_form']; +$public_prefixes = ['api/', 'cron/']; + +$is_public = in_array($path, $public_paths) + || in_array($path_first . '/' . ($segments[1] ?? ''), $public_paths); + +foreach ($public_prefixes as $prefix) { + if (strpos($path, $prefix) === 0) { + $is_public = true; + break; + } +} + +if (!$user && !$is_public) { - header( 'Location: /logowanie' ); + header( 'Location: /login' ); exit; } -echo \view\Site::show(); \ No newline at end of file +echo \view\Site::show(); diff --git a/layout/style-old.css b/layout/style-old.css new file mode 100644 index 0000000..54d1bcb --- /dev/null +++ b/layout/style-old.css @@ -0,0 +1 @@ +.animate{animation:mymove 3s infinite}.text-right{text-align:right}.text-bold{font-weight:900 !important}.nowrap{white-space:nowrap}table{border-collapse:collapse}small{font-size:.75em}table{font-size:13px}@keyframes mymove{50%{opacity:.33}}@keyframes gradient-animation{0%{background-position:15% 0%}50%{background-position:85% 100%}100%{background-position:15% 0%}}@keyframes frame-enter{0%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), 3px calc(100% - 3px), 3px 100%, 100% 100%, 100% 0%, 0% 0%)}25%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%, 100% 100%, 100% 0%, 0% 0%)}50%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, 100% 0%, 0% 0%)}75%{-webkit-clip-path:polygon(0% 100%, 3px 100%, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 0%, 0% 0%)}100%{-webkit-clip-path:polygon(0% 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 0% 100%)}}*{box-sizing:border-box}body{font-family:"Open Sans",sans-serif;margin:0;padding:0;font-size:15px;color:#4e5e6a}.btn{padding:12px 25px;transition:all .3s ease;color:#fff;border:0;border-radius:6px;cursor:pointer;display:inline-flex;text-decoration:none;gap:5px;justify-content:center;align-items:center}.btn.btn_small,.btn.btn-xs{padding:5px 7px;font-size:13px}.btn.btn_small i,.btn.btn-xs i{font-size:12px}.btn.btn-success{background:#57b951}.btn.btn-success:hover{background:#4a9c3b}.btn.btn-primary{background:#6690f4}.btn.btn-primary:hover{background:#3164db}.btn.btn-danger{background:#c00}.btn.btn-danger:hover{background:#b30000}.hide{display:none}.form-error{color:#c00;font-size:13px}.input-group{margin-bottom:10px}input[type=checkbox]{border:1px solid #eee}.form-control{border:1px solid #eee;border-radius:3px;height:35px;width:100%;padding:5px;font-family:"Open Sans",sans-serif}.form-control option{padding:5px}.form-control:focus{border:1px solid #6690f4;outline:none}.unlogged{background:#eef1f9;display:flex;align-items:center;justify-content:center;height:100vh}.unlogged .box-login{background:#fff;padding:25px;border-radius:6px;width:400px}.unlogged .box-login .title{text-align:center;padding:10px 10px 25px;border-bottom:1px solid #eee;font-size:20px;margin-bottom:25px}body>.top{background:#fff;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center}body>.top .logo a{display:inline-flex;color:#6690f4;padding:10px;text-decoration:none}body>.top .logo a span{font-weight:600}body>.top .user-nav{position:relative;font-size:14px;padding:10px}body>.top .user-nav .trigger{cursor:pointer}body>.top .user-nav .trigger i{color:#6690f4}body>.top .user-nav ul{position:absolute;top:100%;left:0;background:#fff;margin:0;padding:0;width:100%;list-style-type:none;border:1px solid #eee;display:none}body>.top .user-nav ul li{cursor:pointer}body>.top .user-nav ul li:hover{background:#f8f9fa}body>.top .user-nav ul li a{color:#333;display:block;text-decoration:none;padding:10px}body>.top .user-nav:hover ul{display:block}.main-menu{display:flex;box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.main-menu ul{display:flex;list-style-type:none;margin:0;padding:0;font-size:14px}.main-menu ul li{cursor:pointer}.main-menu ul li a{color:#333;text-decoration:none;display:inline-flex;padding:10px 15px}.main-menu ul li:hover a{background:#6690f4;color:#fff}.main-menu ul li ul{display:none}.main{padding:25px;background:#d9dee2;min-height:calc(100vh - 80px)}.tasks_container{display:flex;flex-wrap:wrap;gap:20px}.tasks_container .column{width:350px}.tasks_container .column h2{display:flex;padding:10px;background:#fff;margin-bottom:10px;font-size:15px;font-weight:300;border-radius:3px 3px 0 0;justify-content:space-between;align-items:center}.tasks_container .column h2 i{cursor:pointer}.tasks_container .column.tasks_suspended h2{border-bottom:5px solid #c00}.tasks_container .column.tasks_new h2{border-bottom:5px solid #ccc}.tasks_container .column.tasks_bulk h2{border-bottom:5px solid #ff8c00}.tasks_container .column.tasks_to_do h2{border-bottom:5px solid #2aaf47}.tasks_container .column.tasks_to_review h2{border-bottom:5px solid #2535c9}.tasks_container .column.tasks_closed h2{border-bottom:5px solid #000}.tasks_container .column ul{list-style-type:none;margin:0;padding:0}.tasks_container .column ul .task{margin-bottom:5px;background:#fff;padding:10px;border-radius:0;display:flex;position:relative}.tasks_container .column ul .task.notopened{border:2px solid #c00}.tasks_container .column ul .task .left{width:30px}.tasks_container .column ul .task .left .users{display:flex;gap:5px;flex-wrap:wrap}.tasks_container .column ul .task .left .users .user{display:flex;width:20px;height:20px;border-radius:50%;justify-content:center;align-items:center;color:#fff;font-size:13px}.tasks_container .column ul .task .middle{width:calc(100% - 60px)}.tasks_container .column ul .task .right{width:30px;display:flex;flex-wrap:wrap;justify-content:flex-end}.tasks_container .column ul .task .right .recursively{color:#ccc;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .recursively i{font-size:15px}.tasks_container .column ul .task .right .task_start{background:#57b951;color:#fff;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .task_start.hidden{display:none}.tasks_container .column ul .task .right .task_start i{font-size:12px}.tasks_container .column ul .task .right .task_end{background:#c00;color:#fff;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .task_end.hidden{display:none}.tasks_container .column ul .task .right .task_end i{font-size:12px}.tasks_container .column ul .task .name{font-size:14px;color:#333;text-decoration:none;display:block;margin-bottom:5px}.tasks_container .column ul .task .bottom{display:flex;justify-content:space-between}.tasks_container .column ul .task .client_info,.tasks_container .column ul .task .current_status{font-size:12px;font-weight:400}.tasks_container .column ul .task .client_info strong,.tasks_container .column ul .task .current_status strong{font-weight:600}.tasks_container .column ul .task .current_status{position:relative;cursor:pointer}.tasks_container .column ul .task .current_status .status_change{position:absolute;left:0;top:20px;background:#fff;padding:15px;border:1px solid #dfdfdf;border-radius:3px;cursor:pointer;box-shadow:0 0 15px rgba(0,0,0,.1);z-index:99;display:none}.tasks_container .column ul .task .current_status .status_change select{width:250px;padding:10px;border:1px solid #eee;border-radius:3px}.tasks_container .column ul .task .current_status .status_change select option{font-size:15px;padding:3px}.tasks_container .column ul .task .dates{margin-bottom:5px;display:flex;justify-content:space-between;font-size:12px}.tasks_container .column ul .task .dates .danger{color:#c00;font-weight:600}.tasks_container .column ul .task .dates .warning{color:#ff8c00}.tasks_container .column ul .task .dates i{font-size:12px;color:#c9ced4;margin-right:5px}.action_menu{display:flex;margin-bottom:25px;gap:20px}.action_menu .btn{display:inline-flex;padding:7px 15px;color:#fff;border-radius:3px;text-decoration:none;align-items:center;justify-content:center;gap:5px}.action_menu .btn.btn_add{background:#57b951}.action_menu .btn.btn_add:hover{background:#4a9c3b}.action_menu .btn.btn_cancel{background:#c00}.action_menu .btn.btn_cancel:hover{background:#b30000}.action_menu .btn.disabled{opacity:.5}.form_container{background:#fff;padding:25px;max-width:1300px}.form_container.full{max-width:100%}.form_container .form_group{margin-bottom:10px;display:flex}.form_container .form_group>.label{width:300px;display:inline-flex;align-items:flex-start;justify-content:right;padding-right:10px}.form_container .form_group .input{width:calc(100% - 300px)}.task_popup{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none}.task_popup .task_details{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:#fff;padding:25px;border-radius:6px;max-width:1140px;width:100%}.task_popup .task_details .title{font-size:20px;margin-bottom:25px}.task_popup .task_details .title a{color:#333;text-decoration:none;margin-right:10px}.task_popup .task_details .title a.task-delete{color:#c00}.task_popup .task_details .close{position:absolute;top:10px;right:10px;cursor:pointer}.task_popup .task_details .content{display:flex;font-size:14px}.task_popup .task_details .content h3{width:100%;margin-top:0;margin-bottom:5px;font-weight:500;color:#000;font-size:17px}.task_popup .task_details .content .left{width:70%;max-height:700px;overflow-y:auto}.task_popup .task_details .content .left .users{display:flex;gap:20px}.task_popup .task_details .content .left .users .user{display:flex;gap:10px;align-items:center;margin-bottom:10px}.task_popup .task_details .content .left .users .user .avatar{height:30px;width:30px;border-radius:50%;background:#ccc;display:flex;justify-content:center;align-items:center;color:#fff}.task_popup .task_details .content .left .comments{border-radius:3px;padding:0 15px 15px 0;margin-bottom:15px;border-bottom:1px solid #eee}.task_popup .task_details .content .left .comments .new_comment{margin-bottom:15px}.task_popup .task_details .content .left .comments .new_comment textarea{height:75px}.task_popup .task_details .content .left .comments .new_comment .add_comment{background:#57b951;color:#fff;padding:10px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;width:200px;text-decoration:none}.task_popup .task_details .content .left .comments .new_comment .add_comment:hover{background:#4a9c3b}.task_popup .task_details .content .left .comments ul{margin:0;padding:0;list-style-type:none}.task_popup .task_details .content .left .comments ul li{background:#eee;margin-bottom:5px;padding:15px;border-radius:6px;position:relative}.task_popup .task_details .content .left .comments ul li .delete_comment{position:absolute;top:10px;right:10px;cursor:pointer;color:#c00}.task_popup .task_details .content .left .comments ul li .author{font-weight:600;margin-bottom:5px;display:inline-flex;margin-right:10px}.task_popup .task_details .content .left .comments ul li .date{font-size:12px;margin-bottom:5px;display:inline-flex}.task_popup .task_details .content .left .comments ul li .text{margin-bottom:15px;font-size:13px}.task_popup .task_details .content .left .checklist{border-radius:3px;padding:0 15px 15px 0;margin-bottom:15px;border-bottom:1px solid #eee}.task_popup .task_details .content .left .checklist .new_element{display:flex;margin-bottom:15px}.task_popup .task_details .content .left .checklist .new_element a{display:flex;align-items:center;justify-content:center;padding:10px;border-radius:0 6px 6px 0;text-decoration:none;width:35px;background:#57b951;color:#fff}.task_popup .task_details .content .left .checklist ul{margin:0;padding:0;list-style-type:none}.task_popup .task_details .content .left .checklist ul li{display:flex;gap:10px;margin-bottom:5px;background:#fff;border-radius:3px;padding:5px;border:1px solid #eee;font-size:13px;align-items:center}.task_popup .task_details .content .left .checklist ul li i{margin-left:auto;margin-right:0;cursor:pointer;color:#c00}.task_popup .task_details .content .left .description{padding:15px;border-radius:3px;background:#f6f8f9;margin-bottom:15px}.task_popup .task_details .content .right{width:30%;padding:0 15px 15px}.task_popup .task_details .content .right .box{margin-bottom:15px}.task_popup .task_details .content .right .time a{display:block;padding:10px;border-radius:6px;margin-top:10px;text-decoration:none;text-align:center;width:100%}.task_popup .task_details .content .right .time a.task_start{background:#57b951;color:#fff}.task_popup .task_details .content .right .time a.task_end{background:#c00;color:#fff}.task_popup .task_details .content .right .time a.hidden{display:none}.task_popup .task_details .content .right .dates{display:flex;justify-content:space-between;flex-wrap:wrap}.task_popup .task_details .content .right .dates .danger{color:#c00;font-weight:600}.task_popup .task_details .content .right .dates .warning{color:#ff8c00}.task_popup .task_details .content .right .dates i{color:#c9ced4;margin-right:5px}.table{width:100%}.table th,.table td{border:1px solid #eee;padding:5px}.table td.center{text-align:center}.table td.left{text-align:left}.table.table-sm td{padding:5px !important}.table input.form-control{font-size:13px}.projects_container{background:#fff;padding:15px;border-radius:6px;display:flex;gap:20px}.projects_container .left{display:flex;gap:20px;flex-wrap:wrap;width:calc(100% - 250px)}.projects_container .right{width:250px;display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;gap:20px;border-left:1px solid #eee;padding-left:15px}.projects_container .right select{width:200px}.default_popup{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none}.default_popup .popup_content{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:#fff;padding:25px;border-radius:6px;max-width:1140px;width:100%}.default_popup .popup_content .close{position:absolute;top:10px;right:10px;cursor:pointer}#fg-cron{margin:10px 0}#fg-cron .countdown{background:#57b951;color:#fff;padding:10px}#fg-cron #cron-container{max-height:300px;overflow-x:hidden;overflow-y:auto}#fg-cron #cron-container .msg{font-size:13px;padding:5px;border-bottom:1px solid #e8e8e8}.card{background:#fff;padding:25px;border-radius:6px;color:#000;font-size:15px;max-width:1280px}.card.mb25{margin-bottom:25px}.card .card-header{font-weight:600}.card .card-body{padding-top:10px}.card .card-body table{border-collapse:collapse}.card .card-body table th,.card .card-body table td{font-size:14px}.card .card-body table th.bold,.card .card-body table td.bold{font-weight:600}.card .card-body table th.text-right,.card .card-body table td.text-right{text-align:right}.card .card-body table th.text-center,.card .card-body table td.text-center{text-align:center}.card .card-body table th .close-task,.card .card-body table td .close-task{text-decoration:none;color:#6690f4}.card .card-body table th .close-task:hover,.card .card-body table td .close-task:hover{color:#c00}.finance-summary{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px}.finance-summary .panel{background:#fff;border-radius:6px;padding:15px}.finance-summary .panel h1{font-size:20px;margin:0}.finance-summary .panel span{font-size:.85em}.finance-manager{display:grid;gap:10px;grid-template-columns:200px 1fr 500px;padding-top:25px}.finance-manager .manage-menu{display:inline-block;margin-right:10px}.finance-manager .manage-menu a{color:#333;text-decoration:none;font-weight:300;display:block}.finance-manager .actions{width:100px;text-align:center}.finance-manager .actions a{display:inline-flex;margin:0 2px;height:25px;width:25px;align-items:center;justify-content:center;text-decoration:none;color:#000;border:1px solid #e7e7e7;font-size:11px;border-radius:3px;transition:all .3s ease}.finance-manager .actions a:hover{border:1px solid #6690f4}.bootstrap-tagsinput .tag{background:#6690f4;font-size:13px;padding:5px 10px;border-radius:12px}.bootstrap-tagsinput .tag [data-role=remove]{color:#fff !important}.bootstrap-tagsinput .tag [data-role=remove]:hover{box-shadow:none !important;color:#000 !important}.finance-tags{display:flex;flex-wrap:wrap;gap:5px}.finance-tags a:not(.btn){display:flex;width:100%;text-decoration:none;color:#4e5e6a}.finance-tags a:not(.btn).zoom-100{font-size:130%}.finance-tags a:not(.btn).zoom-90{font-size:120%}.finance-tags a:not(.btn).zoom-80{font-size:110%}.finance-tags a:not(.btn).zoom-70{font-size:100%}.finance-tags a:not(.btn).zoom-60{font-size:95%}.finance-tags a:not(.btn).zoom-50{font-size:85%}.finance-tags a:not(.btn).zoom-40{font-size:80%}.finance-tags a:not(.btn).zoom-30{font-size:75%}.finance-tags a:not(.btn).zoom-20{font-size:75%}.finance-tags a:not(.btn).zoom-10{font-size:70%}.finance-tags a:not(.btn).zoom-0{font-size:70%}.manage-menu{position:relative}.manage-menu .context-menu{border-left:4px dotted #000;width:1px;height:100%}.manage-menu .context-menu-container{position:absolute;display:none;background:#fff;box-shadow:5px 5px 15px rgba(0,0,0,.1);top:2px;left:2px}.manage-menu .context-menu-container ul{list-style-type:none;margin:0;padding:0}.manage-menu .context-menu-container ul li a{display:block;padding:7px 15px;white-space:nowrap}.manage-menu .context-menu-container ul li a:hover{background:#f8f8f8}.manage-menu:hover .context-menu-container{display:block}.dt-layout-table{margin-bottom:25px}.pagination button{border:1px solid #eee;background:#fff;display:inline-flex;height:30px;width:30px;align-items:center;justify-content:center;margin:0 2px;transition:all .3s ease}.pagination button:hover{background:#eee}table#products .table-product-title{display:flex;justify-content:space-between}table#products .edit-product-title{display:flex;height:25px;align-items:center;justify-content:center;width:25px;cursor:pointer;background:#fff;border:1px solid #9b9b9b;color:#9b9b9b}table#products .edit-product-title:hover{background:#9b9b9b;color:#fff}table#products a.custom_name{color:#57b951 !important}.chart-with-form{display:flex;gap:20px;align-items:flex-start}.chart-area{flex:1 1 auto;min-width:0}.comment-form{width:360px;flex:0 0 360px}.comment-form .form-group{margin-bottom:12px}.comment-form label{display:block;font-weight:600;margin-bottom:6px}.comment-form input[type=date],.comment-form textarea{width:100%;border:1px solid #ccc;border-radius:4px;padding:0 8px;font-size:14px}.comment-form textarea{min-height:120px;resize:vertical}.comment-form .btn{display:inline-block;padding:8px 14px;border-radius:4px;border:0;background:#337ab7;color:#fff;font-weight:600;cursor:pointer}.comment-form .btn[disabled]{opacity:.6;cursor:not-allowed}.comment-form .hint{font-size:12px;color:#666}.jconfirm-box .form-group .select2-container{width:100% !important;margin-top:8px}.jconfirm-box .select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #ced4da;border-radius:3px;min-height:42px;display:flex;align-items:center;padding:4px 12px;box-shadow:none;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;font-size:14px}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered{padding-left:0;line-height:1.4;color:#495057}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder{color:#adb5bd}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow{height:100%;right:8px}.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,.jconfirm-box .select2-container--default .select2-selection--single:hover{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25);outline:0}.jconfirm-box .select2-container .select2-dropdown{border-color:#ced4da;border-radius:0 0 3px 3px;font-size:14px}.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field{padding:6px 10px;border-radius:3px;border:1px solid #ced4da;font-size:14px}.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#007bff;color:#fff}/*# sourceMappingURL=style.css.map */ \ No newline at end of file diff --git a/layout/style-old.scss b/layout/style-old.scss new file mode 100644 index 0000000..eddb6a8 --- /dev/null +++ b/layout/style-old.scss @@ -0,0 +1,1409 @@ +$cBlue: #6690F4; +$cRed: #aa0505; +$cGreen: #43833f; +$cGreenLight: #57b951; +$cBlack: #4e5e6a; + +.animate { + animation: mymove 3s infinite; +} + +.text-right { + text-align: right; +} + +.text-bold { + font-weight: 900 !important; +} + +.nowrap { + white-space: nowrap; +} + +table { + border-collapse: collapse; +} + +small { + font-size: .75em; +} + +table { + font-size: 13px; +} + +@keyframes mymove { + 50% { + opacity: .33; + } +} + +/* motion */ +@keyframes gradient-animation { + 0% { + background-position: 15% 0%; + } + + 50% { + background-position: 85% 100%; + } + + 100% { + background-position: 15% 0%; + } +} + +@keyframes frame-enter { + 0% { + clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), 3px calc(100% - 3px), 3px 100%, 100% 100%, 100% 0%, 0% 0%); + } + + 25% { + clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%, 100% 100%, 100% 0%, 0% 0%); + } + + 50% { + clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, 100% 0%, 0% 0%); + } + + 75% { + -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 0%, 0% 0%); + } + + 100% { + -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 0% 100%); + } +} + +* { + box-sizing: border-box; +} + +body { + font-family: "Open Sans", sans-serif; + margin: 0; + padding: 0; + font-size: 15px; + color: #4e5e6a; +} + +.btn { + padding: 12px 25px; + transition: all 0.3s ease; + color: #FFF; + border: 0; + border-radius: 6px; + cursor: pointer; + display: inline-flex; + text-decoration: none; + gap: 5px; + justify-content: center; + align-items: center; + + &.btn_small, + &.btn-xs { + padding: 5px 7px; + font-size: 13px; + + i { + font-size: 12px; + } + } + + &.btn-success { + background: #57b951; + + &:hover { + background: #4a9c3b; + } + } + + &.btn-primary { + background: $cBlue; + + &:hover { + background: #3164db; + } + } + + &.btn-danger { + background: #cc0000; + + &:hover { + background: #b30000; + } + } +} + +.hide { + display: none; +} + +.form-error { + color: #cc0000; + font-size: 13px; +} + +.input-group { + margin-bottom: 10px; +} + +input[type="checkbox"] { + border: 1px solid #eee; +} + +.form-control { + border: 1px solid #eee; + border-radius: 3px; + height: 35px; + width: 100%; + padding: 5px; + font-family: "Open Sans", sans-serif; + + option { + padding: 5px; + } + + &:focus { + border: 1px solid $cBlue; + outline: none; + } +} + +.unlogged { + background: #EEF1F9; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + + .box-login { + background: #FFF; + padding: 25px; + border-radius: 6px; + width: 400px; + + .title { + text-align: center; + padding: 10px 10px 25px; + border-bottom: 1px solid #eee; + font-size: 20px; + margin-bottom: 25px; + } + } +} + +body>.top { + background: #FFF; + border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + + .logo { + a { + display: inline-flex; + color: $cBlue; + padding: 10px; + text-decoration: none; + + span { + font-weight: 600; + } + } + } + + .user-nav { + position: relative; + font-size: 14px; + padding: 10px; + + .trigger { + cursor: pointer; + + i { + color: $cBlue; + } + } + + ul { + position: absolute; + top: 100%; + left: 0; + background: #FFF; + margin: 0; + padding: 0; + width: 100%; + list-style-type: none; + border: 1px solid #eee; + display: none; + + li { + cursor: pointer; + + &:hover { + background: #F8F9FA; + } + + a { + color: #333; + display: block; + text-decoration: none; + padding: 10px; + } + } + } + + &:hover { + ul { + display: block; + } + } + } +} + +.main-menu { + display: flex; + box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075) !important; + + ul { + display: flex; + list-style-type: none; + margin: 0; + padding: 0; + font-size: 14px; + + li { + cursor: pointer; + + a { + color: #333; + text-decoration: none; + display: inline-flex; + padding: 10px 15px; + } + + &:hover { + a { + background: $cBlue; + color: #FFF; + } + } + + ul { + display: none; + } + } + } +} + +.main { + padding: 25px; + background: #D9DEE2; + min-height: calc(100vh - 80px); +} + +.tasks_container { + display: flex; + flex-wrap: wrap; + gap: 20px; + + .column { + width: 350px; + + h2 { + display: flex; + padding: 10px; + background: #FFF; + margin-bottom: 10px; + font-size: 15px; + font-weight: 300; + border-radius: 3px 3px 0 0; + justify-content: space-between; + align-items: center; + + i { + cursor: pointer; + } + } + + &.tasks_suspended { + h2 { + border-bottom: 5px solid #cc0000; + } + } + + &.tasks_new { + h2 { + border-bottom: 5px solid #ccc; + } + } + + &.tasks_bulk { + h2 { + border-bottom: 5px solid #ff8c00; + } + } + + &.tasks_to_do { + h2 { + border-bottom: 5px solid #2aaf47; + } + } + + &.tasks_to_review { + h2 { + border-bottom: 5px solid #2535c9; + } + } + + &.tasks_closed { + h2 { + border-bottom: 5px solid #000; + } + } + + ul { + list-style-type: none; + margin: 0; + padding: 0; + + .task { + margin-bottom: 5px; + background: #FFF; + padding: 10px; + border-radius: 0; + display: flex; + position: relative; + + &.notopened { + border: 2px solid #cc0000; + } + + .left { + width: 30px; + + .users { + display: flex; + gap: 5px; + flex-wrap: wrap; + + .user { + display: flex; + width: 20px; + height: 20px; + border-radius: 50%; + justify-content: center; + align-items: center; + color: #FFF; + font-size: 13px; + } + } + } + + .middle { + width: calc(100% - 60px); + } + + .right { + width: 30px; + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + + .recursively { + color: #ccc; + border-radius: 3px; + cursor: pointer; + margin-bottom: 5px; + width: 22px; + height: 22px; + text-align: center; + + i { + font-size: 15px; + } + } + + .task_start { + background: #57b951; + color: #FFF; + border-radius: 3px; + cursor: pointer; + margin-bottom: 5px; + width: 22px; + height: 22px; + text-align: center; + + &.hidden { + display: none; + } + + i { + font-size: 12px; + } + } + + .task_end { + background: #cc0000; + color: #FFF; + border-radius: 3px; + cursor: pointer; + margin-bottom: 5px; + width: 22px; + height: 22px; + text-align: center; + + &.hidden { + display: none; + } + + i { + font-size: 12px; + } + } + } + + .name { + font-size: 14px; + color: #333; + text-decoration: none; + display: block; + margin-bottom: 5px; + } + + .bottom { + display: flex; + justify-content: space-between; + } + + .client_info, + .current_status { + font-size: 12px; + font-weight: 400; + + strong { + font-weight: 600; + } + } + + .current_status { + position: relative; + cursor: pointer; + + .status_change { + position: absolute; + left: 0; + top: 20px; + background: #fff; + padding: 15px; + border: 1px solid #dfdfdf; + border-radius: 3px; + cursor: pointer; + box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); + z-index: 99; + display: none; + + select { + width: 250px; + padding: 10px; + border: 1px solid #eee; + border-radius: 3px; + + option { + font-size: 15px; + padding: 3px; + } + } + } + } + + .dates { + margin-bottom: 5px; + display: flex; + justify-content: space-between; + font-size: 12px; + + .danger { + color: #cc0000; + font-weight: 600; + } + + .warning { + color: #ff8c00; + } + + i { + font-size: 12px; + color: #C9CED4; + margin-right: 5px; + } + } + } + } + } +} + +.action_menu { + display: flex; + margin-bottom: 25px; + gap: 20px; + + .btn { + display: inline-flex; + padding: 7px 15px; + color: #FFF; + border-radius: 3px; + text-decoration: none; + align-items: center; + justify-content: center; + gap: 5px; + + &.btn_add { + background: #57b951; + + &:hover { + background: #4a9c3b; + } + } + + &.btn_cancel { + background: #cc0000; + + &:hover { + background: #b30000; + } + } + + &.disabled { + opacity: .5; + } + } +} + +.form_container { + background: #FFF; + padding: 25px; + max-width: 1300px; + + &.full { + max-width: 100%; + } + + .form_group { + margin-bottom: 10px; + display: flex; + + >.label { + width: 300px; + display: inline-flex; + align-items: flex-start; + justify-content: right; + padding-right: 10px; + } + + .input { + width: calc(100% - 300px); + } + } +} + +.task_popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: none; + + .task_details { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #FFF; + padding: 25px; + border-radius: 6px; + max-width: 1140px; + width: 100%; + + .title { + font-size: 20px; + margin-bottom: 25px; + + a { + color: #333; + text-decoration: none; + margin-right: 10px; + + &.task-delete { + color: #cc0000; + } + } + } + + .close { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; + } + + .content { + display: flex; + font-size: 14px; + + h3 { + width: 100%; + margin-top: 0; + margin-bottom: 5px; + font-weight: 500; + color: #000; + font-size: 17px; + } + + .left { + width: 70%; + max-height: 700px; + overflow-y: auto; + + .users { + display: flex; + gap: 20px; + + .user { + display: flex; + gap: 10px; + align-items: center; + margin-bottom: 10px; + + .avatar { + height: 30px; + width: 30px; + border-radius: 50%; + background: #ccc; + display: flex; + justify-content: center; + align-items: center; + color: #FFF; + } + } + } + + .comments { + border-radius: 3px; + padding: 0 15px 15px 0; + margin-bottom: 15px; + border-bottom: 1px solid #eee; + + .new_comment { + margin-bottom: 15px; + + textarea { + height: 75px; + } + + .add_comment { + background: #57b951; + color: #FFF; + padding: 10px; + border-radius: 6px; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + width: 200px; + text-decoration: none; + + &:hover { + background: #4a9c3b; + } + } + } + + ul { + margin: 0; + padding: 0; + list-style-type: none; + + li { + background: #eee; + margin-bottom: 5px; + padding: 15px; + border-radius: 6px; + position: relative; + + .delete_comment { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; + color: #cc0000; + } + + .author { + font-weight: 600; + margin-bottom: 5px; + display: inline-flex; + margin-right: 10px; + } + + .date { + font-size: 12px; + margin-bottom: 5px; + display: inline-flex; + } + + .text { + margin-bottom: 15px; + font-size: 13px; + } + } + } + } + + .checklist { + border-radius: 3px; + padding: 0 15px 15px 0; + margin-bottom: 15px; + border-bottom: 1px solid #eee; + + .new_element { + display: flex; + margin-bottom: 15px; + + a { + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + border-radius: 0 6px 6px 0; + text-decoration: none; + width: 35px; + background: #57b951; + color: #FFF; + } + } + + ul { + margin: 0; + padding: 0; + list-style-type: none; + + li { + display: flex; + gap: 10px; + margin-bottom: 5px; + background: #FFF; + border-radius: 3px; + padding: 5px; + border: 1px solid #eee; + font-size: 13px; + align-items: center; + + i { + margin-left: auto; + margin-right: 0; + cursor: pointer; + color: #cc0000; + } + } + } + } + + .description { + padding: 15px; + border-radius: 3px; + background: #F6F8F9; + margin-bottom: 15px; + } + } + + .right { + width: 30%; + padding: 0 15px 15px; + + .box { + margin-bottom: 15px; + } + + .time { + a { + display: block; + padding: 10px; + border-radius: 6px; + margin-top: 10px; + text-decoration: none; + text-align: center; + width: 100%; + + &.task_start { + background: #57b951; + color: #FFF; + } + + &.task_end { + background: #cc0000; + color: #FFF; + } + + &.hidden { + display: none; + } + } + } + + .dates { + display: flex; + justify-content: space-between; + flex-wrap: wrap; + + .danger { + color: #cc0000; + font-weight: 600; + } + + .warning { + color: #ff8c00; + } + + i { + color: #C9CED4; + margin-right: 5px; + } + } + } + } + } +} + +.table { + width: 100%; + + th, + td { + border: 1px solid #eee; + padding: 5px; + } + + td.center { + text-align: center; + } + + td.left { + text-align: left; + } + + &.table-sm { + + td { + padding: 5px !important; + } + } + + input.form-control { + font-size: 13px; + } +} + +.projects_container { + background: #FFF; + padding: 15px; + border-radius: 6px; + display: flex; + gap: 20px; + + .left { + display: flex; + gap: 20px; + flex-wrap: wrap; + width: calc(100% - 250px); + } + + .right { + width: 250px; + display: flex; + flex-wrap: wrap; + align-items: flex-start; + justify-content: center; + gap: 20px; + border-left: 1px solid #eee; + padding-left: 15px; + + select { + width: 200px; + } + } +} + +.default_popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: none; + + .popup_content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #FFF; + padding: 25px; + border-radius: 6px; + max-width: 1140px; + width: 100%; + + .close { + position: absolute; + top: 10px; + right: 10px; + cursor: pointer; + } + } +} + +#fg-cron { + margin: 10px 0; + + .countdown { + background: #57b951; + color: #FFF; + padding: 10px; + } + + #cron-container { + max-height: 300px; + overflow-x: hidden; + overflow-y: auto; + + .msg { + font-size: 13px; + padding: 5px; + border-bottom: 1px solid #e8e8e8; + } + } +} + +.card { + background: #FFF; + padding: 25px; + border-radius: 6px; + color: #000; + font-size: 15px; + max-width: 1280px; + + &.mb25 { + margin-bottom: 25px; + } + + .card-header { + font-weight: 600; + } + + .card-body { + padding-top: 10px; + + table { + border-collapse: collapse; + + th, + td { + font-size: 14px; + + &.bold { + font-weight: 600; + } + + &.text-right { + text-align: right; + } + + &.text-center { + text-align: center; + } + + .close-task { + text-decoration: none; + color: $cBlue; + + &:hover { + color: #cc0000; + } + } + } + } + } +} + +.finance-summary { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 10px; + + .panel { + background: #FFF; + border-radius: 6px; + padding: 15px; + + h1 { + font-size: 20px; + margin: 0; + } + + span { + font-size: 0.85em; + } + } +} + +.finance-manager { + display: grid; + gap: 10px; + grid-template-columns: 200px 1fr 500px; + padding-top: 25px; + + .manage-menu { + display: inline-block; + margin-right: 10px; + + a { + color: #333333; + text-decoration: none; + font-weight: 300; + display: block; + } + } + + .actions { + width: 100px; + text-align: center; + + a { + display: inline-flex; + margin: 0 2px; + height: 25px; + width: 25px; + align-items: center; + justify-content: center; + text-decoration: none; + color: #000; + border: 1px solid #e7e7e7; + font-size: 11px; + border-radius: 3px; + transition: all 0.3s ease; + + &:hover { + border: 1px solid $cBlue; + } + } + } +} + +.bootstrap-tagsinput { + .tag { + background: $cBlue; + font-size: 13px; + padding: 5px 10px; + border-radius: 12px; + + [data-role="remove"] { + color: #FFF !important; + + &:hover { + box-shadow: none !important; + color: #000 !important + } + } + } +} + +.finance-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; + + a:not(.btn) { + display: flex; + width: 100%; + text-decoration: none; + color: $cBlack; + + &.zoom-100 { + font-size: 130%; + } + + &.zoom-90 { + font-size: 120%; + } + + &.zoom-80 { + font-size: 110%; + } + + &.zoom-70 { + font-size: 100%; + } + + &.zoom-60 { + font-size: 95%; + } + + &.zoom-50 { + font-size: 85%; + } + + &.zoom-40 { + font-size: 80%; + } + + &.zoom-30 { + font-size: 75%; + } + + &.zoom-20 { + font-size: 75%; + } + + &.zoom-10 { + font-size: 70%; + } + + &.zoom-0 { + font-size: 70%; + } + } +} + +.manage-menu { + position: relative; + + .context-menu { + border-left: 4px dotted #000; + width: 1px; + height: 100%; + } + + .context-menu-container { + position: absolute; + display: none; + background: #FFF; + box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); + top: 2px; + left: 2px; + + ul { + list-style-type: none; + margin: 0; + padding: 0; + + li { + a { + display: block; + padding: 7px 15px; + white-space: nowrap; + + &:hover { + background: #f8f8f8; + } + } + } + } + } + + &:hover { + .context-menu-container { + display: block; + } + } +} + +.dt-layout-table { + margin-bottom: 25px; +} + +.pagination { + button { + border: 1px solid #eee; + background: #FFF; + display: inline-flex; + height: 30px; + width: 30px; + align-items: center; + justify-content: center; + margin: 0 2px; + transition: all 0.3s ease; + + &:hover { + background: #eee; + } + } +} + +table { + &#products { + .table-product-title { + display: flex; + justify-content: space-between; + } + + .edit-product-title { + display: flex; + height: 25px; + align-items: center; + justify-content: center; + width: 25px; + cursor: pointer; + background: #fff; + border: 1px solid #9b9b9b; + color: #9b9b9b; + + &:hover { + background: #9b9b9b; + color: #fff; + } + } + + a { + &.custom_name { + color: $cGreenLight !important; + } + } + } +} + +.chart-with-form { + display: flex; + gap: 20px; + align-items: flex-start; +} + +.chart-area { + flex: 1 1 auto; + min-width: 0; +} + +.comment-form { + width: 360px; + /* stała, wygodna szerokość prawej kolumny */ + flex: 0 0 360px; +} + +.comment-form .form-group { + margin-bottom: 12px; +} + +.comment-form label { + display: block; + font-weight: 600; + margin-bottom: 6px; +} + +.comment-form input[type="date"], +.comment-form textarea { + width: 100%; + border: 1px solid #ccc; + border-radius: 4px; + padding: 0 8px; + font-size: 14px; +} + +.comment-form textarea { + min-height: 120px; + resize: vertical; +} + +.comment-form .btn { + display: inline-block; + padding: 8px 14px; + border-radius: 4px; + border: 0; + background: #337ab7; + color: #fff; + font-weight: 600; + cursor: pointer; +} + +.comment-form .btn[disabled] { + opacity: .6; + cursor: not-allowed; +} + +.comment-form .hint { + font-size: 12px; + color: #666; +} + +/* === Select2 w modalu "Edytuj produkt" === */ + +/* pełna szerokość i taki sam odstęp jak inne pola */ +.jconfirm-box .form-group .select2-container { + width: 100% !important; + margin-top: 8px; +} + +/* wygląd "inputa" */ +.jconfirm-box .select2-container--default .select2-selection--single { + background-color: #fff; + border: 1px solid #ced4da; + /* jak bootstrapowe inputy */ + border-radius: 3px; + min-height: 42px; + /* wysokość podobna do pola tytułu/opisu */ + display: flex; + align-items: center; + padding: 4px 12px; + box-shadow: none; + transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; + font-size: 14px; +} + +/* tekst w środku */ +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered { + padding-left: 0; + line-height: 1.4; + color: #495057; +} + +/* placeholder */ +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #adb5bd; +} + +/* strzałka po prawej – wyrównanie */ +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow { + height: 100%; + right: 8px; +} + +/* efekt hover/focus – jak na form-control */ +.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single, +.jconfirm-box .select2-container--default .select2-selection--single:hover { + border-color: #80bdff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, .25); + outline: 0; +} + +/* dropdown (lista kategorii) */ +.jconfirm-box .select2-container .select2-dropdown { + border-color: #ced4da; + border-radius: 0 0 3px 3px; + font-size: 14px; +} + +/* pole wyszukiwania w dropdownie */ +.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field { + padding: 6px 10px; + border-radius: 3px; + border: 1px solid #ced4da; + font-size: 14px; +} + +/* podświetlenie zaznaczonej pozycji */ +.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #007bff; + color: #fff; +} \ No newline at end of file diff --git a/layout/style.css b/layout/style.css index 54d1bcb..c0b2b5e 100644 --- a/layout/style.css +++ b/layout/style.css @@ -1 +1,1618 @@ -.animate{animation:mymove 3s infinite}.text-right{text-align:right}.text-bold{font-weight:900 !important}.nowrap{white-space:nowrap}table{border-collapse:collapse}small{font-size:.75em}table{font-size:13px}@keyframes mymove{50%{opacity:.33}}@keyframes gradient-animation{0%{background-position:15% 0%}50%{background-position:85% 100%}100%{background-position:15% 0%}}@keyframes frame-enter{0%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), 3px calc(100% - 3px), 3px 100%, 100% 100%, 100% 0%, 0% 0%)}25%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%, 100% 100%, 100% 0%, 0% 0%)}50%{clip-path:polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, 100% 0%, 0% 0%)}75%{-webkit-clip-path:polygon(0% 100%, 3px 100%, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 0%, 0% 0%)}100%{-webkit-clip-path:polygon(0% 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 0% 100%)}}*{box-sizing:border-box}body{font-family:"Open Sans",sans-serif;margin:0;padding:0;font-size:15px;color:#4e5e6a}.btn{padding:12px 25px;transition:all .3s ease;color:#fff;border:0;border-radius:6px;cursor:pointer;display:inline-flex;text-decoration:none;gap:5px;justify-content:center;align-items:center}.btn.btn_small,.btn.btn-xs{padding:5px 7px;font-size:13px}.btn.btn_small i,.btn.btn-xs i{font-size:12px}.btn.btn-success{background:#57b951}.btn.btn-success:hover{background:#4a9c3b}.btn.btn-primary{background:#6690f4}.btn.btn-primary:hover{background:#3164db}.btn.btn-danger{background:#c00}.btn.btn-danger:hover{background:#b30000}.hide{display:none}.form-error{color:#c00;font-size:13px}.input-group{margin-bottom:10px}input[type=checkbox]{border:1px solid #eee}.form-control{border:1px solid #eee;border-radius:3px;height:35px;width:100%;padding:5px;font-family:"Open Sans",sans-serif}.form-control option{padding:5px}.form-control:focus{border:1px solid #6690f4;outline:none}.unlogged{background:#eef1f9;display:flex;align-items:center;justify-content:center;height:100vh}.unlogged .box-login{background:#fff;padding:25px;border-radius:6px;width:400px}.unlogged .box-login .title{text-align:center;padding:10px 10px 25px;border-bottom:1px solid #eee;font-size:20px;margin-bottom:25px}body>.top{background:#fff;border-bottom:1px solid #eee;display:flex;justify-content:space-between;align-items:center}body>.top .logo a{display:inline-flex;color:#6690f4;padding:10px;text-decoration:none}body>.top .logo a span{font-weight:600}body>.top .user-nav{position:relative;font-size:14px;padding:10px}body>.top .user-nav .trigger{cursor:pointer}body>.top .user-nav .trigger i{color:#6690f4}body>.top .user-nav ul{position:absolute;top:100%;left:0;background:#fff;margin:0;padding:0;width:100%;list-style-type:none;border:1px solid #eee;display:none}body>.top .user-nav ul li{cursor:pointer}body>.top .user-nav ul li:hover{background:#f8f9fa}body>.top .user-nav ul li a{color:#333;display:block;text-decoration:none;padding:10px}body>.top .user-nav:hover ul{display:block}.main-menu{display:flex;box-shadow:0 .125rem .25rem rgba(0,0,0,.075) !important}.main-menu ul{display:flex;list-style-type:none;margin:0;padding:0;font-size:14px}.main-menu ul li{cursor:pointer}.main-menu ul li a{color:#333;text-decoration:none;display:inline-flex;padding:10px 15px}.main-menu ul li:hover a{background:#6690f4;color:#fff}.main-menu ul li ul{display:none}.main{padding:25px;background:#d9dee2;min-height:calc(100vh - 80px)}.tasks_container{display:flex;flex-wrap:wrap;gap:20px}.tasks_container .column{width:350px}.tasks_container .column h2{display:flex;padding:10px;background:#fff;margin-bottom:10px;font-size:15px;font-weight:300;border-radius:3px 3px 0 0;justify-content:space-between;align-items:center}.tasks_container .column h2 i{cursor:pointer}.tasks_container .column.tasks_suspended h2{border-bottom:5px solid #c00}.tasks_container .column.tasks_new h2{border-bottom:5px solid #ccc}.tasks_container .column.tasks_bulk h2{border-bottom:5px solid #ff8c00}.tasks_container .column.tasks_to_do h2{border-bottom:5px solid #2aaf47}.tasks_container .column.tasks_to_review h2{border-bottom:5px solid #2535c9}.tasks_container .column.tasks_closed h2{border-bottom:5px solid #000}.tasks_container .column ul{list-style-type:none;margin:0;padding:0}.tasks_container .column ul .task{margin-bottom:5px;background:#fff;padding:10px;border-radius:0;display:flex;position:relative}.tasks_container .column ul .task.notopened{border:2px solid #c00}.tasks_container .column ul .task .left{width:30px}.tasks_container .column ul .task .left .users{display:flex;gap:5px;flex-wrap:wrap}.tasks_container .column ul .task .left .users .user{display:flex;width:20px;height:20px;border-radius:50%;justify-content:center;align-items:center;color:#fff;font-size:13px}.tasks_container .column ul .task .middle{width:calc(100% - 60px)}.tasks_container .column ul .task .right{width:30px;display:flex;flex-wrap:wrap;justify-content:flex-end}.tasks_container .column ul .task .right .recursively{color:#ccc;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .recursively i{font-size:15px}.tasks_container .column ul .task .right .task_start{background:#57b951;color:#fff;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .task_start.hidden{display:none}.tasks_container .column ul .task .right .task_start i{font-size:12px}.tasks_container .column ul .task .right .task_end{background:#c00;color:#fff;border-radius:3px;cursor:pointer;margin-bottom:5px;width:22px;height:22px;text-align:center}.tasks_container .column ul .task .right .task_end.hidden{display:none}.tasks_container .column ul .task .right .task_end i{font-size:12px}.tasks_container .column ul .task .name{font-size:14px;color:#333;text-decoration:none;display:block;margin-bottom:5px}.tasks_container .column ul .task .bottom{display:flex;justify-content:space-between}.tasks_container .column ul .task .client_info,.tasks_container .column ul .task .current_status{font-size:12px;font-weight:400}.tasks_container .column ul .task .client_info strong,.tasks_container .column ul .task .current_status strong{font-weight:600}.tasks_container .column ul .task .current_status{position:relative;cursor:pointer}.tasks_container .column ul .task .current_status .status_change{position:absolute;left:0;top:20px;background:#fff;padding:15px;border:1px solid #dfdfdf;border-radius:3px;cursor:pointer;box-shadow:0 0 15px rgba(0,0,0,.1);z-index:99;display:none}.tasks_container .column ul .task .current_status .status_change select{width:250px;padding:10px;border:1px solid #eee;border-radius:3px}.tasks_container .column ul .task .current_status .status_change select option{font-size:15px;padding:3px}.tasks_container .column ul .task .dates{margin-bottom:5px;display:flex;justify-content:space-between;font-size:12px}.tasks_container .column ul .task .dates .danger{color:#c00;font-weight:600}.tasks_container .column ul .task .dates .warning{color:#ff8c00}.tasks_container .column ul .task .dates i{font-size:12px;color:#c9ced4;margin-right:5px}.action_menu{display:flex;margin-bottom:25px;gap:20px}.action_menu .btn{display:inline-flex;padding:7px 15px;color:#fff;border-radius:3px;text-decoration:none;align-items:center;justify-content:center;gap:5px}.action_menu .btn.btn_add{background:#57b951}.action_menu .btn.btn_add:hover{background:#4a9c3b}.action_menu .btn.btn_cancel{background:#c00}.action_menu .btn.btn_cancel:hover{background:#b30000}.action_menu .btn.disabled{opacity:.5}.form_container{background:#fff;padding:25px;max-width:1300px}.form_container.full{max-width:100%}.form_container .form_group{margin-bottom:10px;display:flex}.form_container .form_group>.label{width:300px;display:inline-flex;align-items:flex-start;justify-content:right;padding-right:10px}.form_container .form_group .input{width:calc(100% - 300px)}.task_popup{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none}.task_popup .task_details{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:#fff;padding:25px;border-radius:6px;max-width:1140px;width:100%}.task_popup .task_details .title{font-size:20px;margin-bottom:25px}.task_popup .task_details .title a{color:#333;text-decoration:none;margin-right:10px}.task_popup .task_details .title a.task-delete{color:#c00}.task_popup .task_details .close{position:absolute;top:10px;right:10px;cursor:pointer}.task_popup .task_details .content{display:flex;font-size:14px}.task_popup .task_details .content h3{width:100%;margin-top:0;margin-bottom:5px;font-weight:500;color:#000;font-size:17px}.task_popup .task_details .content .left{width:70%;max-height:700px;overflow-y:auto}.task_popup .task_details .content .left .users{display:flex;gap:20px}.task_popup .task_details .content .left .users .user{display:flex;gap:10px;align-items:center;margin-bottom:10px}.task_popup .task_details .content .left .users .user .avatar{height:30px;width:30px;border-radius:50%;background:#ccc;display:flex;justify-content:center;align-items:center;color:#fff}.task_popup .task_details .content .left .comments{border-radius:3px;padding:0 15px 15px 0;margin-bottom:15px;border-bottom:1px solid #eee}.task_popup .task_details .content .left .comments .new_comment{margin-bottom:15px}.task_popup .task_details .content .left .comments .new_comment textarea{height:75px}.task_popup .task_details .content .left .comments .new_comment .add_comment{background:#57b951;color:#fff;padding:10px;border-radius:6px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;width:200px;text-decoration:none}.task_popup .task_details .content .left .comments .new_comment .add_comment:hover{background:#4a9c3b}.task_popup .task_details .content .left .comments ul{margin:0;padding:0;list-style-type:none}.task_popup .task_details .content .left .comments ul li{background:#eee;margin-bottom:5px;padding:15px;border-radius:6px;position:relative}.task_popup .task_details .content .left .comments ul li .delete_comment{position:absolute;top:10px;right:10px;cursor:pointer;color:#c00}.task_popup .task_details .content .left .comments ul li .author{font-weight:600;margin-bottom:5px;display:inline-flex;margin-right:10px}.task_popup .task_details .content .left .comments ul li .date{font-size:12px;margin-bottom:5px;display:inline-flex}.task_popup .task_details .content .left .comments ul li .text{margin-bottom:15px;font-size:13px}.task_popup .task_details .content .left .checklist{border-radius:3px;padding:0 15px 15px 0;margin-bottom:15px;border-bottom:1px solid #eee}.task_popup .task_details .content .left .checklist .new_element{display:flex;margin-bottom:15px}.task_popup .task_details .content .left .checklist .new_element a{display:flex;align-items:center;justify-content:center;padding:10px;border-radius:0 6px 6px 0;text-decoration:none;width:35px;background:#57b951;color:#fff}.task_popup .task_details .content .left .checklist ul{margin:0;padding:0;list-style-type:none}.task_popup .task_details .content .left .checklist ul li{display:flex;gap:10px;margin-bottom:5px;background:#fff;border-radius:3px;padding:5px;border:1px solid #eee;font-size:13px;align-items:center}.task_popup .task_details .content .left .checklist ul li i{margin-left:auto;margin-right:0;cursor:pointer;color:#c00}.task_popup .task_details .content .left .description{padding:15px;border-radius:3px;background:#f6f8f9;margin-bottom:15px}.task_popup .task_details .content .right{width:30%;padding:0 15px 15px}.task_popup .task_details .content .right .box{margin-bottom:15px}.task_popup .task_details .content .right .time a{display:block;padding:10px;border-radius:6px;margin-top:10px;text-decoration:none;text-align:center;width:100%}.task_popup .task_details .content .right .time a.task_start{background:#57b951;color:#fff}.task_popup .task_details .content .right .time a.task_end{background:#c00;color:#fff}.task_popup .task_details .content .right .time a.hidden{display:none}.task_popup .task_details .content .right .dates{display:flex;justify-content:space-between;flex-wrap:wrap}.task_popup .task_details .content .right .dates .danger{color:#c00;font-weight:600}.task_popup .task_details .content .right .dates .warning{color:#ff8c00}.task_popup .task_details .content .right .dates i{color:#c9ced4;margin-right:5px}.table{width:100%}.table th,.table td{border:1px solid #eee;padding:5px}.table td.center{text-align:center}.table td.left{text-align:left}.table.table-sm td{padding:5px !important}.table input.form-control{font-size:13px}.projects_container{background:#fff;padding:15px;border-radius:6px;display:flex;gap:20px}.projects_container .left{display:flex;gap:20px;flex-wrap:wrap;width:calc(100% - 250px)}.projects_container .right{width:250px;display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:center;gap:20px;border-left:1px solid #eee;padding-left:15px}.projects_container .right select{width:200px}.default_popup{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:none}.default_popup .popup_content{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:#fff;padding:25px;border-radius:6px;max-width:1140px;width:100%}.default_popup .popup_content .close{position:absolute;top:10px;right:10px;cursor:pointer}#fg-cron{margin:10px 0}#fg-cron .countdown{background:#57b951;color:#fff;padding:10px}#fg-cron #cron-container{max-height:300px;overflow-x:hidden;overflow-y:auto}#fg-cron #cron-container .msg{font-size:13px;padding:5px;border-bottom:1px solid #e8e8e8}.card{background:#fff;padding:25px;border-radius:6px;color:#000;font-size:15px;max-width:1280px}.card.mb25{margin-bottom:25px}.card .card-header{font-weight:600}.card .card-body{padding-top:10px}.card .card-body table{border-collapse:collapse}.card .card-body table th,.card .card-body table td{font-size:14px}.card .card-body table th.bold,.card .card-body table td.bold{font-weight:600}.card .card-body table th.text-right,.card .card-body table td.text-right{text-align:right}.card .card-body table th.text-center,.card .card-body table td.text-center{text-align:center}.card .card-body table th .close-task,.card .card-body table td .close-task{text-decoration:none;color:#6690f4}.card .card-body table th .close-task:hover,.card .card-body table td .close-task:hover{color:#c00}.finance-summary{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:10px}.finance-summary .panel{background:#fff;border-radius:6px;padding:15px}.finance-summary .panel h1{font-size:20px;margin:0}.finance-summary .panel span{font-size:.85em}.finance-manager{display:grid;gap:10px;grid-template-columns:200px 1fr 500px;padding-top:25px}.finance-manager .manage-menu{display:inline-block;margin-right:10px}.finance-manager .manage-menu a{color:#333;text-decoration:none;font-weight:300;display:block}.finance-manager .actions{width:100px;text-align:center}.finance-manager .actions a{display:inline-flex;margin:0 2px;height:25px;width:25px;align-items:center;justify-content:center;text-decoration:none;color:#000;border:1px solid #e7e7e7;font-size:11px;border-radius:3px;transition:all .3s ease}.finance-manager .actions a:hover{border:1px solid #6690f4}.bootstrap-tagsinput .tag{background:#6690f4;font-size:13px;padding:5px 10px;border-radius:12px}.bootstrap-tagsinput .tag [data-role=remove]{color:#fff !important}.bootstrap-tagsinput .tag [data-role=remove]:hover{box-shadow:none !important;color:#000 !important}.finance-tags{display:flex;flex-wrap:wrap;gap:5px}.finance-tags a:not(.btn){display:flex;width:100%;text-decoration:none;color:#4e5e6a}.finance-tags a:not(.btn).zoom-100{font-size:130%}.finance-tags a:not(.btn).zoom-90{font-size:120%}.finance-tags a:not(.btn).zoom-80{font-size:110%}.finance-tags a:not(.btn).zoom-70{font-size:100%}.finance-tags a:not(.btn).zoom-60{font-size:95%}.finance-tags a:not(.btn).zoom-50{font-size:85%}.finance-tags a:not(.btn).zoom-40{font-size:80%}.finance-tags a:not(.btn).zoom-30{font-size:75%}.finance-tags a:not(.btn).zoom-20{font-size:75%}.finance-tags a:not(.btn).zoom-10{font-size:70%}.finance-tags a:not(.btn).zoom-0{font-size:70%}.manage-menu{position:relative}.manage-menu .context-menu{border-left:4px dotted #000;width:1px;height:100%}.manage-menu .context-menu-container{position:absolute;display:none;background:#fff;box-shadow:5px 5px 15px rgba(0,0,0,.1);top:2px;left:2px}.manage-menu .context-menu-container ul{list-style-type:none;margin:0;padding:0}.manage-menu .context-menu-container ul li a{display:block;padding:7px 15px;white-space:nowrap}.manage-menu .context-menu-container ul li a:hover{background:#f8f8f8}.manage-menu:hover .context-menu-container{display:block}.dt-layout-table{margin-bottom:25px}.pagination button{border:1px solid #eee;background:#fff;display:inline-flex;height:30px;width:30px;align-items:center;justify-content:center;margin:0 2px;transition:all .3s ease}.pagination button:hover{background:#eee}table#products .table-product-title{display:flex;justify-content:space-between}table#products .edit-product-title{display:flex;height:25px;align-items:center;justify-content:center;width:25px;cursor:pointer;background:#fff;border:1px solid #9b9b9b;color:#9b9b9b}table#products .edit-product-title:hover{background:#9b9b9b;color:#fff}table#products a.custom_name{color:#57b951 !important}.chart-with-form{display:flex;gap:20px;align-items:flex-start}.chart-area{flex:1 1 auto;min-width:0}.comment-form{width:360px;flex:0 0 360px}.comment-form .form-group{margin-bottom:12px}.comment-form label{display:block;font-weight:600;margin-bottom:6px}.comment-form input[type=date],.comment-form textarea{width:100%;border:1px solid #ccc;border-radius:4px;padding:0 8px;font-size:14px}.comment-form textarea{min-height:120px;resize:vertical}.comment-form .btn{display:inline-block;padding:8px 14px;border-radius:4px;border:0;background:#337ab7;color:#fff;font-weight:600;cursor:pointer}.comment-form .btn[disabled]{opacity:.6;cursor:not-allowed}.comment-form .hint{font-size:12px;color:#666}.jconfirm-box .form-group .select2-container{width:100% !important;margin-top:8px}.jconfirm-box .select2-container--default .select2-selection--single{background-color:#fff;border:1px solid #ced4da;border-radius:3px;min-height:42px;display:flex;align-items:center;padding:4px 12px;box-shadow:none;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;font-size:14px}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered{padding-left:0;line-height:1.4;color:#495057}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder{color:#adb5bd}.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow{height:100%;right:8px}.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,.jconfirm-box .select2-container--default .select2-selection--single:hover{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25);outline:0}.jconfirm-box .select2-container .select2-dropdown{border-color:#ced4da;border-radius:0 0 3px 3px;font-size:14px}.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field{padding:6px 10px;border-radius:3px;border:1px solid #ced4da;font-size:14px}.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected]{background-color:#007bff;color:#fff}/*# sourceMappingURL=style.css.map */ \ No newline at end of file +* { + box-sizing: border-box; +} + +body { + font-family: "Open Sans", sans-serif; + margin: 0; + padding: 0; + font-size: 14px; + color: #4E5E6A; + background: #F4F6F9; +} + +.hide { + display: none; +} + +small { + font-size: 0.75em; +} + +.text-right { + text-align: right; +} + +.text-bold { + font-weight: 700 !important; +} + +.nowrap { + white-space: nowrap; +} + +body.unlogged { + background: #F4F6F9; + margin: 0; + padding: 0; +} + +.login-container { + display: flex; + min-height: 100vh; +} + +.login-brand { + flex: 0 0 45%; + background: linear-gradient(135deg, #1E2A3A 0%, #2C3E57 50%, #6690F4 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 60px; + position: relative; + overflow: hidden; +} +.login-brand::before { + content: ""; + position: absolute; + top: -50%; + right: -50%; + width: 100%; + height: 100%; + background: radial-gradient(circle, rgba(102, 144, 244, 0.15) 0%, transparent 70%); + border-radius: 50%; +} +.login-brand .brand-content { + position: relative; + z-index: 1; + color: #FFFFFF; + max-width: 400px; +} +.login-brand .brand-logo { + font-size: 48px; + font-weight: 300; + margin-bottom: 20px; + letter-spacing: -1px; +} +.login-brand .brand-logo strong { + font-weight: 700; +} +.login-brand .brand-tagline { + font-size: 18px; + opacity: 0.85; + line-height: 1.6; + margin-bottom: 50px; +} +.login-brand .brand-features .feature { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 20px; + opacity: 0.8; +} +.login-brand .brand-features .feature i { + font-size: 20px; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; +} +.login-brand .brand-features .feature span { + font-size: 15px; +} + +.login-form-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 60px; + background: #FFFFFF; +} + +.login-box { + width: 100%; + max-width: 420px; +} +.login-box .login-header { + margin-bottom: 35px; +} +.login-box .login-header h1 { + font-size: 28px; + font-weight: 700; + color: #2D3748; + margin: 0 0 8px; +} +.login-box .login-header p { + color: #718096; + font-size: 15px; + margin: 0; +} +.login-box .form-group { + margin-bottom: 20px; +} +.login-box .form-group label { + display: block; + font-size: 13px; + font-weight: 600; + color: #2D3748; + margin-bottom: 6px; +} +.login-box .input-with-icon { + position: relative; +} +.login-box .input-with-icon i { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: #A0AEC0; + font-size: 14px; +} +.login-box .input-with-icon .form-control { + padding-left: 42px; +} +.login-box .form-control { + width: 100%; + height: 46px; + border: 2px solid #E2E8F0; + border-radius: 8px; + padding: 0 14px; + font-size: 14px; + font-family: "Open Sans", sans-serif; + color: #2D3748; + transition: border-color 0.3s, box-shadow 0.3s; +} +.login-box .form-control::placeholder { + color: #CBD5E0; +} +.login-box .form-control:focus { + border-color: #6690F4; + box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.15); + outline: none; +} +.login-box .form-error { + color: #CC0000; + font-size: 12px; + margin-top: 4px; +} +.login-box .checkbox-group .checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 13px; + color: #718096; + font-weight: 400; +} +.login-box .checkbox-group .checkbox-label input[type=checkbox] { + width: 16px; + height: 16px; + accent-color: #6690F4; +} +.login-box .btn-login { + width: 100%; + height: 48px; + font-size: 15px; + font-weight: 600; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; +} +.login-box .btn-login.disabled { + opacity: 0.7; + pointer-events: none; +} +.login-box .alert { + display: none; + padding: 12px 16px; + border-radius: 8px; + font-size: 13px; + margin-bottom: 20px; +} +.login-box .alert.alert-danger { + background: #FFF5F5; + color: #CC0000; + border: 1px solid #FED7D7; +} +.login-box .alert.alert-success { + background: #F0FFF4; + color: #276749; + border: 1px solid #C6F6D5; +} + +@media (max-width: 768px) { + .login-brand { + display: none; + } + .login-form-wrapper { + padding: 30px 20px; + } +} +body.logged { + display: flex; + min-height: 100vh; + background: #F4F6F9; +} + +.sidebar { + width: 260px; + min-height: 100vh; + background: #1E2A3A; + position: fixed; + top: 0; + left: 0; + z-index: 1000; + display: flex; + flex-direction: column; + transition: width 0.3s ease; + overflow: hidden; +} +.sidebar.collapsed { + width: 70px; +} +.sidebar.collapsed .sidebar-header { + padding: 16px 0; + justify-content: center; +} +.sidebar.collapsed .sidebar-header .sidebar-logo { + display: none; +} +.sidebar.collapsed .sidebar-header .sidebar-toggle i { + transform: rotate(180deg); +} +.sidebar.collapsed .sidebar-nav ul li a { + padding: 12px 0; + justify-content: center; +} +.sidebar.collapsed .sidebar-nav ul li a span { + display: none; +} +.sidebar.collapsed .sidebar-nav ul li a i { + margin-right: 0; + font-size: 18px; +} +.sidebar.collapsed .sidebar-footer .sidebar-user { + justify-content: center; +} +.sidebar.collapsed .sidebar-footer .sidebar-user .user-info { + display: none; +} +.sidebar.collapsed .sidebar-footer .sidebar-logout { + justify-content: center; +} +.sidebar.collapsed .sidebar-footer .sidebar-logout span { + display: none; +} +.sidebar.collapsed .nav-divider { + margin: 8px 15px; +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 20px 16px; + border-bottom: 1px solid rgba(255, 255, 255, 0.08); +} +.sidebar-header .sidebar-logo a { + color: #FFFFFF; + text-decoration: none; + font-size: 24px; + font-weight: 300; + letter-spacing: -0.5px; +} +.sidebar-header .sidebar-logo a strong { + font-weight: 700; +} +.sidebar-header .sidebar-toggle { + background: none; + border: none; + color: #A8B7C7; + cursor: pointer; + padding: 6px; + border-radius: 6px; + transition: all 0.3s; +} +.sidebar-header .sidebar-toggle:hover { + background: rgba(255, 255, 255, 0.08); + color: #FFFFFF; +} +.sidebar-header .sidebar-toggle i { + transition: transform 0.3s; +} + +.sidebar-nav { + flex: 1; + padding: 12px 0; + overflow-y: auto; +} +.sidebar-nav ul { + list-style: none; + margin: 0; + padding: 0; +} +.sidebar-nav ul li.nav-divider { + height: 1px; + background: rgba(255, 255, 255, 0.08); + margin: 8px 20px; +} +.sidebar-nav ul li a { + display: flex; + align-items: center; + padding: 11px 20px; + color: #A8B7C7; + text-decoration: none; + font-size: 14px; + transition: all 0.2s; + border-left: 3px solid transparent; +} +.sidebar-nav ul li a i { + width: 20px; + text-align: center; + margin-right: 12px; + font-size: 15px; +} +.sidebar-nav ul li a:hover { + background: #263548; + color: #FFFFFF; +} +.sidebar-nav ul li.active > a { + background: rgba(102, 144, 244, 0.15); + color: #FFFFFF; + border-left-color: #6690F4; +} +.sidebar-nav ul li.active > a i { + color: #6690F4; +} + +.sidebar-footer { + padding: 16px 20px; + border-top: 1px solid rgba(255, 255, 255, 0.08); +} +.sidebar-footer .sidebar-user { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} +.sidebar-footer .sidebar-user .user-avatar { + width: 34px; + height: 34px; + border-radius: 50%; + background: rgba(102, 144, 244, 0.2); + display: flex; + align-items: center; + justify-content: center; + color: #6690F4; + font-size: 14px; + flex-shrink: 0; +} +.sidebar-footer .sidebar-user .user-info { + overflow: hidden; +} +.sidebar-footer .sidebar-user .user-info .user-email { + color: #A8B7C7; + font-size: 12px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.sidebar-footer .sidebar-logout { + display: flex; + align-items: center; + gap: 8px; + color: #E53E3E; + text-decoration: none; + font-size: 13px; + padding: 8px 10px; + border-radius: 6px; + transition: all 0.2s; +} +.sidebar-footer .sidebar-logout i { + font-size: 14px; +} +.sidebar-footer .sidebar-logout:hover { + background: rgba(229, 62, 62, 0.1); +} + +.main-wrapper { + margin-left: 260px; + flex: 1; + min-height: 100vh; + transition: margin-left 0.3s ease; + display: flex; + flex-direction: column; +} +.main-wrapper.expanded { + margin-left: 70px; +} + +.topbar { + height: 56px; + background: #FFFFFF; + border-bottom: 1px solid #E2E8F0; + display: flex; + align-items: center; + padding: 0 25px; + position: sticky; + top: 0; + z-index: 500; +} +.topbar .topbar-toggle { + background: none; + border: none; + color: #4E5E6A; + cursor: pointer; + padding: 8px 10px; + border-radius: 6px; + font-size: 16px; + margin-right: 15px; + transition: all 0.2s; +} +.topbar .topbar-toggle:hover { + background: #F4F6F9; +} +.topbar .topbar-breadcrumb { + font-size: 16px; + font-weight: 600; + color: #2D3748; +} + +.content { + flex: 1; + padding: 25px; +} + +.app-alert { + background: #EBF8FF; + border: 1px solid #BEE3F8; + color: #2B6CB0; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 14px; +} + +.btn { + padding: 10px 20px; + transition: all 0.2s ease; + color: #FFFFFF; + border: 0; + border-radius: 6px; + cursor: pointer; + display: inline-flex; + text-decoration: none; + gap: 6px; + justify-content: center; + align-items: center; + font-size: 14px; + font-family: "Open Sans", sans-serif; + font-weight: 500; +} +.btn.btn_small, .btn.btn-xs, .btn.btn-sm { + padding: 5px 10px; + font-size: 12px; +} +.btn.btn_small i, .btn.btn-xs i, .btn.btn-sm i { + font-size: 11px; +} +.btn.btn-success { + background: #57B951; +} +.btn.btn-success:hover { + background: #4a9c3b; +} +.btn.btn-primary { + background: #6690F4; +} +.btn.btn-primary:hover { + background: #3164db; +} +.btn.btn-danger { + background: #CC0000; +} +.btn.btn-danger:hover { + background: #b30000; +} +.btn.disabled { + opacity: 0.6; + pointer-events: none; +} + +.form-control { + border: 1px solid #E2E8F0; + border-radius: 6px; + height: 38px; + width: 100%; + padding: 6px 12px; + font-family: "Open Sans", sans-serif; + font-size: 14px; + color: #2D3748; + transition: border-color 0.2s, box-shadow 0.2s; +} +.form-control option { + padding: 5px; +} +.form-control:focus { + border-color: #6690F4; + box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1); + outline: none; +} + +input[type=checkbox] { + border: 1px solid #E2E8F0; +} + +table { + border-collapse: collapse; + font-size: 13px; +} + +.table { + width: 100%; +} +.table th, +.table td { + border: 1px solid #E2E8F0; + padding: 8px 10px; +} +.table th { + background: #F7FAFC; + font-weight: 600; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #718096; +} +.table td.center { + text-align: center; +} +.table td.left { + text-align: left; +} +.table.table-sm td { + padding: 5px !important; +} +.table input.form-control { + font-size: 13px; + height: 32px; +} + +.card { + background: #FFFFFF; + padding: 20px; + border-radius: 8px; + color: #2D3748; + font-size: 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); +} +.card.mb25 { + margin-bottom: 20px; +} +.card .card-header { + font-weight: 600; + font-size: 15px; +} +.card .card-body { + padding-top: 12px; +} +.card .card-body table th, +.card .card-body table td { + font-size: 13px; +} +.card .card-body table th.bold, +.card .card-body table td.bold { + font-weight: 600; +} +.card .card-body table th.text-right, +.card .card-body table td.text-right { + text-align: right; +} +.card .card-body table th.text-center, +.card .card-body table td.text-center { + text-align: center; +} + +.action_menu { + display: flex; + margin-bottom: 20px; + gap: 12px; +} +.action_menu .btn { + padding: 8px 16px; +} +.action_menu .btn.btn_add { + background: #57B951; +} +.action_menu .btn.btn_add:hover { + background: #4a9c3b; +} +.action_menu .btn.btn_cancel { + background: #CC0000; +} +.action_menu .btn.btn_cancel:hover { + background: #b30000; +} + +.settings-card { + background: #FFFFFF; + border-radius: 10px; + padding: 28px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); +} +.settings-card .settings-card-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #E2E8F0; +} +.settings-card .settings-card-header .settings-card-icon { + width: 44px; + height: 44px; + border-radius: 10px; + background: rgb(225.706097561, 233.7475609756, 252.893902439); + color: #6690F4; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + flex-shrink: 0; +} +.settings-card .settings-card-header h3 { + margin: 0; + font-size: 17px; + font-weight: 600; + color: #2D3748; +} +.settings-card .settings-card-header small { + color: #8899A6; + font-size: 13px; +} +.settings-card .settings-field { + margin-bottom: 18px; +} +.settings-card .settings-field label { + display: block; + font-size: 13px; + font-weight: 600; + color: #2D3748; + margin-bottom: 6px; +} +.settings-card .settings-input-wrap { + position: relative; +} +.settings-card .settings-input-wrap .settings-input-icon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #A0AEC0; + font-size: 14px; + pointer-events: none; +} +.settings-card .settings-input-wrap .form-control { + padding-left: 38px; +} +.settings-card .settings-input-wrap .settings-toggle-pw { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #A0AEC0; + cursor: pointer; + padding: 6px 10px; + font-size: 14px; + transition: color 0.2s; +} +.settings-card .settings-input-wrap .settings-toggle-pw:hover { + color: #6690F4; +} +.settings-card .settings-fields-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0 24px; +} +@media (max-width: 768px) { + .settings-card .settings-fields-grid { + grid-template-columns: 1fr; + } +} +.settings-card .settings-alert-error { + display: flex; + align-items: center; + gap: 10px; + background: #FFF5F5; + color: #CC0000; + border: 1px solid #FED7D7; + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 20px; + font-size: 13px; +} +.settings-card .settings-alert-error i { + font-size: 16px; + flex-shrink: 0; +} + +.clients-page .clients-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.clients-page .clients-header h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: #2D3748; +} +.clients-page .clients-header h2 i { + color: #6690F4; + margin-right: 8px; +} +.clients-page .clients-table-wrap { + background: #FFFFFF; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; +} +.clients-page .clients-table-wrap .table { + margin: 0; +} +.clients-page .clients-table-wrap .table thead th { + background: #F8FAFC; + border-bottom: 2px solid #E2E8F0; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + padding: 14px 20px; +} +.clients-page .clients-table-wrap .table tbody td { + padding: 14px 20px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; +} +.clients-page .clients-table-wrap .table tbody tr:hover { + background: #F8FAFC; +} +.clients-page .clients-table-wrap .table .client-id { + color: #8899A6; + font-size: 13px; + font-weight: 600; +} +.clients-page .clients-table-wrap .table .client-name { + font-weight: 600; + color: #2D3748; +} +.clients-page .badge-id { + display: inline-block; + background: #EEF2FF; + color: #6690F4; + font-size: 13px; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + font-family: monospace; +} +.clients-page .actions-cell { + text-align: center; + white-space: nowrap; +} +.clients-page .btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 8px; + border: none; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; + margin: 0 2px; +} +.clients-page .btn-icon.btn-icon-edit { + background: #EEF2FF; + color: #6690F4; +} +.clients-page .btn-icon.btn-icon-edit:hover { + background: #6690F4; + color: #FFFFFF; +} +.clients-page .btn-icon.btn-icon-delete { + background: #FFF5F5; + color: #CC0000; +} +.clients-page .btn-icon.btn-icon-delete:hover { + background: #CC0000; + color: #FFFFFF; +} +.clients-page .empty-state { + text-align: center; + padding: 50px 20px !important; + color: #A0AEC0; +} +.clients-page .empty-state i { + font-size: 40px; + margin-bottom: 12px; + display: block; +} +.clients-page .empty-state p { + margin: 0; + font-size: 15px; +} + +.btn-secondary { + background: #E2E8F0; + color: #2D3748; + border: none; + padding: 8px 18px; + border-radius: 6px; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; +} +.btn-secondary:hover { + background: #CBD5E0; +} + +.campaigns-page .campaigns-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.campaigns-page .campaigns-header h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: #2D3748; +} +.campaigns-page .campaigns-header h2 i { + color: #6690F4; + margin-right: 8px; +} +.campaigns-page .campaigns-filters { + display: flex; + gap: 20px; + margin-bottom: 20px; +} +.campaigns-page .campaigns-filters .filter-group { + flex: 1; +} +.campaigns-page .campaigns-filters .filter-group label { + display: block; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + margin-bottom: 6px; +} +.campaigns-page .campaigns-filters .filter-group label i { + margin-right: 4px; +} +.campaigns-page .campaigns-filters .filter-group .form-control { + width: 100%; + padding: 10px 14px; + border: 1px solid #E2E8F0; + border-radius: 8px; + font-size: 14px; + color: #2D3748; + background: #FFFFFF; + transition: border-color 0.2s; + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; +} +.campaigns-page .campaigns-filters .filter-group .form-control:focus { + outline: none; + border-color: #6690F4; + box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1); +} +.campaigns-page .campaigns-filters .filter-group .filter-with-action { + display: flex; + gap: 8px; +} +.campaigns-page .campaigns-filters .filter-group .filter-with-action .form-control { + flex: 1; +} +.campaigns-page .campaigns-filters .filter-group .filter-with-action .btn-icon { + flex-shrink: 0; + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + border: none; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; +} +.campaigns-page .campaigns-filters .filter-group .filter-with-action .btn-icon.btn-icon-delete { + background: #FFF5F5; + color: #CC0000; +} +.campaigns-page .campaigns-filters .filter-group .filter-with-action .btn-icon.btn-icon-delete:hover { + background: #CC0000; + color: #FFFFFF; +} +.campaigns-page .campaigns-chart-wrap { + background: #FFFFFF; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + padding: 20px; + margin-bottom: 20px; + min-height: 350px; +} +.campaigns-page .campaigns-table-wrap { + background: #FFFFFF; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; +} +.campaigns-page .campaigns-table-wrap .table { + margin: 0; + width: 100% !important; +} +.campaigns-page .campaigns-table-wrap .table thead th { + background: #F8FAFC; + border-bottom: 2px solid #E2E8F0; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + padding: 12px 16px; + white-space: nowrap; +} +.campaigns-page .campaigns-table-wrap .table tbody td { + padding: 10px 16px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; + font-size: 13px; +} +.campaigns-page .campaigns-table-wrap .table tbody tr:hover { + background: #F8FAFC; +} +.campaigns-page .campaigns-table-wrap .dt-layout-row { + padding: 14px 20px; + margin: 0 !important; + border-top: 1px solid #F1F5F9; +} +.campaigns-page .campaigns-table-wrap .dt-layout-row:first-child { + display: none; +} +.campaigns-page .campaigns-table-wrap .dt-info { + font-size: 13px; + color: #8899A6; +} +.campaigns-page .campaigns-table-wrap .dt-paging .pagination { + margin: 0; + padding: 0; + list-style: none; + display: flex; + align-items: center; + gap: 6px; +} +.campaigns-page .campaigns-table-wrap .dt-paging .pagination .page-item .page-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + width: fit-content; + height: 36px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + border: 1px solid #E2E8F0; + background: #FFFFFF; + color: #4E5E6A; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + line-height: 1; + white-space: nowrap; +} +.campaigns-page .campaigns-table-wrap .dt-paging .pagination .page-item .page-link:hover { + background: #EEF2FF; + color: #6690F4; + border-color: #6690F4; +} +.campaigns-page .campaigns-table-wrap .dt-paging .pagination .page-item.active .page-link { + background: #6690F4; + color: #FFFFFF; + border-color: #6690F4; + font-weight: 600; +} +.campaigns-page .campaigns-table-wrap .dt-paging .pagination .page-item.disabled .page-link { + opacity: 0.35; + cursor: default; + pointer-events: none; +} +.campaigns-page .campaigns-table-wrap .dt-processing { + background: rgba(255, 255, 255, 0.9); + color: #4E5E6A; + font-size: 14px; +} +.campaigns-page .delete-history-entry { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #FFF5F5; + color: #CC0000; + transition: all 0.2s; +} +.campaigns-page .delete-history-entry:hover { + background: #CC0000; + color: #FFFFFF; +} + +.products-page .products-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} +.products-page .products-header h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: #2D3748; +} +.products-page .products-header h2 i { + color: #6690F4; + margin-right: 8px; +} +.products-page .products-filters { + display: flex; + gap: 20px; + margin-bottom: 16px; +} +.products-page .products-filters .filter-group label { + display: block; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + margin-bottom: 6px; +} +.products-page .products-filters .filter-group label i { + margin-right: 4px; +} +.products-page .products-filters .filter-group .form-control { + width: 100%; + padding: 10px 14px; + border: 1px solid #E2E8F0; + border-radius: 8px; + font-size: 14px; + color: #2D3748; + background: #FFFFFF; + transition: border-color 0.2s; +} +.products-page .products-filters .filter-group .form-control:focus { + outline: none; + border-color: #6690F4; + box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1); +} +.products-page .products-filters .filter-group select.form-control { + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; +} +.products-page .products-filters .filter-group.filter-group-client { + flex: 1; +} +.products-page .products-filters .filter-group.filter-group-roas { + flex: 0 0 200px; +} +.products-page .products-actions { + margin-bottom: 12px; +} +.products-page .products-actions .btn-danger { + padding: 7px 14px; + font-size: 13px; + border-radius: 6px; + border: none; + cursor: pointer; + transition: all 0.2s; +} +.products-page .products-actions .btn-danger:disabled { + opacity: 0.4; + cursor: default; +} +.products-page .products-table-wrap { + background: #FFFFFF; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; +} +.products-page .products-table-wrap .table { + margin: 0; + width: 100% !important; +} +.products-page .products-table-wrap .table thead th { + background: #F8FAFC; + border-bottom: 2px solid #E2E8F0; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.3px; + color: #8899A6; + padding: 10px 8px; + white-space: nowrap; +} +.products-page .products-table-wrap .table tbody td { + padding: 6px 8px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; + font-size: 12px; +} +.products-page .products-table-wrap .table tbody tr:hover { + background: #F8FAFC; +} +.products-page .products-table-wrap .table input.min_roas, +.products-page .products-table-wrap .table input.form-control-sm, +.products-page .products-table-wrap .table select.custom_label_4, +.products-page .products-table-wrap .table select.form-control-sm { + padding: 3px 6px; + font-size: 12px; + border: 1px solid #E2E8F0; + border-radius: 4px; + background: #FFFFFF; +} +.products-page .products-table-wrap .dt-layout-row { + padding: 14px 20px; + margin: 0 !important; + border-top: 1px solid #F1F5F9; +} +.products-page .products-table-wrap .dt-layout-row:first-child { + display: none; +} +.products-page .products-table-wrap .dt-info { + font-size: 13px; + color: #8899A6; +} +.products-page .products-table-wrap .dt-paging .pagination { + margin: 0; + padding: 0; + list-style: none; + display: flex; + align-items: center; + gap: 6px; +} +.products-page .products-table-wrap .dt-paging .pagination .page-item .page-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + height: 36px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + border: 1px solid #E2E8F0; + background: #FFFFFF; + color: #4E5E6A; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + line-height: 1; + white-space: nowrap; +} +.products-page .products-table-wrap .dt-paging .pagination .page-item .page-link:hover { + background: #EEF2FF; + color: #6690F4; + border-color: #6690F4; +} +.products-page .products-table-wrap .dt-paging .pagination .page-item.active .page-link { + background: #6690F4; + color: #FFFFFF; + border-color: #6690F4; + font-weight: 600; +} +.products-page .products-table-wrap .dt-paging .pagination .page-item.disabled .page-link { + opacity: 0.35; + cursor: default; + pointer-events: none; +} +.products-page .products-table-wrap .dt-processing { + background: rgba(255, 255, 255, 0.9); + color: #4E5E6A; + font-size: 14px; +} +.products-page .delete-product { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #FFF5F5; + color: #CC0000; + transition: all 0.2s; +} +.products-page .delete-product:hover { + background: #CC0000; + color: #FFFFFF; +} +.products-page .edit-product-title { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #EEF2FF; + color: #6690F4; + transition: all 0.2s; +} +.products-page .edit-product-title:hover { + background: #6690F4; + color: #FFFFFF; +} + +.desc-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; +} +.desc-header label { + margin: 0; +} + +.desc-tabs { + display: flex; + gap: 2px; + background: #eee; + border-radius: 6px; + padding: 2px; +} + +.desc-tab { + border: none; + background: transparent; + padding: 4px 12px; + font-size: 12px; + border-radius: 4px; + cursor: pointer; + color: #666; + transition: all 0.15s ease; +} +.desc-tab i { + margin-right: 4px; +} +.desc-tab.active { + background: #fff; + color: #333; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); + font-weight: 500; +} +.desc-tab:hover:not(.active) { + color: #333; +} + +.desc-wrap { + flex: 1; + min-width: 0; +} + +.desc-preview ul, .desc-preview ol { + margin: 6px 0; + padding-left: 20px; +} +.desc-preview li { + margin-bottom: 3px; +} +.desc-preview b, .desc-preview strong { + font-weight: 600; +} + +.input-with-ai { + display: flex; + gap: 8px; + align-items: flex-start; +} +.input-with-ai .form-control { + flex: 1; +} + +.btn-ai-suggest { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border-radius: 8px; + border: 1px solid #C084FC; + background: linear-gradient(135deg, #F3E8FF, #EDE9FE); + color: #7C3AED; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + min-height: 38px; +} +.btn-ai-suggest i { + font-size: 13px; +} +.btn-ai-suggest:hover { + background: linear-gradient(135deg, #7C3AED, #6D28D9); + color: #FFF; + border-color: #6D28D9; +} +.btn-ai-suggest:disabled { + opacity: 0.7; + cursor: wait; +} + +.form_container { + background: #FFFFFF; + padding: 25px; + max-width: 1300px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); +} +.form_container.full { + max-width: 100%; +} +.form_container .form_group { + margin-bottom: 12px; + display: flex; +} +.form_container .form_group > .label { + width: 300px; + display: inline-flex; + align-items: flex-start; + justify-content: right; + padding-right: 12px; +} +.form_container .form_group .input { + width: calc(100% - 300px); +} + +.default_popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.45); + display: none; + z-index: 2000; +} +.default_popup .popup_content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #FFFFFF; + padding: 25px; + border-radius: 10px; + max-width: 1140px; + width: 95%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); +} +.default_popup .popup_content .popup_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} +.default_popup .popup_content .popup_header .title { + font-size: 18px; + font-weight: 600; +} +.default_popup .popup_content .close { + cursor: pointer; + color: #A0AEC0; + font-size: 18px; + padding: 4px; +} +.default_popup .popup_content .close:hover { + color: #CC0000; +} + +.dt-layout-table { + margin-bottom: 20px; +} + +.pagination button { + border: 1px solid #E2E8F0; + background: #FFFFFF; + display: inline-flex; + height: 32px; + width: 32px; + align-items: center; + justify-content: center; + margin: 0 2px; + border-radius: 4px; + transition: all 0.2s; + cursor: pointer; +} +.pagination button:hover { + background: #F4F6F9; + border-color: #6690F4; +} + +table#products a { + color: inherit; + text-decoration: none; +} +table#products .table-product-title { + display: flex; + justify-content: space-between; +} +table#products .edit-product-title { + display: flex; + height: 25px; + align-items: center; + justify-content: center; + width: 25px; + cursor: pointer; + background: #FFFFFF; + border: 1px solid #CBD5E0; + color: #CBD5E0; + border-radius: 4px; +} +table#products .edit-product-title:hover { + background: #CBD5E0; + color: #FFFFFF; +} +table#products a.custom_name { + color: #57b951 !important; +} + +.chart-with-form { + display: flex; + gap: 20px; + align-items: flex-start; +} + +.chart-area { + flex: 1 1 auto; + min-width: 0; +} + +.comment-form { + width: 360px; + flex: 0 0 360px; +} +.comment-form .form-group { + margin-bottom: 12px; +} +.comment-form label { + display: block; + font-weight: 600; + margin-bottom: 6px; + font-size: 13px; +} +.comment-form input[type=date], +.comment-form textarea { + width: 100%; + border: 1px solid #E2E8F0; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + font-family: "Open Sans", sans-serif; +} +.comment-form textarea { + min-height: 120px; + resize: vertical; +} +.comment-form .btn { + padding: 8px 16px; +} +.comment-form .btn[disabled] { + opacity: 0.6; + cursor: not-allowed; +} +.comment-form .hint { + font-size: 12px; + color: #718096; +} + +.jconfirm-box .form-group .select2-container { + width: 100% !important; + margin-top: 8px; +} + +.jconfirm-box .select2-container--default .select2-selection--single { + background-color: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 6px; + min-height: 42px; + display: flex; + align-items: center; + padding: 4px 12px; + box-shadow: none; + transition: border-color 0.2s, box-shadow 0.2s; + font-size: 14px; +} + +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered { + padding-left: 0; + line-height: 1.4; + color: #495057; +} + +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder { + color: #CBD5E0; +} + +.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow { + height: 100%; + right: 8px; +} + +.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single, +.jconfirm-box .select2-container--default .select2-selection--single:hover { + border-color: #6690F4; + box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.1); + outline: 0; +} + +.jconfirm-box .select2-container .select2-dropdown { + border-color: #E2E8F0; + border-radius: 0 0 6px 6px; + font-size: 14px; +} + +.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field { + padding: 6px 10px; + border-radius: 4px; + border: 1px solid #E2E8F0; + font-size: 14px; +} + +.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] { + background-color: #6690F4; + color: #FFFFFF; +} + +@media (max-width: 992px) { + .sidebar { + transform: translateX(-100%); + } + .sidebar.mobile-open { + transform: translateX(0); + } + .main-wrapper { + margin-left: 0 !important; + } +} diff --git a/layout/style.css.map b/layout/style.css.map index e6c15af..5f7207b 100644 --- a/layout/style.css.map +++ b/layout/style.css.map @@ -1 +1 @@ -{"version":3,"sources":["style.scss"],"names":[],"mappings":"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","file":"style.css","sourcesContent":["$cBlue: #6690F4;\r\n$cRed: #aa0505;\r\n$cGreen: #43833f;\r\n$cGreenLight: #57b951;\r\n$cBlack: #4e5e6a;\r\n\r\n.animate {\r\n animation: mymove 3s infinite;\r\n}\r\n\r\n.text-right {\r\n text-align: right;\r\n}\r\n\r\n.text-bold {\r\n font-weight: 900 !important;\r\n}\r\n\r\n.nowrap {\r\n white-space: nowrap;\r\n}\r\n\r\ntable {\r\n border-collapse: collapse;\r\n}\r\n\r\nsmall {\r\n font-size: .75em;\r\n}\r\n\r\ntable {\r\n font-size: 13px;\r\n}\r\n\r\n@keyframes mymove {\r\n 50% {\r\n opacity: .33;\r\n }\r\n}\r\n\r\n/* motion */\r\n@keyframes gradient-animation {\r\n 0% {\r\n background-position: 15% 0%;\r\n }\r\n\r\n 50% {\r\n background-position: 85% 100%;\r\n }\r\n\r\n 100% {\r\n background-position: 15% 0%;\r\n }\r\n}\r\n\r\n@keyframes frame-enter {\r\n 0% {\r\n clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), 3px calc(100% - 3px), 3px 100%, 100% 100%, 100% 0%, 0% 0%);\r\n }\r\n\r\n 25% {\r\n clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%, 100% 100%, 100% 0%, 0% 0%);\r\n }\r\n\r\n 50% {\r\n clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, 100% 0%, 0% 0%);\r\n }\r\n\r\n 75% {\r\n -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 0%, 0% 0%);\r\n }\r\n\r\n 100% {\r\n -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 0% 100%);\r\n }\r\n}\r\n\r\n* {\r\n box-sizing: border-box;\r\n}\r\n\r\nbody {\r\n font-family: \"Open Sans\", sans-serif;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 15px;\r\n color: #4e5e6a;\r\n}\r\n\r\n.btn {\r\n padding: 12px 25px;\r\n transition: all 0.3s ease;\r\n color: #FFF;\r\n border: 0;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n display: inline-flex;\r\n text-decoration: none;\r\n gap: 5px;\r\n justify-content: center;\r\n align-items: center;\r\n\r\n &.btn_small,\r\n &.btn-xs {\r\n padding: 5px 7px;\r\n font-size: 13px;\r\n\r\n i {\r\n font-size: 12px;\r\n }\r\n }\r\n\r\n &.btn-success {\r\n background: #57b951;\r\n\r\n &:hover {\r\n background: #4a9c3b;\r\n }\r\n }\r\n\r\n &.btn-primary {\r\n background: $cBlue;\r\n\r\n &:hover {\r\n background: #3164db;\r\n }\r\n }\r\n\r\n &.btn-danger {\r\n background: #cc0000;\r\n\r\n &:hover {\r\n background: #b30000;\r\n }\r\n }\r\n}\r\n\r\n.hide {\r\n display: none;\r\n}\r\n\r\n.form-error {\r\n color: #cc0000;\r\n font-size: 13px;\r\n}\r\n\r\n.input-group {\r\n margin-bottom: 10px;\r\n}\r\n\r\ninput[type=\"checkbox\"] {\r\n border: 1px solid #eee;\r\n}\r\n\r\n.form-control {\r\n border: 1px solid #eee;\r\n border-radius: 3px;\r\n height: 35px;\r\n width: 100%;\r\n padding: 5px;\r\n font-family: \"Open Sans\", sans-serif;\r\n\r\n option {\r\n padding: 5px;\r\n }\r\n\r\n &:focus {\r\n border: 1px solid $cBlue;\r\n outline: none;\r\n }\r\n}\r\n\r\n.unlogged {\r\n background: #EEF1F9;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n height: 100vh;\r\n\r\n .box-login {\r\n background: #FFF;\r\n padding: 25px;\r\n border-radius: 6px;\r\n width: 400px;\r\n\r\n .title {\r\n text-align: center;\r\n padding: 10px 10px 25px;\r\n border-bottom: 1px solid #eee;\r\n font-size: 20px;\r\n margin-bottom: 25px;\r\n }\r\n }\r\n}\r\n\r\nbody>.top {\r\n background: #FFF;\r\n border-bottom: 1px solid #eee;\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n\r\n .logo {\r\n a {\r\n display: inline-flex;\r\n color: $cBlue;\r\n padding: 10px;\r\n text-decoration: none;\r\n\r\n span {\r\n font-weight: 600;\r\n }\r\n }\r\n }\r\n\r\n .user-nav {\r\n position: relative;\r\n font-size: 14px;\r\n padding: 10px;\r\n\r\n .trigger {\r\n cursor: pointer;\r\n\r\n i {\r\n color: $cBlue;\r\n }\r\n }\r\n\r\n ul {\r\n position: absolute;\r\n top: 100%;\r\n left: 0;\r\n background: #FFF;\r\n margin: 0;\r\n padding: 0;\r\n width: 100%;\r\n list-style-type: none;\r\n border: 1px solid #eee;\r\n display: none;\r\n\r\n li {\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: #F8F9FA;\r\n }\r\n\r\n a {\r\n color: #333;\r\n display: block;\r\n text-decoration: none;\r\n padding: 10px;\r\n }\r\n }\r\n }\r\n\r\n &:hover {\r\n ul {\r\n display: block;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.main-menu {\r\n display: flex;\r\n box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075) !important;\r\n\r\n ul {\r\n display: flex;\r\n list-style-type: none;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 14px;\r\n\r\n li {\r\n cursor: pointer;\r\n\r\n a {\r\n color: #333;\r\n text-decoration: none;\r\n display: inline-flex;\r\n padding: 10px 15px;\r\n }\r\n\r\n &:hover {\r\n a {\r\n background: $cBlue;\r\n color: #FFF;\r\n }\r\n }\r\n\r\n ul {\r\n display: none;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.main {\r\n padding: 25px;\r\n background: #D9DEE2;\r\n min-height: calc(100vh - 80px);\r\n}\r\n\r\n.tasks_container {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 20px;\r\n\r\n .column {\r\n width: 350px;\r\n\r\n h2 {\r\n display: flex;\r\n padding: 10px;\r\n background: #FFF;\r\n margin-bottom: 10px;\r\n font-size: 15px;\r\n font-weight: 300;\r\n border-radius: 3px 3px 0 0;\r\n justify-content: space-between;\r\n align-items: center;\r\n\r\n i {\r\n cursor: pointer;\r\n }\r\n }\r\n\r\n &.tasks_suspended {\r\n h2 {\r\n border-bottom: 5px solid #cc0000;\r\n }\r\n }\r\n\r\n &.tasks_new {\r\n h2 {\r\n border-bottom: 5px solid #ccc;\r\n }\r\n }\r\n\r\n &.tasks_bulk {\r\n h2 {\r\n border-bottom: 5px solid #ff8c00;\r\n }\r\n }\r\n\r\n &.tasks_to_do {\r\n h2 {\r\n border-bottom: 5px solid #2aaf47;\r\n }\r\n }\r\n\r\n &.tasks_to_review {\r\n h2 {\r\n border-bottom: 5px solid #2535c9;\r\n }\r\n }\r\n\r\n &.tasks_closed {\r\n h2 {\r\n border-bottom: 5px solid #000;\r\n }\r\n }\r\n\r\n ul {\r\n list-style-type: none;\r\n margin: 0;\r\n padding: 0;\r\n\r\n .task {\r\n margin-bottom: 5px;\r\n background: #FFF;\r\n padding: 10px;\r\n border-radius: 0;\r\n display: flex;\r\n position: relative;\r\n\r\n &.notopened {\r\n border: 2px solid #cc0000;\r\n }\r\n\r\n .left {\r\n width: 30px;\r\n\r\n .users {\r\n display: flex;\r\n gap: 5px;\r\n flex-wrap: wrap;\r\n\r\n .user {\r\n display: flex;\r\n width: 20px;\r\n height: 20px;\r\n border-radius: 50%;\r\n justify-content: center;\r\n align-items: center;\r\n color: #FFF;\r\n font-size: 13px;\r\n }\r\n }\r\n }\r\n\r\n .middle {\r\n width: calc(100% - 60px);\r\n }\r\n\r\n .right {\r\n width: 30px;\r\n display: flex;\r\n flex-wrap: wrap;\r\n justify-content: flex-end;\r\n\r\n .recursively {\r\n color: #ccc;\r\n border-radius: 3px;\r\n cursor: pointer;\r\n margin-bottom: 5px;\r\n width: 22px;\r\n height: 22px;\r\n text-align: center;\r\n\r\n i {\r\n font-size: 15px;\r\n }\r\n }\r\n\r\n .task_start {\r\n background: #57b951;\r\n color: #FFF;\r\n border-radius: 3px;\r\n cursor: pointer;\r\n margin-bottom: 5px;\r\n width: 22px;\r\n height: 22px;\r\n text-align: center;\r\n\r\n &.hidden {\r\n display: none;\r\n }\r\n\r\n i {\r\n font-size: 12px;\r\n }\r\n }\r\n\r\n .task_end {\r\n background: #cc0000;\r\n color: #FFF;\r\n border-radius: 3px;\r\n cursor: pointer;\r\n margin-bottom: 5px;\r\n width: 22px;\r\n height: 22px;\r\n text-align: center;\r\n\r\n &.hidden {\r\n display: none;\r\n }\r\n\r\n i {\r\n font-size: 12px;\r\n }\r\n }\r\n }\r\n\r\n .name {\r\n font-size: 14px;\r\n color: #333;\r\n text-decoration: none;\r\n display: block;\r\n margin-bottom: 5px;\r\n }\r\n\r\n .bottom {\r\n display: flex;\r\n justify-content: space-between;\r\n }\r\n\r\n .client_info,\r\n .current_status {\r\n font-size: 12px;\r\n font-weight: 400;\r\n\r\n strong {\r\n font-weight: 600;\r\n }\r\n }\r\n\r\n .current_status {\r\n position: relative;\r\n cursor: pointer;\r\n\r\n .status_change {\r\n position: absolute;\r\n left: 0;\r\n top: 20px;\r\n background: #fff;\r\n padding: 15px;\r\n border: 1px solid #dfdfdf;\r\n border-radius: 3px;\r\n cursor: pointer;\r\n box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);\r\n z-index: 99;\r\n display: none;\r\n\r\n select {\r\n width: 250px;\r\n padding: 10px;\r\n border: 1px solid #eee;\r\n border-radius: 3px;\r\n\r\n option {\r\n font-size: 15px;\r\n padding: 3px;\r\n }\r\n }\r\n }\r\n }\r\n\r\n .dates {\r\n margin-bottom: 5px;\r\n display: flex;\r\n justify-content: space-between;\r\n font-size: 12px;\r\n\r\n .danger {\r\n color: #cc0000;\r\n font-weight: 600;\r\n }\r\n\r\n .warning {\r\n color: #ff8c00;\r\n }\r\n\r\n i {\r\n font-size: 12px;\r\n color: #C9CED4;\r\n margin-right: 5px;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n.action_menu {\r\n display: flex;\r\n margin-bottom: 25px;\r\n gap: 20px;\r\n\r\n .btn {\r\n display: inline-flex;\r\n padding: 7px 15px;\r\n color: #FFF;\r\n border-radius: 3px;\r\n text-decoration: none;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 5px;\r\n\r\n &.btn_add {\r\n background: #57b951;\r\n\r\n &:hover {\r\n background: #4a9c3b;\r\n }\r\n }\r\n\r\n &.btn_cancel {\r\n background: #cc0000;\r\n\r\n &:hover {\r\n background: #b30000;\r\n }\r\n }\r\n\r\n &.disabled {\r\n opacity: .5;\r\n }\r\n }\r\n}\r\n\r\n.form_container {\r\n background: #FFF;\r\n padding: 25px;\r\n max-width: 1300px;\r\n\r\n &.full {\r\n max-width: 100%;\r\n }\r\n\r\n .form_group {\r\n margin-bottom: 10px;\r\n display: flex;\r\n\r\n >.label {\r\n width: 300px;\r\n display: inline-flex;\r\n align-items: flex-start;\r\n justify-content: right;\r\n padding-right: 10px;\r\n }\r\n\r\n .input {\r\n width: calc(100% - 300px);\r\n }\r\n }\r\n}\r\n\r\n.task_popup {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: none;\r\n\r\n .task_details {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n background: #FFF;\r\n padding: 25px;\r\n border-radius: 6px;\r\n max-width: 1140px;\r\n width: 100%;\r\n\r\n .title {\r\n font-size: 20px;\r\n margin-bottom: 25px;\r\n\r\n a {\r\n color: #333;\r\n text-decoration: none;\r\n margin-right: 10px;\r\n\r\n &.task-delete {\r\n color: #cc0000;\r\n }\r\n }\r\n }\r\n\r\n .close {\r\n position: absolute;\r\n top: 10px;\r\n right: 10px;\r\n cursor: pointer;\r\n }\r\n\r\n .content {\r\n display: flex;\r\n font-size: 14px;\r\n\r\n h3 {\r\n width: 100%;\r\n margin-top: 0;\r\n margin-bottom: 5px;\r\n font-weight: 500;\r\n color: #000;\r\n font-size: 17px;\r\n }\r\n\r\n .left {\r\n width: 70%;\r\n max-height: 700px;\r\n overflow-y: auto;\r\n\r\n .users {\r\n display: flex;\r\n gap: 20px;\r\n\r\n .user {\r\n display: flex;\r\n gap: 10px;\r\n align-items: center;\r\n margin-bottom: 10px;\r\n\r\n .avatar {\r\n height: 30px;\r\n width: 30px;\r\n border-radius: 50%;\r\n background: #ccc;\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n color: #FFF;\r\n }\r\n }\r\n }\r\n\r\n .comments {\r\n border-radius: 3px;\r\n padding: 0 15px 15px 0;\r\n margin-bottom: 15px;\r\n border-bottom: 1px solid #eee;\r\n\r\n .new_comment {\r\n margin-bottom: 15px;\r\n\r\n textarea {\r\n height: 75px;\r\n }\r\n\r\n .add_comment {\r\n background: #57b951;\r\n color: #FFF;\r\n padding: 10px;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 200px;\r\n text-decoration: none;\r\n\r\n &:hover {\r\n background: #4a9c3b;\r\n }\r\n }\r\n }\r\n\r\n ul {\r\n margin: 0;\r\n padding: 0;\r\n list-style-type: none;\r\n\r\n li {\r\n background: #eee;\r\n margin-bottom: 5px;\r\n padding: 15px;\r\n border-radius: 6px;\r\n position: relative;\r\n\r\n .delete_comment {\r\n position: absolute;\r\n top: 10px;\r\n right: 10px;\r\n cursor: pointer;\r\n color: #cc0000;\r\n }\r\n\r\n .author {\r\n font-weight: 600;\r\n margin-bottom: 5px;\r\n display: inline-flex;\r\n margin-right: 10px;\r\n }\r\n\r\n .date {\r\n font-size: 12px;\r\n margin-bottom: 5px;\r\n display: inline-flex;\r\n }\r\n\r\n .text {\r\n margin-bottom: 15px;\r\n font-size: 13px;\r\n }\r\n }\r\n }\r\n }\r\n\r\n .checklist {\r\n border-radius: 3px;\r\n padding: 0 15px 15px 0;\r\n margin-bottom: 15px;\r\n border-bottom: 1px solid #eee;\r\n\r\n .new_element {\r\n display: flex;\r\n margin-bottom: 15px;\r\n\r\n a {\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 10px;\r\n border-radius: 0 6px 6px 0;\r\n text-decoration: none;\r\n width: 35px;\r\n background: #57b951;\r\n color: #FFF;\r\n }\r\n }\r\n\r\n ul {\r\n margin: 0;\r\n padding: 0;\r\n list-style-type: none;\r\n\r\n li {\r\n display: flex;\r\n gap: 10px;\r\n margin-bottom: 5px;\r\n background: #FFF;\r\n border-radius: 3px;\r\n padding: 5px;\r\n border: 1px solid #eee;\r\n font-size: 13px;\r\n align-items: center;\r\n\r\n i {\r\n margin-left: auto;\r\n margin-right: 0;\r\n cursor: pointer;\r\n color: #cc0000;\r\n }\r\n }\r\n }\r\n }\r\n\r\n .description {\r\n padding: 15px;\r\n border-radius: 3px;\r\n background: #F6F8F9;\r\n margin-bottom: 15px;\r\n }\r\n }\r\n\r\n .right {\r\n width: 30%;\r\n padding: 0 15px 15px;\r\n\r\n .box {\r\n margin-bottom: 15px;\r\n }\r\n\r\n .time {\r\n a {\r\n display: block;\r\n padding: 10px;\r\n border-radius: 6px;\r\n margin-top: 10px;\r\n text-decoration: none;\r\n text-align: center;\r\n width: 100%;\r\n\r\n &.task_start {\r\n background: #57b951;\r\n color: #FFF;\r\n }\r\n\r\n &.task_end {\r\n background: #cc0000;\r\n color: #FFF;\r\n }\r\n\r\n &.hidden {\r\n display: none;\r\n }\r\n }\r\n }\r\n\r\n .dates {\r\n display: flex;\r\n justify-content: space-between;\r\n flex-wrap: wrap;\r\n\r\n .danger {\r\n color: #cc0000;\r\n font-weight: 600;\r\n }\r\n\r\n .warning {\r\n color: #ff8c00;\r\n }\r\n\r\n i {\r\n color: #C9CED4;\r\n margin-right: 5px;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n.table {\r\n width: 100%;\r\n\r\n th,\r\n td {\r\n border: 1px solid #eee;\r\n padding: 5px;\r\n }\r\n\r\n td.center {\r\n text-align: center;\r\n }\r\n\r\n td.left {\r\n text-align: left;\r\n }\r\n\r\n &.table-sm {\r\n\r\n td {\r\n padding: 5px !important;\r\n }\r\n }\r\n\r\n input.form-control {\r\n font-size: 13px;\r\n }\r\n}\r\n\r\n.projects_container {\r\n background: #FFF;\r\n padding: 15px;\r\n border-radius: 6px;\r\n display: flex;\r\n gap: 20px;\r\n\r\n .left {\r\n display: flex;\r\n gap: 20px;\r\n flex-wrap: wrap;\r\n width: calc(100% - 250px);\r\n }\r\n\r\n .right {\r\n width: 250px;\r\n display: flex;\r\n flex-wrap: wrap;\r\n align-items: flex-start;\r\n justify-content: center;\r\n gap: 20px;\r\n border-left: 1px solid #eee;\r\n padding-left: 15px;\r\n\r\n select {\r\n width: 200px;\r\n }\r\n }\r\n}\r\n\r\n.default_popup {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.5);\r\n display: none;\r\n\r\n .popup_content {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n background: #FFF;\r\n padding: 25px;\r\n border-radius: 6px;\r\n max-width: 1140px;\r\n width: 100%;\r\n\r\n .close {\r\n position: absolute;\r\n top: 10px;\r\n right: 10px;\r\n cursor: pointer;\r\n }\r\n }\r\n}\r\n\r\n#fg-cron {\r\n margin: 10px 0;\r\n\r\n .countdown {\r\n background: #57b951;\r\n color: #FFF;\r\n padding: 10px;\r\n }\r\n\r\n #cron-container {\r\n max-height: 300px;\r\n overflow-x: hidden;\r\n overflow-y: auto;\r\n\r\n .msg {\r\n font-size: 13px;\r\n padding: 5px;\r\n border-bottom: 1px solid #e8e8e8;\r\n }\r\n }\r\n}\r\n\r\n.card {\r\n background: #FFF;\r\n padding: 25px;\r\n border-radius: 6px;\r\n color: #000;\r\n font-size: 15px;\r\n max-width: 1280px;\r\n\r\n &.mb25 {\r\n margin-bottom: 25px;\r\n }\r\n\r\n .card-header {\r\n font-weight: 600;\r\n }\r\n\r\n .card-body {\r\n padding-top: 10px;\r\n\r\n table {\r\n border-collapse: collapse;\r\n\r\n th,\r\n td {\r\n font-size: 14px;\r\n\r\n &.bold {\r\n font-weight: 600;\r\n }\r\n\r\n &.text-right {\r\n text-align: right;\r\n }\r\n\r\n &.text-center {\r\n text-align: center;\r\n }\r\n\r\n .close-task {\r\n text-decoration: none;\r\n color: $cBlue;\r\n\r\n &:hover {\r\n color: #cc0000;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n.finance-summary {\r\n display: grid;\r\n grid-template-columns: 1fr 1fr 1fr 1fr;\r\n gap: 10px;\r\n\r\n .panel {\r\n background: #FFF;\r\n border-radius: 6px;\r\n padding: 15px;\r\n\r\n h1 {\r\n font-size: 20px;\r\n margin: 0;\r\n }\r\n\r\n span {\r\n font-size: 0.85em;\r\n }\r\n }\r\n}\r\n\r\n.finance-manager {\r\n display: grid;\r\n gap: 10px;\r\n grid-template-columns: 200px 1fr 500px;\r\n padding-top: 25px;\r\n\r\n .manage-menu {\r\n display: inline-block;\r\n margin-right: 10px;\r\n\r\n a {\r\n color: #333333;\r\n text-decoration: none;\r\n font-weight: 300;\r\n display: block;\r\n }\r\n }\r\n\r\n .actions {\r\n width: 100px;\r\n text-align: center;\r\n\r\n a {\r\n display: inline-flex;\r\n margin: 0 2px;\r\n height: 25px;\r\n width: 25px;\r\n align-items: center;\r\n justify-content: center;\r\n text-decoration: none;\r\n color: #000;\r\n border: 1px solid #e7e7e7;\r\n font-size: 11px;\r\n border-radius: 3px;\r\n transition: all 0.3s ease;\r\n\r\n &:hover {\r\n border: 1px solid $cBlue;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.bootstrap-tagsinput {\r\n .tag {\r\n background: $cBlue;\r\n font-size: 13px;\r\n padding: 5px 10px;\r\n border-radius: 12px;\r\n\r\n [data-role=\"remove\"] {\r\n color: #FFF !important;\r\n\r\n &:hover {\r\n box-shadow: none !important;\r\n color: #000 !important\r\n }\r\n }\r\n }\r\n}\r\n\r\n.finance-tags {\r\n display: flex;\r\n flex-wrap: wrap;\r\n gap: 5px;\r\n\r\n a:not(.btn) {\r\n display: flex;\r\n width: 100%;\r\n text-decoration: none;\r\n color: $cBlack;\r\n\r\n &.zoom-100 {\r\n font-size: 130%;\r\n }\r\n\r\n &.zoom-90 {\r\n font-size: 120%;\r\n }\r\n\r\n &.zoom-80 {\r\n font-size: 110%;\r\n }\r\n\r\n &.zoom-70 {\r\n font-size: 100%;\r\n }\r\n\r\n &.zoom-60 {\r\n font-size: 95%;\r\n }\r\n\r\n &.zoom-50 {\r\n font-size: 85%;\r\n }\r\n\r\n &.zoom-40 {\r\n font-size: 80%;\r\n }\r\n\r\n &.zoom-30 {\r\n font-size: 75%;\r\n }\r\n\r\n &.zoom-20 {\r\n font-size: 75%;\r\n }\r\n\r\n &.zoom-10 {\r\n font-size: 70%;\r\n }\r\n\r\n &.zoom-0 {\r\n font-size: 70%;\r\n }\r\n }\r\n}\r\n\r\n.manage-menu {\r\n position: relative;\r\n\r\n .context-menu {\r\n border-left: 4px dotted #000;\r\n width: 1px;\r\n height: 100%;\r\n }\r\n\r\n .context-menu-container {\r\n position: absolute;\r\n display: none;\r\n background: #FFF;\r\n box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1);\r\n top: 2px;\r\n left: 2px;\r\n\r\n ul {\r\n list-style-type: none;\r\n margin: 0;\r\n padding: 0;\r\n\r\n li {\r\n a {\r\n display: block;\r\n padding: 7px 15px;\r\n white-space: nowrap;\r\n\r\n &:hover {\r\n background: #f8f8f8;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n &:hover {\r\n .context-menu-container {\r\n display: block;\r\n }\r\n }\r\n}\r\n\r\n.dt-layout-table {\r\n margin-bottom: 25px;\r\n}\r\n\r\n.pagination {\r\n button {\r\n border: 1px solid #eee;\r\n background: #FFF;\r\n display: inline-flex;\r\n height: 30px;\r\n width: 30px;\r\n align-items: center;\r\n justify-content: center;\r\n margin: 0 2px;\r\n transition: all 0.3s ease;\r\n\r\n &:hover {\r\n background: #eee;\r\n }\r\n }\r\n}\r\n\r\ntable {\r\n &#products {\r\n .table-product-title {\r\n display: flex;\r\n justify-content: space-between;\r\n }\r\n\r\n .edit-product-title {\r\n display: flex;\r\n height: 25px;\r\n align-items: center;\r\n justify-content: center;\r\n width: 25px;\r\n cursor: pointer;\r\n background: #fff;\r\n border: 1px solid #9b9b9b;\r\n color: #9b9b9b;\r\n\r\n &:hover {\r\n background: #9b9b9b;\r\n color: #fff;\r\n }\r\n }\r\n\r\n a {\r\n &.custom_name {\r\n color: $cGreenLight !important;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.chart-with-form {\r\n display: flex;\r\n gap: 20px;\r\n align-items: flex-start;\r\n}\r\n\r\n.chart-area {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n}\r\n\r\n.comment-form {\r\n width: 360px;\r\n /* stała, wygodna szerokość prawej kolumny */\r\n flex: 0 0 360px;\r\n}\r\n\r\n.comment-form .form-group {\r\n margin-bottom: 12px;\r\n}\r\n\r\n.comment-form label {\r\n display: block;\r\n font-weight: 600;\r\n margin-bottom: 6px;\r\n}\r\n\r\n.comment-form input[type=\"date\"],\r\n.comment-form textarea {\r\n width: 100%;\r\n border: 1px solid #ccc;\r\n border-radius: 4px;\r\n padding: 0 8px;\r\n font-size: 14px;\r\n}\r\n\r\n.comment-form textarea {\r\n min-height: 120px;\r\n resize: vertical;\r\n}\r\n\r\n.comment-form .btn {\r\n display: inline-block;\r\n padding: 8px 14px;\r\n border-radius: 4px;\r\n border: 0;\r\n background: #337ab7;\r\n color: #fff;\r\n font-weight: 600;\r\n cursor: pointer;\r\n}\r\n\r\n.comment-form .btn[disabled] {\r\n opacity: .6;\r\n cursor: not-allowed;\r\n}\r\n\r\n.comment-form .hint {\r\n font-size: 12px;\r\n color: #666;\r\n}\r\n\r\n/* === Select2 w modalu \"Edytuj produkt\" === */\r\n\r\n/* pełna szerokość i taki sam odstęp jak inne pola */\r\n.jconfirm-box .form-group .select2-container {\r\n width: 100% !important;\r\n margin-top: 8px;\r\n}\r\n\r\n/* wygląd \"inputa\" */\r\n.jconfirm-box .select2-container--default .select2-selection--single {\r\n background-color: #fff;\r\n border: 1px solid #ced4da;\r\n /* jak bootstrapowe inputy */\r\n border-radius: 3px;\r\n min-height: 42px;\r\n /* wysokość podobna do pola tytułu/opisu */\r\n display: flex;\r\n align-items: center;\r\n padding: 4px 12px;\r\n box-shadow: none;\r\n transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;\r\n font-size: 14px;\r\n}\r\n\r\n/* tekst w środku */\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered {\r\n padding-left: 0;\r\n line-height: 1.4;\r\n color: #495057;\r\n}\r\n\r\n/* placeholder */\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder {\r\n color: #adb5bd;\r\n}\r\n\r\n/* strzałka po prawej – wyrównanie */\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow {\r\n height: 100%;\r\n right: 8px;\r\n}\r\n\r\n/* efekt hover/focus – jak na form-control */\r\n.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,\r\n.jconfirm-box .select2-container--default .select2-selection--single:hover {\r\n border-color: #80bdff;\r\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, .25);\r\n outline: 0;\r\n}\r\n\r\n/* dropdown (lista kategorii) */\r\n.jconfirm-box .select2-container .select2-dropdown {\r\n border-color: #ced4da;\r\n border-radius: 0 0 3px 3px;\r\n font-size: 14px;\r\n}\r\n\r\n/* pole wyszukiwania w dropdownie */\r\n.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field {\r\n padding: 6px 10px;\r\n border-radius: 3px;\r\n border: 1px solid #ced4da;\r\n font-size: 14px;\r\n}\r\n\r\n/* podświetlenie zaznaczonej pozycji */\r\n.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] {\r\n background-color: #007bff;\r\n color: #fff;\r\n}"]} \ No newline at end of file +{"version":3,"sources":["style.scss"],"names":[],"mappings":"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","file":"style.css","sourcesContent":["// === adsPRO - Nowe style ===\r\n\r\n// --- Zmienne ---\r\n$cPrimary: #6690F4;\r\n$cPrimaryDark: #3164db;\r\n$cSidebarBg: #1E2A3A;\r\n$cSidebarText: #A8B7C7;\r\n$cSidebarHover: #263548;\r\n$cSidebarActive: $cPrimary;\r\n$cContentBg: #F4F6F9;\r\n$cWhite: #FFFFFF;\r\n$cText: #4E5E6A;\r\n$cTextDark: #2D3748;\r\n$cBorder: #E2E8F0;\r\n$cSuccess: #57B951;\r\n$cSuccessDark: #4a9c3b;\r\n$cDanger: #CC0000;\r\n$cDangerDark: #b30000;\r\n$cWarning: #FF8C00;\r\n$cGreenLight: #57b951;\r\n\r\n$sidebarWidth: 260px;\r\n$sidebarCollapsed: 70px;\r\n$topbarHeight: 56px;\r\n$transitionSpeed: 0.3s;\r\n\r\n// --- Reset i baza ---\r\n* {\r\n box-sizing: border-box;\r\n}\r\n\r\nbody {\r\n font-family: \"Open Sans\", sans-serif;\r\n margin: 0;\r\n padding: 0;\r\n font-size: 14px;\r\n color: $cText;\r\n background: $cContentBg;\r\n}\r\n\r\n.hide {\r\n display: none;\r\n}\r\n\r\n// --- Typografia ---\r\nsmall {\r\n font-size: .75em;\r\n}\r\n\r\n.text-right {\r\n text-align: right;\r\n}\r\n\r\n.text-bold {\r\n font-weight: 700 !important;\r\n}\r\n\r\n.nowrap {\r\n white-space: nowrap;\r\n}\r\n\r\n// ===========================\r\n// LOGIN PAGE (unlogged)\r\n// ===========================\r\nbody.unlogged {\r\n background: $cContentBg;\r\n margin: 0;\r\n padding: 0;\r\n}\r\n\r\n.login-container {\r\n display: flex;\r\n min-height: 100vh;\r\n}\r\n\r\n.login-brand {\r\n flex: 0 0 45%;\r\n background: linear-gradient(135deg, $cSidebarBg 0%, #2C3E57 50%, $cPrimary 100%);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px;\r\n position: relative;\r\n overflow: hidden;\r\n\r\n &::before {\r\n content: '';\r\n position: absolute;\r\n top: -50%;\r\n right: -50%;\r\n width: 100%;\r\n height: 100%;\r\n background: radial-gradient(circle, rgba($cPrimary, 0.15) 0%, transparent 70%);\r\n border-radius: 50%;\r\n }\r\n\r\n .brand-content {\r\n position: relative;\r\n z-index: 1;\r\n color: $cWhite;\r\n max-width: 400px;\r\n }\r\n\r\n .brand-logo {\r\n font-size: 48px;\r\n font-weight: 300;\r\n margin-bottom: 20px;\r\n letter-spacing: -1px;\r\n\r\n strong {\r\n font-weight: 700;\r\n }\r\n }\r\n\r\n .brand-tagline {\r\n font-size: 18px;\r\n opacity: 0.85;\r\n line-height: 1.6;\r\n margin-bottom: 50px;\r\n }\r\n\r\n .brand-features {\r\n .feature {\r\n display: flex;\r\n align-items: center;\r\n gap: 15px;\r\n margin-bottom: 20px;\r\n opacity: 0.8;\r\n\r\n i {\r\n font-size: 20px;\r\n width: 40px;\r\n height: 40px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n background: rgba($cWhite, 0.1);\r\n border-radius: 10px;\r\n }\r\n\r\n span {\r\n font-size: 15px;\r\n }\r\n }\r\n }\r\n}\r\n\r\n.login-form-wrapper {\r\n flex: 1;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n padding: 60px;\r\n background: $cWhite;\r\n}\r\n\r\n.login-box {\r\n width: 100%;\r\n max-width: 420px;\r\n\r\n .login-header {\r\n margin-bottom: 35px;\r\n\r\n h1 {\r\n font-size: 28px;\r\n font-weight: 700;\r\n color: $cTextDark;\r\n margin: 0 0 8px;\r\n }\r\n\r\n p {\r\n color: #718096;\r\n font-size: 15px;\r\n margin: 0;\r\n }\r\n }\r\n\r\n .form-group {\r\n margin-bottom: 20px;\r\n\r\n label {\r\n display: block;\r\n font-size: 13px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n margin-bottom: 6px;\r\n }\r\n }\r\n\r\n .input-with-icon {\r\n position: relative;\r\n\r\n i {\r\n position: absolute;\r\n left: 14px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n color: #A0AEC0;\r\n font-size: 14px;\r\n }\r\n\r\n .form-control {\r\n padding-left: 42px;\r\n }\r\n }\r\n\r\n .form-control {\r\n width: 100%;\r\n height: 46px;\r\n border: 2px solid $cBorder;\r\n border-radius: 8px;\r\n padding: 0 14px;\r\n font-size: 14px;\r\n font-family: \"Open Sans\", sans-serif;\r\n color: $cTextDark;\r\n transition: border-color $transitionSpeed, box-shadow $transitionSpeed;\r\n\r\n &::placeholder {\r\n color: #CBD5E0;\r\n }\r\n\r\n &:focus {\r\n border-color: $cPrimary;\r\n box-shadow: 0 0 0 3px rgba($cPrimary, 0.15);\r\n outline: none;\r\n }\r\n }\r\n\r\n .form-error {\r\n color: $cDanger;\r\n font-size: 12px;\r\n margin-top: 4px;\r\n }\r\n\r\n .checkbox-group {\r\n .checkbox-label {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n cursor: pointer;\r\n font-size: 13px;\r\n color: #718096;\r\n font-weight: 400;\r\n\r\n input[type=\"checkbox\"] {\r\n width: 16px;\r\n height: 16px;\r\n accent-color: $cPrimary;\r\n }\r\n }\r\n }\r\n\r\n .btn-login {\r\n width: 100%;\r\n height: 48px;\r\n font-size: 15px;\r\n font-weight: 600;\r\n border-radius: 8px;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n gap: 8px;\r\n\r\n &.disabled {\r\n opacity: 0.7;\r\n pointer-events: none;\r\n }\r\n }\r\n\r\n .alert {\r\n display: none;\r\n padding: 12px 16px;\r\n border-radius: 8px;\r\n font-size: 13px;\r\n margin-bottom: 20px;\r\n\r\n &.alert-danger {\r\n background: #FFF5F5;\r\n color: $cDanger;\r\n border: 1px solid #FED7D7;\r\n }\r\n\r\n &.alert-success {\r\n background: #F0FFF4;\r\n color: #276749;\r\n border: 1px solid #C6F6D5;\r\n }\r\n }\r\n}\r\n\r\n// Responsywność logowania\r\n@media (max-width: 768px) {\r\n .login-brand {\r\n display: none;\r\n }\r\n\r\n .login-form-wrapper {\r\n padding: 30px 20px;\r\n }\r\n}\r\n\r\n// ===========================\r\n// LAYOUT (logged) - SIDEBAR\r\n// ===========================\r\nbody.logged {\r\n display: flex;\r\n min-height: 100vh;\r\n background: $cContentBg;\r\n}\r\n\r\n// --- Sidebar ---\r\n.sidebar {\r\n width: $sidebarWidth;\r\n min-height: 100vh;\r\n background: $cSidebarBg;\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n z-index: 1000;\r\n display: flex;\r\n flex-direction: column;\r\n transition: width $transitionSpeed ease;\r\n overflow: hidden;\r\n\r\n &.collapsed {\r\n width: $sidebarCollapsed;\r\n\r\n .sidebar-header {\r\n padding: 16px 0;\r\n justify-content: center;\r\n\r\n .sidebar-logo {\r\n display: none;\r\n }\r\n\r\n .sidebar-toggle i {\r\n transform: rotate(180deg);\r\n }\r\n }\r\n\r\n .sidebar-nav ul li a {\r\n padding: 12px 0;\r\n justify-content: center;\r\n\r\n span {\r\n display: none;\r\n }\r\n\r\n i {\r\n margin-right: 0;\r\n font-size: 18px;\r\n }\r\n }\r\n\r\n .sidebar-footer {\r\n .sidebar-user {\r\n justify-content: center;\r\n\r\n .user-info {\r\n display: none;\r\n }\r\n }\r\n\r\n .sidebar-logout {\r\n justify-content: center;\r\n\r\n span {\r\n display: none;\r\n }\r\n }\r\n }\r\n\r\n .nav-divider {\r\n margin: 8px 15px;\r\n }\r\n }\r\n}\r\n\r\n.sidebar-header {\r\n display: flex;\r\n align-items: center;\r\n justify-content: space-between;\r\n padding: 20px 20px 16px;\r\n border-bottom: 1px solid rgba($cWhite, 0.08);\r\n\r\n .sidebar-logo a {\r\n color: $cWhite;\r\n text-decoration: none;\r\n font-size: 24px;\r\n font-weight: 300;\r\n letter-spacing: -0.5px;\r\n\r\n strong {\r\n font-weight: 700;\r\n }\r\n }\r\n\r\n .sidebar-toggle {\r\n background: none;\r\n border: none;\r\n color: $cSidebarText;\r\n cursor: pointer;\r\n padding: 6px;\r\n border-radius: 6px;\r\n transition: all $transitionSpeed;\r\n\r\n &:hover {\r\n background: rgba($cWhite, 0.08);\r\n color: $cWhite;\r\n }\r\n\r\n i {\r\n transition: transform $transitionSpeed;\r\n }\r\n }\r\n}\r\n\r\n.sidebar-nav {\r\n flex: 1;\r\n padding: 12px 0;\r\n overflow-y: auto;\r\n\r\n ul {\r\n list-style: none;\r\n margin: 0;\r\n padding: 0;\r\n\r\n li {\r\n &.nav-divider {\r\n height: 1px;\r\n background: rgba($cWhite, 0.08);\r\n margin: 8px 20px;\r\n }\r\n\r\n a {\r\n display: flex;\r\n align-items: center;\r\n padding: 11px 20px;\r\n color: $cSidebarText;\r\n text-decoration: none;\r\n font-size: 14px;\r\n transition: all 0.2s;\r\n border-left: 3px solid transparent;\r\n\r\n i {\r\n width: 20px;\r\n text-align: center;\r\n margin-right: 12px;\r\n font-size: 15px;\r\n }\r\n\r\n &:hover {\r\n background: $cSidebarHover;\r\n color: $cWhite;\r\n }\r\n }\r\n\r\n &.active>a {\r\n background: rgba($cPrimary, 0.15);\r\n color: $cWhite;\r\n border-left-color: $cPrimary;\r\n\r\n i {\r\n color: $cPrimary;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n.sidebar-footer {\r\n padding: 16px 20px;\r\n border-top: 1px solid rgba($cWhite, 0.08);\r\n\r\n .sidebar-user {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n margin-bottom: 12px;\r\n\r\n .user-avatar {\r\n width: 34px;\r\n height: 34px;\r\n border-radius: 50%;\r\n background: rgba($cPrimary, 0.2);\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n color: $cPrimary;\r\n font-size: 14px;\r\n flex-shrink: 0;\r\n }\r\n\r\n .user-info {\r\n overflow: hidden;\r\n\r\n .user-email {\r\n color: $cSidebarText;\r\n font-size: 12px;\r\n display: block;\r\n white-space: nowrap;\r\n overflow: hidden;\r\n text-overflow: ellipsis;\r\n }\r\n }\r\n }\r\n\r\n .sidebar-logout {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n color: #E53E3E;\r\n text-decoration: none;\r\n font-size: 13px;\r\n padding: 8px 10px;\r\n border-radius: 6px;\r\n transition: all 0.2s;\r\n\r\n i {\r\n font-size: 14px;\r\n }\r\n\r\n &:hover {\r\n background: rgba(#E53E3E, 0.1);\r\n }\r\n }\r\n}\r\n\r\n// --- Main wrapper ---\r\n.main-wrapper {\r\n margin-left: $sidebarWidth;\r\n flex: 1;\r\n min-height: 100vh;\r\n transition: margin-left $transitionSpeed ease;\r\n display: flex;\r\n flex-direction: column;\r\n\r\n &.expanded {\r\n margin-left: $sidebarCollapsed;\r\n }\r\n}\r\n\r\n// --- Topbar ---\r\n.topbar {\r\n height: $topbarHeight;\r\n background: $cWhite;\r\n border-bottom: 1px solid $cBorder;\r\n display: flex;\r\n align-items: center;\r\n padding: 0 25px;\r\n position: sticky;\r\n top: 0;\r\n z-index: 500;\r\n\r\n .topbar-toggle {\r\n background: none;\r\n border: none;\r\n color: $cText;\r\n cursor: pointer;\r\n padding: 8px 10px;\r\n border-radius: 6px;\r\n font-size: 16px;\r\n margin-right: 15px;\r\n transition: all 0.2s;\r\n\r\n &:hover {\r\n background: $cContentBg;\r\n }\r\n }\r\n\r\n .topbar-breadcrumb {\r\n font-size: 16px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n }\r\n}\r\n\r\n// --- Content area ---\r\n.content {\r\n flex: 1;\r\n padding: 25px;\r\n}\r\n\r\n.app-alert {\r\n background: #EBF8FF;\r\n border: 1px solid #BEE3F8;\r\n color: #2B6CB0;\r\n padding: 12px 16px;\r\n border-radius: 8px;\r\n margin-bottom: 20px;\r\n font-size: 14px;\r\n}\r\n\r\n// ===========================\r\n// KOMPONENTY WSPÓLNE\r\n// ===========================\r\n\r\n// --- Buttons ---\r\n.btn {\r\n padding: 10px 20px;\r\n transition: all 0.2s ease;\r\n color: $cWhite;\r\n border: 0;\r\n border-radius: 6px;\r\n cursor: pointer;\r\n display: inline-flex;\r\n text-decoration: none;\r\n gap: 6px;\r\n justify-content: center;\r\n align-items: center;\r\n font-size: 14px;\r\n font-family: \"Open Sans\", sans-serif;\r\n font-weight: 500;\r\n\r\n &.btn_small,\r\n &.btn-xs,\r\n &.btn-sm {\r\n padding: 5px 10px;\r\n font-size: 12px;\r\n\r\n i {\r\n font-size: 11px;\r\n }\r\n }\r\n\r\n &.btn-success {\r\n background: $cSuccess;\r\n\r\n &:hover {\r\n background: $cSuccessDark;\r\n }\r\n }\r\n\r\n &.btn-primary {\r\n background: $cPrimary;\r\n\r\n &:hover {\r\n background: $cPrimaryDark;\r\n }\r\n }\r\n\r\n &.btn-danger {\r\n background: $cDanger;\r\n\r\n &:hover {\r\n background: $cDangerDark;\r\n }\r\n }\r\n\r\n &.disabled {\r\n opacity: 0.6;\r\n pointer-events: none;\r\n }\r\n}\r\n\r\n// --- Form controls ---\r\n.form-control {\r\n border: 1px solid $cBorder;\r\n border-radius: 6px;\r\n height: 38px;\r\n width: 100%;\r\n padding: 6px 12px;\r\n font-family: \"Open Sans\", sans-serif;\r\n font-size: 14px;\r\n color: $cTextDark;\r\n transition: border-color 0.2s, box-shadow 0.2s;\r\n\r\n option {\r\n padding: 5px;\r\n }\r\n\r\n &:focus {\r\n border-color: $cPrimary;\r\n box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);\r\n outline: none;\r\n }\r\n}\r\n\r\ninput[type=\"checkbox\"] {\r\n border: 1px solid $cBorder;\r\n}\r\n\r\n// --- Tables ---\r\ntable {\r\n border-collapse: collapse;\r\n font-size: 13px;\r\n}\r\n\r\n.table {\r\n width: 100%;\r\n\r\n th,\r\n td {\r\n border: 1px solid $cBorder;\r\n padding: 8px 10px;\r\n }\r\n\r\n th {\r\n background: #F7FAFC;\r\n font-weight: 600;\r\n font-size: 12px;\r\n text-transform: uppercase;\r\n letter-spacing: 0.03em;\r\n color: #718096;\r\n }\r\n\r\n td.center {\r\n text-align: center;\r\n }\r\n\r\n td.left {\r\n text-align: left;\r\n }\r\n\r\n &.table-sm td {\r\n padding: 5px !important;\r\n }\r\n\r\n input.form-control {\r\n font-size: 13px;\r\n height: 32px;\r\n }\r\n}\r\n\r\n// --- Cards ---\r\n.card {\r\n background: $cWhite;\r\n padding: 20px;\r\n border-radius: 8px;\r\n color: $cTextDark;\r\n font-size: 14px;\r\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);\r\n\r\n &.mb25 {\r\n margin-bottom: 20px;\r\n }\r\n\r\n .card-header {\r\n font-weight: 600;\r\n font-size: 15px;\r\n }\r\n\r\n .card-body {\r\n padding-top: 12px;\r\n\r\n table {\r\n\r\n th,\r\n td {\r\n font-size: 13px;\r\n\r\n &.bold {\r\n font-weight: 600;\r\n }\r\n\r\n &.text-right {\r\n text-align: right;\r\n }\r\n\r\n &.text-center {\r\n text-align: center;\r\n }\r\n }\r\n }\r\n }\r\n}\r\n\r\n// --- Action menu ---\r\n.action_menu {\r\n display: flex;\r\n margin-bottom: 20px;\r\n gap: 12px;\r\n\r\n .btn {\r\n padding: 8px 16px;\r\n\r\n &.btn_add {\r\n background: $cSuccess;\r\n\r\n &:hover {\r\n background: $cSuccessDark;\r\n }\r\n }\r\n\r\n &.btn_cancel {\r\n background: $cDanger;\r\n\r\n &:hover {\r\n background: $cDangerDark;\r\n }\r\n }\r\n }\r\n}\r\n\r\n// --- Settings page ---\r\n.settings-card {\r\n background: $cWhite;\r\n border-radius: 10px;\r\n padding: 28px;\r\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\r\n\r\n .settings-card-header {\r\n display: flex;\r\n align-items: center;\r\n gap: 14px;\r\n margin-bottom: 24px;\r\n padding-bottom: 16px;\r\n border-bottom: 1px solid $cBorder;\r\n\r\n .settings-card-icon {\r\n width: 44px;\r\n height: 44px;\r\n border-radius: 10px;\r\n background: lighten($cPrimary, 26%);\r\n color: $cPrimary;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n font-size: 18px;\r\n flex-shrink: 0;\r\n }\r\n\r\n h3 {\r\n margin: 0;\r\n font-size: 17px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n }\r\n\r\n small {\r\n color: #8899A6;\r\n font-size: 13px;\r\n }\r\n }\r\n\r\n .settings-field {\r\n margin-bottom: 18px;\r\n\r\n label {\r\n display: block;\r\n font-size: 13px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n margin-bottom: 6px;\r\n }\r\n }\r\n\r\n .settings-input-wrap {\r\n position: relative;\r\n\r\n .settings-input-icon {\r\n position: absolute;\r\n left: 12px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n color: #A0AEC0;\r\n font-size: 14px;\r\n pointer-events: none;\r\n }\r\n\r\n .form-control {\r\n padding-left: 38px;\r\n }\r\n\r\n .settings-toggle-pw {\r\n position: absolute;\r\n right: 4px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n background: none;\r\n border: none;\r\n color: #A0AEC0;\r\n cursor: pointer;\r\n padding: 6px 10px;\r\n font-size: 14px;\r\n transition: color 0.2s;\r\n\r\n &:hover {\r\n color: $cPrimary;\r\n }\r\n }\r\n }\r\n\r\n .settings-fields-grid {\r\n display: grid;\r\n grid-template-columns: 1fr 1fr;\r\n gap: 0 24px;\r\n\r\n @media (max-width: 768px) {\r\n grid-template-columns: 1fr;\r\n }\r\n }\r\n\r\n .settings-alert-error {\r\n display: flex;\r\n align-items: center;\r\n gap: 10px;\r\n background: #FFF5F5;\r\n color: $cDanger;\r\n border: 1px solid #FED7D7;\r\n border-radius: 8px;\r\n padding: 12px 16px;\r\n margin-bottom: 20px;\r\n font-size: 13px;\r\n\r\n i {\r\n font-size: 16px;\r\n flex-shrink: 0;\r\n }\r\n }\r\n}\r\n\r\n// --- Clients page ---\r\n.clients-page {\r\n .clients-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 20px;\r\n\r\n h2 {\r\n margin: 0;\r\n font-size: 20px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n\r\n i {\r\n color: $cPrimary;\r\n margin-right: 8px;\r\n }\r\n }\r\n }\r\n\r\n .clients-table-wrap {\r\n background: $cWhite;\r\n border-radius: 10px;\r\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\r\n overflow: hidden;\r\n\r\n .table {\r\n margin: 0;\r\n\r\n thead th {\r\n background: #F8FAFC;\r\n border-bottom: 2px solid $cBorder;\r\n font-size: 12px;\r\n font-weight: 700;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n color: #8899A6;\r\n padding: 14px 20px;\r\n }\r\n\r\n tbody td {\r\n padding: 14px 20px;\r\n vertical-align: middle;\r\n border-bottom: 1px solid #F1F5F9;\r\n }\r\n\r\n tbody tr:hover {\r\n background: #F8FAFC;\r\n }\r\n\r\n .client-id {\r\n color: #8899A6;\r\n font-size: 13px;\r\n font-weight: 600;\r\n }\r\n\r\n .client-name {\r\n font-weight: 600;\r\n color: $cTextDark;\r\n }\r\n }\r\n }\r\n\r\n .badge-id {\r\n display: inline-block;\r\n background: #EEF2FF;\r\n color: $cPrimary;\r\n font-size: 13px;\r\n font-weight: 600;\r\n padding: 4px 10px;\r\n border-radius: 6px;\r\n font-family: monospace;\r\n }\r\n\r\n .actions-cell {\r\n text-align: center;\r\n white-space: nowrap;\r\n }\r\n\r\n .btn-icon {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 34px;\r\n height: 34px;\r\n border-radius: 8px;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 14px;\r\n transition: all 0.2s;\r\n margin: 0 2px;\r\n\r\n &.btn-icon-edit {\r\n background: #EEF2FF;\r\n color: $cPrimary;\r\n\r\n &:hover {\r\n background: $cPrimary;\r\n color: $cWhite;\r\n }\r\n }\r\n\r\n &.btn-icon-delete {\r\n background: #FFF5F5;\r\n color: $cDanger;\r\n\r\n &:hover {\r\n background: $cDanger;\r\n color: $cWhite;\r\n }\r\n }\r\n }\r\n\r\n .empty-state {\r\n text-align: center;\r\n padding: 50px 20px !important;\r\n color: #A0AEC0;\r\n\r\n i {\r\n font-size: 40px;\r\n margin-bottom: 12px;\r\n display: block;\r\n }\r\n\r\n p {\r\n margin: 0;\r\n font-size: 15px;\r\n }\r\n }\r\n}\r\n\r\n.btn-secondary {\r\n background: #E2E8F0;\r\n color: $cTextDark;\r\n border: none;\r\n padding: 8px 18px;\r\n border-radius: 6px;\r\n font-size: 14px;\r\n cursor: pointer;\r\n transition: background 0.2s;\r\n\r\n &:hover {\r\n background: #CBD5E0;\r\n }\r\n}\r\n\r\n// ===========================\r\n// CAMPAIGNS PAGE\r\n// ===========================\r\n.campaigns-page {\r\n .campaigns-header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 20px;\r\n\r\n h2 {\r\n margin: 0;\r\n font-size: 20px;\r\n font-weight: 600;\r\n color: $cTextDark;\r\n\r\n i {\r\n color: $cPrimary;\r\n margin-right: 8px;\r\n }\r\n }\r\n }\r\n\r\n .campaigns-filters {\r\n display: flex;\r\n gap: 20px;\r\n margin-bottom: 20px;\r\n\r\n .filter-group {\r\n flex: 1;\r\n\r\n label {\r\n display: block;\r\n font-size: 12px;\r\n font-weight: 700;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n color: #8899A6;\r\n margin-bottom: 6px;\r\n\r\n i {\r\n margin-right: 4px;\r\n }\r\n }\r\n\r\n .form-control {\r\n width: 100%;\r\n padding: 10px 14px;\r\n border: 1px solid $cBorder;\r\n border-radius: 8px;\r\n font-size: 14px;\r\n color: $cTextDark;\r\n background: $cWhite;\r\n transition: border-color 0.2s;\r\n appearance: none;\r\n -webkit-appearance: none;\r\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E\");\r\n background-repeat: no-repeat;\r\n background-position: right 12px center;\r\n padding-right: 32px;\r\n\r\n &:focus {\r\n outline: none;\r\n border-color: $cPrimary;\r\n box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);\r\n }\r\n }\r\n\r\n .filter-with-action {\r\n display: flex;\r\n gap: 8px;\r\n\r\n .form-control {\r\n flex: 1;\r\n }\r\n\r\n .btn-icon {\r\n flex-shrink: 0;\r\n width: 42px;\r\n height: 42px;\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n border-radius: 8px;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 14px;\r\n transition: all 0.2s;\r\n\r\n &.btn-icon-delete {\r\n background: #FFF5F5;\r\n color: $cDanger;\r\n\r\n &:hover {\r\n background: $cDanger;\r\n color: $cWhite;\r\n }\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n .campaigns-chart-wrap {\r\n background: $cWhite;\r\n border-radius: 10px;\r\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\r\n padding: 20px;\r\n margin-bottom: 20px;\r\n min-height: 350px;\r\n }\r\n\r\n .campaigns-table-wrap {\r\n background: $cWhite;\r\n border-radius: 10px;\r\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);\r\n overflow: hidden;\r\n\r\n .table {\r\n margin: 0;\r\n width: 100% !important;\r\n\r\n thead th {\r\n background: #F8FAFC;\r\n border-bottom: 2px solid $cBorder;\r\n font-size: 12px;\r\n font-weight: 700;\r\n text-transform: uppercase;\r\n letter-spacing: 0.5px;\r\n color: #8899A6;\r\n padding: 12px 16px;\r\n white-space: nowrap;\r\n }\r\n\r\n tbody td {\r\n padding: 10px 16px;\r\n vertical-align: middle;\r\n border-bottom: 1px solid #F1F5F9;\r\n font-size: 13px;\r\n }\r\n\r\n tbody tr:hover {\r\n background: #F8FAFC;\r\n }\r\n }\r\n\r\n // DataTables 2.x overrides\r\n .dt-layout-row {\r\n padding: 14px 20px;\r\n margin: 0 !important;\r\n border-top: 1px solid #F1F5F9;\r\n\r\n // Ukryj wiersz z search/length jeśli pusty\r\n &:first-child {\r\n display: none;\r\n }\r\n }\r\n\r\n .dt-info {\r\n font-size: 13px;\r\n color: #8899A6;\r\n }\r\n\r\n .dt-paging {\r\n .pagination {\r\n margin: 0;\r\n padding: 0;\r\n list-style: none;\r\n display: flex;\r\n align-items: center;\r\n gap: 6px;\r\n\r\n .page-item {\r\n .page-link {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n min-width: 36px;\r\n width: fit-content;\r\n height: 36px;\r\n padding: 0 14px;\r\n border-radius: 8px;\r\n font-size: 13px;\r\n font-weight: 500;\r\n border: 1px solid $cBorder;\r\n background: $cWhite;\r\n color: $cText;\r\n cursor: pointer;\r\n transition: all 0.2s;\r\n text-decoration: none;\r\n line-height: 1;\r\n white-space: nowrap;\r\n\r\n &:hover {\r\n background: #EEF2FF;\r\n color: $cPrimary;\r\n border-color: $cPrimary;\r\n }\r\n }\r\n\r\n &.active .page-link {\r\n background: $cPrimary;\r\n color: $cWhite;\r\n border-color: $cPrimary;\r\n font-weight: 600;\r\n }\r\n\r\n &.disabled .page-link {\r\n opacity: 0.35;\r\n cursor: default;\r\n pointer-events: none;\r\n }\r\n }\r\n }\r\n }\r\n\r\n .dt-processing {\r\n background: rgba($cWhite, 0.9);\r\n color: $cText;\r\n font-size: 14px;\r\n }\r\n }\r\n\r\n .delete-history-entry {\r\n display: inline-flex;\r\n align-items: center;\r\n justify-content: center;\r\n width: 30px;\r\n height: 30px;\r\n border-radius: 6px;\r\n border: none;\r\n cursor: pointer;\r\n font-size: 12px;\r\n background: #FFF5F5;\r\n color: $cDanger;\r\n transition: all 0.2s;\r\n\r\n &:hover {\r\n background: $cDanger;\r\n color: $cWhite;\r\n }\r\n }\r\n}\r\n\r\n// --- Form container ---\r\n.form_container {\r\n background: $cWhite;\r\n padding: 25px;\r\n max-width: 1300px;\r\n border-radius: 8px;\r\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);\r\n\r\n &.full {\r\n max-width: 100%;\r\n }\r\n\r\n .form_group {\r\n margin-bottom: 12px;\r\n display: flex;\r\n\r\n >.label {\r\n width: 300px;\r\n display: inline-flex;\r\n align-items: flex-start;\r\n justify-content: right;\r\n padding-right: 12px;\r\n }\r\n\r\n .input {\r\n width: calc(100% - 300px);\r\n }\r\n }\r\n}\r\n\r\n// --- Default popup ---\r\n.default_popup {\r\n position: fixed;\r\n top: 0;\r\n left: 0;\r\n width: 100%;\r\n height: 100%;\r\n background: rgba(0, 0, 0, 0.45);\r\n display: none;\r\n z-index: 2000;\r\n\r\n .popup_content {\r\n position: absolute;\r\n top: 50%;\r\n left: 50%;\r\n transform: translate(-50%, -50%);\r\n background: $cWhite;\r\n padding: 25px;\r\n border-radius: 10px;\r\n max-width: 1140px;\r\n width: 95%;\r\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);\r\n\r\n .popup_header {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n margin-bottom: 15px;\r\n\r\n .title {\r\n font-size: 18px;\r\n font-weight: 600;\r\n }\r\n }\r\n\r\n .close {\r\n cursor: pointer;\r\n color: #A0AEC0;\r\n font-size: 18px;\r\n padding: 4px;\r\n\r\n &:hover {\r\n color: $cDanger;\r\n }\r\n }\r\n }\r\n}\r\n\r\n// --- DataTables override ---\r\n.dt-layout-table {\r\n margin-bottom: 20px;\r\n}\r\n\r\n.pagination {\r\n button {\r\n border: 1px solid $cBorder;\r\n background: $cWhite;\r\n display: inline-flex;\r\n height: 32px;\r\n width: 32px;\r\n align-items: center;\r\n justify-content: center;\r\n margin: 0 2px;\r\n border-radius: 4px;\r\n transition: all 0.2s;\r\n cursor: pointer;\r\n\r\n &:hover {\r\n background: $cContentBg;\r\n border-color: $cPrimary;\r\n }\r\n }\r\n}\r\n\r\n// ===========================\r\n// PRODUCTS specific\r\n// ===========================\r\ntable#products {\r\n .table-product-title {\r\n display: flex;\r\n justify-content: space-between;\r\n }\r\n\r\n .edit-product-title {\r\n display: flex;\r\n height: 25px;\r\n align-items: center;\r\n justify-content: center;\r\n width: 25px;\r\n cursor: pointer;\r\n background: $cWhite;\r\n border: 1px solid #CBD5E0;\r\n color: #CBD5E0;\r\n border-radius: 4px;\r\n\r\n &:hover {\r\n background: #CBD5E0;\r\n color: $cWhite;\r\n }\r\n }\r\n\r\n a.custom_name {\r\n color: $cGreenLight !important;\r\n }\r\n}\r\n\r\n// --- Chart with form ---\r\n.chart-with-form {\r\n display: flex;\r\n gap: 20px;\r\n align-items: flex-start;\r\n}\r\n\r\n.chart-area {\r\n flex: 1 1 auto;\r\n min-width: 0;\r\n}\r\n\r\n.comment-form {\r\n width: 360px;\r\n flex: 0 0 360px;\r\n\r\n .form-group {\r\n margin-bottom: 12px;\r\n }\r\n\r\n label {\r\n display: block;\r\n font-weight: 600;\r\n margin-bottom: 6px;\r\n font-size: 13px;\r\n }\r\n\r\n input[type=\"date\"],\r\n textarea {\r\n width: 100%;\r\n border: 1px solid $cBorder;\r\n border-radius: 6px;\r\n padding: 8px 12px;\r\n font-size: 14px;\r\n font-family: \"Open Sans\", sans-serif;\r\n }\r\n\r\n textarea {\r\n min-height: 120px;\r\n resize: vertical;\r\n }\r\n\r\n .btn {\r\n padding: 8px 16px;\r\n }\r\n\r\n .btn[disabled] {\r\n opacity: 0.6;\r\n cursor: not-allowed;\r\n }\r\n\r\n .hint {\r\n font-size: 12px;\r\n color: #718096;\r\n }\r\n}\r\n\r\n// --- Select2 w modalu ---\r\n.jconfirm-box .form-group .select2-container {\r\n width: 100% !important;\r\n margin-top: 8px;\r\n}\r\n\r\n.jconfirm-box .select2-container--default .select2-selection--single {\r\n background-color: $cWhite;\r\n border: 1px solid $cBorder;\r\n border-radius: 6px;\r\n min-height: 42px;\r\n display: flex;\r\n align-items: center;\r\n padding: 4px 12px;\r\n box-shadow: none;\r\n transition: border-color 0.2s, box-shadow 0.2s;\r\n font-size: 14px;\r\n}\r\n\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered {\r\n padding-left: 0;\r\n line-height: 1.4;\r\n color: #495057;\r\n}\r\n\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder {\r\n color: #CBD5E0;\r\n}\r\n\r\n.jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow {\r\n height: 100%;\r\n right: 8px;\r\n}\r\n\r\n.jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single,\r\n.jconfirm-box .select2-container--default .select2-selection--single:hover {\r\n border-color: $cPrimary;\r\n box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);\r\n outline: 0;\r\n}\r\n\r\n.jconfirm-box .select2-container .select2-dropdown {\r\n border-color: $cBorder;\r\n border-radius: 0 0 6px 6px;\r\n font-size: 14px;\r\n}\r\n\r\n.jconfirm-box .select2-container .select2-search--dropdown .select2-search__field {\r\n padding: 6px 10px;\r\n border-radius: 4px;\r\n border: 1px solid $cBorder;\r\n font-size: 14px;\r\n}\r\n\r\n.jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] {\r\n background-color: $cPrimary;\r\n color: $cWhite;\r\n}\r\n\r\n// ===========================\r\n// RESPONSYWNOŚĆ\r\n// ===========================\r\n@media (max-width: 992px) {\r\n .sidebar {\r\n transform: translateX(-100%);\r\n\r\n &.mobile-open {\r\n transform: translateX(0);\r\n }\r\n }\r\n\r\n .main-wrapper {\r\n margin-left: 0 !important;\r\n }\r\n}"]} \ No newline at end of file diff --git a/layout/style.scss b/layout/style.scss index eddb6a8..a535e95 100644 --- a/layout/style.scss +++ b/layout/style.scss @@ -1,80 +1,31 @@ -$cBlue: #6690F4; -$cRed: #aa0505; -$cGreen: #43833f; +@use "sass:color"; +// === adsPRO - Nowe style === + +// --- Zmienne --- +$cPrimary: #6690F4; +$cPrimaryDark: #3164db; +$cSidebarBg: #1E2A3A; +$cSidebarText: #A8B7C7; +$cSidebarHover: #263548; +$cSidebarActive: $cPrimary; +$cContentBg: #F4F6F9; +$cWhite: #FFFFFF; +$cText: #4E5E6A; +$cTextDark: #2D3748; +$cBorder: #E2E8F0; +$cSuccess: #57B951; +$cSuccessDark: #4a9c3b; +$cDanger: #CC0000; +$cDangerDark: #b30000; +$cWarning: #FF8C00; $cGreenLight: #57b951; -$cBlack: #4e5e6a; -.animate { - animation: mymove 3s infinite; -} - -.text-right { - text-align: right; -} - -.text-bold { - font-weight: 900 !important; -} - -.nowrap { - white-space: nowrap; -} - -table { - border-collapse: collapse; -} - -small { - font-size: .75em; -} - -table { - font-size: 13px; -} - -@keyframes mymove { - 50% { - opacity: .33; - } -} - -/* motion */ -@keyframes gradient-animation { - 0% { - background-position: 15% 0%; - } - - 50% { - background-position: 85% 100%; - } - - 100% { - background-position: 15% 0%; - } -} - -@keyframes frame-enter { - 0% { - clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), 3px calc(100% - 3px), 3px 100%, 100% 100%, 100% 0%, 0% 0%); - } - - 25% { - clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) calc(100% - 3px), calc(100% - 3px) 100%, 100% 100%, 100% 0%, 0% 0%); - } - - 50% { - clip-path: polygon(0% 100%, 3px 100%, 3px 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, calc(100% - 3px) 3px, 100% 0%, 0% 0%); - } - - 75% { - -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 3px, 3px 0%, 0% 0%); - } - - 100% { - -webkit-clip-path: polygon(0% 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 3px 100%, 0% 100%); - } -} +$sidebarWidth: 260px; +$sidebarCollapsed: 70px; +$topbarHeight: 56px; +$transitionSpeed: 0.3s; +// --- Reset i baza --- * { box-sizing: border-box; } @@ -83,799 +34,657 @@ body { font-family: "Open Sans", sans-serif; margin: 0; padding: 0; - font-size: 15px; - color: #4e5e6a; -} - -.btn { - padding: 12px 25px; - transition: all 0.3s ease; - color: #FFF; - border: 0; - border-radius: 6px; - cursor: pointer; - display: inline-flex; - text-decoration: none; - gap: 5px; - justify-content: center; - align-items: center; - - &.btn_small, - &.btn-xs { - padding: 5px 7px; - font-size: 13px; - - i { - font-size: 12px; - } - } - - &.btn-success { - background: #57b951; - - &:hover { - background: #4a9c3b; - } - } - - &.btn-primary { - background: $cBlue; - - &:hover { - background: #3164db; - } - } - - &.btn-danger { - background: #cc0000; - - &:hover { - background: #b30000; - } - } + font-size: 14px; + color: $cText; + background: $cContentBg; } .hide { display: none; } -.form-error { - color: #cc0000; - font-size: 13px; +// --- Typografia --- +small { + font-size: .75em; } -.input-group { - margin-bottom: 10px; +.text-right { + text-align: right; } -input[type="checkbox"] { - border: 1px solid #eee; +.text-bold { + font-weight: 700 !important; } -.form-control { - border: 1px solid #eee; - border-radius: 3px; - height: 35px; +.nowrap { + white-space: nowrap; +} + +// =========================== +// LOGIN PAGE (unlogged) +// =========================== +body.unlogged { + background: $cContentBg; + margin: 0; + padding: 0; +} + +.login-container { + display: flex; + min-height: 100vh; +} + +.login-brand { + flex: 0 0 45%; + background: linear-gradient(135deg, $cSidebarBg 0%, #2C3E57 50%, $cPrimary 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 60px; + position: relative; + overflow: hidden; + + &::before { + content: ''; + position: absolute; + top: -50%; + right: -50%; + width: 100%; + height: 100%; + background: radial-gradient(circle, rgba($cPrimary, 0.15) 0%, transparent 70%); + border-radius: 50%; + } + + .brand-content { + position: relative; + z-index: 1; + color: $cWhite; + max-width: 400px; + } + + .brand-logo { + font-size: 48px; + font-weight: 300; + margin-bottom: 20px; + letter-spacing: -1px; + + strong { + font-weight: 700; + } + } + + .brand-tagline { + font-size: 18px; + opacity: 0.85; + line-height: 1.6; + margin-bottom: 50px; + } + + .brand-features { + .feature { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 20px; + opacity: 0.8; + + i { + font-size: 20px; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: rgba($cWhite, 0.1); + border-radius: 10px; + } + + span { + font-size: 15px; + } + } + } +} + +.login-form-wrapper { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 60px; + background: $cWhite; +} + +.login-box { width: 100%; - padding: 5px; + max-width: 420px; + + .login-header { + margin-bottom: 35px; + + h1 { + font-size: 28px; + font-weight: 700; + color: $cTextDark; + margin: 0 0 8px; + } + + p { + color: #718096; + font-size: 15px; + margin: 0; + } + } + + .form-group { + margin-bottom: 20px; + + label { + display: block; + font-size: 13px; + font-weight: 600; + color: $cTextDark; + margin-bottom: 6px; + } + } + + .input-with-icon { + position: relative; + + i { + position: absolute; + left: 14px; + top: 50%; + transform: translateY(-50%); + color: #A0AEC0; + font-size: 14px; + } + + .form-control { + padding-left: 42px; + } + } + + .form-control { + width: 100%; + height: 46px; + border: 2px solid $cBorder; + border-radius: 8px; + padding: 0 14px; + font-size: 14px; + font-family: "Open Sans", sans-serif; + color: $cTextDark; + transition: border-color $transitionSpeed, box-shadow $transitionSpeed; + + &::placeholder { + color: #CBD5E0; + } + + &:focus { + border-color: $cPrimary; + box-shadow: 0 0 0 3px rgba($cPrimary, 0.15); + outline: none; + } + } + + .form-error { + color: $cDanger; + font-size: 12px; + margin-top: 4px; + } + + .checkbox-group { + .checkbox-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-size: 13px; + color: #718096; + font-weight: 400; + + input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: $cPrimary; + } + } + } + + .btn-login { + width: 100%; + height: 48px; + font-size: 15px; + font-weight: 600; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + + &.disabled { + opacity: 0.7; + pointer-events: none; + } + } + + .alert { + display: none; + padding: 12px 16px; + border-radius: 8px; + font-size: 13px; + margin-bottom: 20px; + + &.alert-danger { + background: #FFF5F5; + color: $cDanger; + border: 1px solid #FED7D7; + } + + &.alert-success { + background: #F0FFF4; + color: #276749; + border: 1px solid #C6F6D5; + } + } +} + +// Responsywność logowania +@media (max-width: 768px) { + .login-brand { + display: none; + } + + .login-form-wrapper { + padding: 30px 20px; + } +} + +// =========================== +// LAYOUT (logged) - SIDEBAR +// =========================== +body.logged { + display: flex; + min-height: 100vh; + background: $cContentBg; +} + +// --- Sidebar --- +.sidebar { + width: $sidebarWidth; + min-height: 100vh; + background: $cSidebarBg; + position: fixed; + top: 0; + left: 0; + z-index: 1000; + display: flex; + flex-direction: column; + transition: width $transitionSpeed ease; + overflow: hidden; + + &.collapsed { + width: $sidebarCollapsed; + + .sidebar-header { + padding: 16px 0; + justify-content: center; + + .sidebar-logo { + display: none; + } + + .sidebar-toggle i { + transform: rotate(180deg); + } + } + + .sidebar-nav ul li a { + padding: 12px 0; + justify-content: center; + + span { + display: none; + } + + i { + margin-right: 0; + font-size: 18px; + } + } + + .sidebar-footer { + .sidebar-user { + justify-content: center; + + .user-info { + display: none; + } + } + + .sidebar-logout { + justify-content: center; + + span { + display: none; + } + } + } + + .nav-divider { + margin: 8px 15px; + } + } +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 20px 20px 16px; + border-bottom: 1px solid rgba($cWhite, 0.08); + + .sidebar-logo a { + color: $cWhite; + text-decoration: none; + font-size: 24px; + font-weight: 300; + letter-spacing: -0.5px; + + strong { + font-weight: 700; + } + } + + .sidebar-toggle { + background: none; + border: none; + color: $cSidebarText; + cursor: pointer; + padding: 6px; + border-radius: 6px; + transition: all $transitionSpeed; + + &:hover { + background: rgba($cWhite, 0.08); + color: $cWhite; + } + + i { + transition: transform $transitionSpeed; + } + } +} + +.sidebar-nav { + flex: 1; + padding: 12px 0; + overflow-y: auto; + + ul { + list-style: none; + margin: 0; + padding: 0; + + li { + &.nav-divider { + height: 1px; + background: rgba($cWhite, 0.08); + margin: 8px 20px; + } + + a { + display: flex; + align-items: center; + padding: 11px 20px; + color: $cSidebarText; + text-decoration: none; + font-size: 14px; + transition: all 0.2s; + border-left: 3px solid transparent; + + i { + width: 20px; + text-align: center; + margin-right: 12px; + font-size: 15px; + } + + &:hover { + background: $cSidebarHover; + color: $cWhite; + } + } + + &.active>a { + background: rgba($cPrimary, 0.15); + color: $cWhite; + border-left-color: $cPrimary; + + i { + color: $cPrimary; + } + } + } + } +} + +.sidebar-footer { + padding: 16px 20px; + border-top: 1px solid rgba($cWhite, 0.08); + + .sidebar-user { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; + + .user-avatar { + width: 34px; + height: 34px; + border-radius: 50%; + background: rgba($cPrimary, 0.2); + display: flex; + align-items: center; + justify-content: center; + color: $cPrimary; + font-size: 14px; + flex-shrink: 0; + } + + .user-info { + overflow: hidden; + + .user-email { + color: $cSidebarText; + font-size: 12px; + display: block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + + .sidebar-logout { + display: flex; + align-items: center; + gap: 8px; + color: #E53E3E; + text-decoration: none; + font-size: 13px; + padding: 8px 10px; + border-radius: 6px; + transition: all 0.2s; + + i { + font-size: 14px; + } + + &:hover { + background: rgba(#E53E3E, 0.1); + } + } +} + +// --- Main wrapper --- +.main-wrapper { + margin-left: $sidebarWidth; + flex: 1; + min-height: 100vh; + transition: margin-left $transitionSpeed ease; + display: flex; + flex-direction: column; + + &.expanded { + margin-left: $sidebarCollapsed; + } +} + +// --- Topbar --- +.topbar { + height: $topbarHeight; + background: $cWhite; + border-bottom: 1px solid $cBorder; + display: flex; + align-items: center; + padding: 0 25px; + position: sticky; + top: 0; + z-index: 500; + + .topbar-toggle { + background: none; + border: none; + color: $cText; + cursor: pointer; + padding: 8px 10px; + border-radius: 6px; + font-size: 16px; + margin-right: 15px; + transition: all 0.2s; + + &:hover { + background: $cContentBg; + } + } + + .topbar-breadcrumb { + font-size: 16px; + font-weight: 600; + color: $cTextDark; + } +} + +// --- Content area --- +.content { + flex: 1; + padding: 25px; +} + +.app-alert { + background: #EBF8FF; + border: 1px solid #BEE3F8; + color: #2B6CB0; + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 20px; + font-size: 14px; +} + +// =========================== +// KOMPONENTY WSPÓLNE +// =========================== + +// --- Buttons --- +.btn { + padding: 10px 20px; + transition: all 0.2s ease; + color: $cWhite; + border: 0; + border-radius: 6px; + cursor: pointer; + display: inline-flex; + text-decoration: none; + gap: 6px; + justify-content: center; + align-items: center; + font-size: 14px; font-family: "Open Sans", sans-serif; + font-weight: 500; + + &.btn_small, + &.btn-xs, + &.btn-sm { + padding: 5px 10px; + font-size: 12px; + + i { + font-size: 11px; + } + } + + &.btn-success { + background: $cSuccess; + + &:hover { + background: $cSuccessDark; + } + } + + &.btn-primary { + background: $cPrimary; + + &:hover { + background: $cPrimaryDark; + } + } + + &.btn-danger { + background: $cDanger; + + &:hover { + background: $cDangerDark; + } + } + + &.disabled { + opacity: 0.6; + pointer-events: none; + } +} + +// --- Form controls --- +.form-control { + border: 1px solid $cBorder; + border-radius: 6px; + height: 38px; + width: 100%; + padding: 6px 12px; + font-family: "Open Sans", sans-serif; + font-size: 14px; + color: $cTextDark; + transition: border-color 0.2s, box-shadow 0.2s; option { padding: 5px; } &:focus { - border: 1px solid $cBlue; + border-color: $cPrimary; + box-shadow: 0 0 0 3px rgba($cPrimary, 0.1); outline: none; } } -.unlogged { - background: #EEF1F9; - display: flex; - align-items: center; - justify-content: center; - height: 100vh; - - .box-login { - background: #FFF; - padding: 25px; - border-radius: 6px; - width: 400px; - - .title { - text-align: center; - padding: 10px 10px 25px; - border-bottom: 1px solid #eee; - font-size: 20px; - margin-bottom: 25px; - } - } +input[type="checkbox"] { + border: 1px solid $cBorder; } -body>.top { - background: #FFF; - border-bottom: 1px solid #eee; - display: flex; - justify-content: space-between; - align-items: center; - - .logo { - a { - display: inline-flex; - color: $cBlue; - padding: 10px; - text-decoration: none; - - span { - font-weight: 600; - } - } - } - - .user-nav { - position: relative; - font-size: 14px; - padding: 10px; - - .trigger { - cursor: pointer; - - i { - color: $cBlue; - } - } - - ul { - position: absolute; - top: 100%; - left: 0; - background: #FFF; - margin: 0; - padding: 0; - width: 100%; - list-style-type: none; - border: 1px solid #eee; - display: none; - - li { - cursor: pointer; - - &:hover { - background: #F8F9FA; - } - - a { - color: #333; - display: block; - text-decoration: none; - padding: 10px; - } - } - } - - &:hover { - ul { - display: block; - } - } - } -} - -.main-menu { - display: flex; - box-shadow: 0 .125rem .25rem rgba(0, 0, 0, .075) !important; - - ul { - display: flex; - list-style-type: none; - margin: 0; - padding: 0; - font-size: 14px; - - li { - cursor: pointer; - - a { - color: #333; - text-decoration: none; - display: inline-flex; - padding: 10px 15px; - } - - &:hover { - a { - background: $cBlue; - color: #FFF; - } - } - - ul { - display: none; - } - } - } -} - -.main { - padding: 25px; - background: #D9DEE2; - min-height: calc(100vh - 80px); -} - -.tasks_container { - display: flex; - flex-wrap: wrap; - gap: 20px; - - .column { - width: 350px; - - h2 { - display: flex; - padding: 10px; - background: #FFF; - margin-bottom: 10px; - font-size: 15px; - font-weight: 300; - border-radius: 3px 3px 0 0; - justify-content: space-between; - align-items: center; - - i { - cursor: pointer; - } - } - - &.tasks_suspended { - h2 { - border-bottom: 5px solid #cc0000; - } - } - - &.tasks_new { - h2 { - border-bottom: 5px solid #ccc; - } - } - - &.tasks_bulk { - h2 { - border-bottom: 5px solid #ff8c00; - } - } - - &.tasks_to_do { - h2 { - border-bottom: 5px solid #2aaf47; - } - } - - &.tasks_to_review { - h2 { - border-bottom: 5px solid #2535c9; - } - } - - &.tasks_closed { - h2 { - border-bottom: 5px solid #000; - } - } - - ul { - list-style-type: none; - margin: 0; - padding: 0; - - .task { - margin-bottom: 5px; - background: #FFF; - padding: 10px; - border-radius: 0; - display: flex; - position: relative; - - &.notopened { - border: 2px solid #cc0000; - } - - .left { - width: 30px; - - .users { - display: flex; - gap: 5px; - flex-wrap: wrap; - - .user { - display: flex; - width: 20px; - height: 20px; - border-radius: 50%; - justify-content: center; - align-items: center; - color: #FFF; - font-size: 13px; - } - } - } - - .middle { - width: calc(100% - 60px); - } - - .right { - width: 30px; - display: flex; - flex-wrap: wrap; - justify-content: flex-end; - - .recursively { - color: #ccc; - border-radius: 3px; - cursor: pointer; - margin-bottom: 5px; - width: 22px; - height: 22px; - text-align: center; - - i { - font-size: 15px; - } - } - - .task_start { - background: #57b951; - color: #FFF; - border-radius: 3px; - cursor: pointer; - margin-bottom: 5px; - width: 22px; - height: 22px; - text-align: center; - - &.hidden { - display: none; - } - - i { - font-size: 12px; - } - } - - .task_end { - background: #cc0000; - color: #FFF; - border-radius: 3px; - cursor: pointer; - margin-bottom: 5px; - width: 22px; - height: 22px; - text-align: center; - - &.hidden { - display: none; - } - - i { - font-size: 12px; - } - } - } - - .name { - font-size: 14px; - color: #333; - text-decoration: none; - display: block; - margin-bottom: 5px; - } - - .bottom { - display: flex; - justify-content: space-between; - } - - .client_info, - .current_status { - font-size: 12px; - font-weight: 400; - - strong { - font-weight: 600; - } - } - - .current_status { - position: relative; - cursor: pointer; - - .status_change { - position: absolute; - left: 0; - top: 20px; - background: #fff; - padding: 15px; - border: 1px solid #dfdfdf; - border-radius: 3px; - cursor: pointer; - box-shadow: 0 0 15px rgba(0, 0, 0, 0.1); - z-index: 99; - display: none; - - select { - width: 250px; - padding: 10px; - border: 1px solid #eee; - border-radius: 3px; - - option { - font-size: 15px; - padding: 3px; - } - } - } - } - - .dates { - margin-bottom: 5px; - display: flex; - justify-content: space-between; - font-size: 12px; - - .danger { - color: #cc0000; - font-weight: 600; - } - - .warning { - color: #ff8c00; - } - - i { - font-size: 12px; - color: #C9CED4; - margin-right: 5px; - } - } - } - } - } -} - -.action_menu { - display: flex; - margin-bottom: 25px; - gap: 20px; - - .btn { - display: inline-flex; - padding: 7px 15px; - color: #FFF; - border-radius: 3px; - text-decoration: none; - align-items: center; - justify-content: center; - gap: 5px; - - &.btn_add { - background: #57b951; - - &:hover { - background: #4a9c3b; - } - } - - &.btn_cancel { - background: #cc0000; - - &:hover { - background: #b30000; - } - } - - &.disabled { - opacity: .5; - } - } -} - -.form_container { - background: #FFF; - padding: 25px; - max-width: 1300px; - - &.full { - max-width: 100%; - } - - .form_group { - margin-bottom: 10px; - display: flex; - - >.label { - width: 300px; - display: inline-flex; - align-items: flex-start; - justify-content: right; - padding-right: 10px; - } - - .input { - width: calc(100% - 300px); - } - } -} - -.task_popup { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: none; - - .task_details { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: #FFF; - padding: 25px; - border-radius: 6px; - max-width: 1140px; - width: 100%; - - .title { - font-size: 20px; - margin-bottom: 25px; - - a { - color: #333; - text-decoration: none; - margin-right: 10px; - - &.task-delete { - color: #cc0000; - } - } - } - - .close { - position: absolute; - top: 10px; - right: 10px; - cursor: pointer; - } - - .content { - display: flex; - font-size: 14px; - - h3 { - width: 100%; - margin-top: 0; - margin-bottom: 5px; - font-weight: 500; - color: #000; - font-size: 17px; - } - - .left { - width: 70%; - max-height: 700px; - overflow-y: auto; - - .users { - display: flex; - gap: 20px; - - .user { - display: flex; - gap: 10px; - align-items: center; - margin-bottom: 10px; - - .avatar { - height: 30px; - width: 30px; - border-radius: 50%; - background: #ccc; - display: flex; - justify-content: center; - align-items: center; - color: #FFF; - } - } - } - - .comments { - border-radius: 3px; - padding: 0 15px 15px 0; - margin-bottom: 15px; - border-bottom: 1px solid #eee; - - .new_comment { - margin-bottom: 15px; - - textarea { - height: 75px; - } - - .add_comment { - background: #57b951; - color: #FFF; - padding: 10px; - border-radius: 6px; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - width: 200px; - text-decoration: none; - - &:hover { - background: #4a9c3b; - } - } - } - - ul { - margin: 0; - padding: 0; - list-style-type: none; - - li { - background: #eee; - margin-bottom: 5px; - padding: 15px; - border-radius: 6px; - position: relative; - - .delete_comment { - position: absolute; - top: 10px; - right: 10px; - cursor: pointer; - color: #cc0000; - } - - .author { - font-weight: 600; - margin-bottom: 5px; - display: inline-flex; - margin-right: 10px; - } - - .date { - font-size: 12px; - margin-bottom: 5px; - display: inline-flex; - } - - .text { - margin-bottom: 15px; - font-size: 13px; - } - } - } - } - - .checklist { - border-radius: 3px; - padding: 0 15px 15px 0; - margin-bottom: 15px; - border-bottom: 1px solid #eee; - - .new_element { - display: flex; - margin-bottom: 15px; - - a { - display: flex; - align-items: center; - justify-content: center; - padding: 10px; - border-radius: 0 6px 6px 0; - text-decoration: none; - width: 35px; - background: #57b951; - color: #FFF; - } - } - - ul { - margin: 0; - padding: 0; - list-style-type: none; - - li { - display: flex; - gap: 10px; - margin-bottom: 5px; - background: #FFF; - border-radius: 3px; - padding: 5px; - border: 1px solid #eee; - font-size: 13px; - align-items: center; - - i { - margin-left: auto; - margin-right: 0; - cursor: pointer; - color: #cc0000; - } - } - } - } - - .description { - padding: 15px; - border-radius: 3px; - background: #F6F8F9; - margin-bottom: 15px; - } - } - - .right { - width: 30%; - padding: 0 15px 15px; - - .box { - margin-bottom: 15px; - } - - .time { - a { - display: block; - padding: 10px; - border-radius: 6px; - margin-top: 10px; - text-decoration: none; - text-align: center; - width: 100%; - - &.task_start { - background: #57b951; - color: #FFF; - } - - &.task_end { - background: #cc0000; - color: #FFF; - } - - &.hidden { - display: none; - } - } - } - - .dates { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - - .danger { - color: #cc0000; - font-weight: 600; - } - - .warning { - color: #ff8c00; - } - - i { - color: #C9CED4; - margin-right: 5px; - } - } - } - } - } +// --- Tables --- +table { + border-collapse: collapse; + font-size: 13px; } .table { @@ -883,8 +692,17 @@ body>.top { th, td { - border: 1px solid #eee; - padding: 5px; + border: 1px solid $cBorder; + padding: 8px 10px; + } + + th { + background: #F7FAFC; + font-weight: 600; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.03em; + color: #718096; } td.center { @@ -895,124 +713,42 @@ body>.top { text-align: left; } - &.table-sm { - - td { - padding: 5px !important; - } + &.table-sm td { + padding: 5px !important; } input.form-control { font-size: 13px; + height: 32px; } } -.projects_container { - background: #FFF; - padding: 15px; - border-radius: 6px; - display: flex; - gap: 20px; - - .left { - display: flex; - gap: 20px; - flex-wrap: wrap; - width: calc(100% - 250px); - } - - .right { - width: 250px; - display: flex; - flex-wrap: wrap; - align-items: flex-start; - justify-content: center; - gap: 20px; - border-left: 1px solid #eee; - padding-left: 15px; - - select { - width: 200px; - } - } -} - -.default_popup { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.5); - display: none; - - .popup_content { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background: #FFF; - padding: 25px; - border-radius: 6px; - max-width: 1140px; - width: 100%; - - .close { - position: absolute; - top: 10px; - right: 10px; - cursor: pointer; - } - } -} - -#fg-cron { - margin: 10px 0; - - .countdown { - background: #57b951; - color: #FFF; - padding: 10px; - } - - #cron-container { - max-height: 300px; - overflow-x: hidden; - overflow-y: auto; - - .msg { - font-size: 13px; - padding: 5px; - border-bottom: 1px solid #e8e8e8; - } - } -} - +// --- Cards --- .card { - background: #FFF; - padding: 25px; - border-radius: 6px; - color: #000; - font-size: 15px; - max-width: 1280px; + background: $cWhite; + padding: 20px; + border-radius: 8px; + color: $cTextDark; + font-size: 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); &.mb25 { - margin-bottom: 25px; + margin-bottom: 20px; } .card-header { font-weight: 600; + font-size: 15px; } .card-body { - padding-top: 10px; + padding-top: 12px; table { - border-collapse: collapse; th, td { - font-size: 14px; + font-size: 13px; &.bold { font-weight: 600; @@ -1025,256 +761,1038 @@ body>.top { &.text-center { text-align: center; } - - .close-task { - text-decoration: none; - color: $cBlue; - - &:hover { - color: #cc0000; - } - } } } } } -.finance-summary { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - gap: 10px; +// --- Action menu --- +.action_menu { + display: flex; + margin-bottom: 20px; + gap: 12px; - .panel { - background: #FFF; - border-radius: 6px; - padding: 15px; + .btn { + padding: 8px 16px; - h1 { - font-size: 20px; - margin: 0; + &.btn_add { + background: $cSuccess; + + &:hover { + background: $cSuccessDark; + } } - span { - font-size: 0.85em; + &.btn_cancel { + background: $cDanger; + + &:hover { + background: $cDangerDark; + } } } } -.finance-manager { - display: grid; - gap: 10px; - grid-template-columns: 200px 1fr 500px; - padding-top: 25px; +// --- Settings page --- +.settings-card { + background: $cWhite; + border-radius: 10px; + padding: 28px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); - .manage-menu { - display: inline-block; - margin-right: 10px; + .settings-card-header { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid $cBorder; - a { - color: #333333; - text-decoration: none; - font-weight: 300; - display: block; - } - } - - .actions { - width: 100px; - text-align: center; - - a { - display: inline-flex; - margin: 0 2px; - height: 25px; - width: 25px; + .settings-card-icon { + width: 44px; + height: 44px; + border-radius: 10px; + background: color.adjust($cPrimary, $lightness: 26%); + color: $cPrimary; + display: flex; align-items: center; justify-content: center; - text-decoration: none; - color: #000; - border: 1px solid #e7e7e7; - font-size: 11px; - border-radius: 3px; - transition: all 0.3s ease; - - &:hover { - border: 1px solid $cBlue; - } - } - } -} - -.bootstrap-tagsinput { - .tag { - background: $cBlue; - font-size: 13px; - padding: 5px 10px; - border-radius: 12px; - - [data-role="remove"] { - color: #FFF !important; - - &:hover { - box-shadow: none !important; - color: #000 !important - } - } - } -} - -.finance-tags { - display: flex; - flex-wrap: wrap; - gap: 5px; - - a:not(.btn) { - display: flex; - width: 100%; - text-decoration: none; - color: $cBlack; - - &.zoom-100 { - font-size: 130%; + font-size: 18px; + flex-shrink: 0; } - &.zoom-90 { - font-size: 120%; - } - - &.zoom-80 { - font-size: 110%; - } - - &.zoom-70 { - font-size: 100%; - } - - &.zoom-60 { - font-size: 95%; - } - - &.zoom-50 { - font-size: 85%; - } - - &.zoom-40 { - font-size: 80%; - } - - &.zoom-30 { - font-size: 75%; - } - - &.zoom-20 { - font-size: 75%; - } - - &.zoom-10 { - font-size: 70%; - } - - &.zoom-0 { - font-size: 70%; - } - } -} - -.manage-menu { - position: relative; - - .context-menu { - border-left: 4px dotted #000; - width: 1px; - height: 100%; - } - - .context-menu-container { - position: absolute; - display: none; - background: #FFF; - box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.1); - top: 2px; - left: 2px; - - ul { - list-style-type: none; + h3 { margin: 0; - padding: 0; + font-size: 17px; + font-weight: 600; + color: $cTextDark; + } - li { - a { - display: block; - padding: 7px 15px; - white-space: nowrap; + small { + color: #8899A6; + font-size: 13px; + } + } - &:hover { - background: #f8f8f8; + .settings-field { + margin-bottom: 18px; + + label { + display: block; + font-size: 13px; + font-weight: 600; + color: $cTextDark; + margin-bottom: 6px; + } + } + + .settings-input-wrap { + position: relative; + + .settings-input-icon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: #A0AEC0; + font-size: 14px; + pointer-events: none; + } + + .form-control { + padding-left: 38px; + } + + .settings-toggle-pw { + position: absolute; + right: 4px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: #A0AEC0; + cursor: pointer; + padding: 6px 10px; + font-size: 14px; + transition: color 0.2s; + + &:hover { + color: $cPrimary; + } + } + } + + .settings-fields-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 0 24px; + + @media (max-width: 768px) { + grid-template-columns: 1fr; + } + } + + .settings-alert-error { + display: flex; + align-items: center; + gap: 10px; + background: #FFF5F5; + color: $cDanger; + border: 1px solid #FED7D7; + border-radius: 8px; + padding: 12px 16px; + margin-bottom: 20px; + font-size: 13px; + + i { + font-size: 16px; + flex-shrink: 0; + } + } +} + +// --- Clients page --- +.clients-page { + .clients-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + + h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: $cTextDark; + + i { + color: $cPrimary; + margin-right: 8px; + } + } + } + + .clients-table-wrap { + background: $cWhite; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; + + .table { + margin: 0; + + thead th { + background: #F8FAFC; + border-bottom: 2px solid $cBorder; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + padding: 14px 20px; + } + + tbody td { + padding: 14px 20px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; + } + + tbody tr:hover { + background: #F8FAFC; + } + + .client-id { + color: #8899A6; + font-size: 13px; + font-weight: 600; + } + + .client-name { + font-weight: 600; + color: $cTextDark; + } + } + } + + .badge-id { + display: inline-block; + background: #EEF2FF; + color: $cPrimary; + font-size: 13px; + font-weight: 600; + padding: 4px 10px; + border-radius: 6px; + font-family: monospace; + } + + .actions-cell { + text-align: center; + white-space: nowrap; + } + + .btn-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 8px; + border: none; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; + margin: 0 2px; + + &.btn-icon-edit { + background: #EEF2FF; + color: $cPrimary; + + &:hover { + background: $cPrimary; + color: $cWhite; + } + } + + &.btn-icon-delete { + background: #FFF5F5; + color: $cDanger; + + &:hover { + background: $cDanger; + color: $cWhite; + } + } + } + + .empty-state { + text-align: center; + padding: 50px 20px !important; + color: #A0AEC0; + + i { + font-size: 40px; + margin-bottom: 12px; + display: block; + } + + p { + margin: 0; + font-size: 15px; + } + } +} + +.btn-secondary { + background: #E2E8F0; + color: $cTextDark; + border: none; + padding: 8px 18px; + border-radius: 6px; + font-size: 14px; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: #CBD5E0; + } +} + +// =========================== +// CAMPAIGNS PAGE +// =========================== +.campaigns-page { + .campaigns-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + + h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: $cTextDark; + + i { + color: $cPrimary; + margin-right: 8px; + } + } + } + + .campaigns-filters { + display: flex; + gap: 20px; + margin-bottom: 20px; + + .filter-group { + flex: 1; + + label { + display: block; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + margin-bottom: 6px; + + i { + margin-right: 4px; + } + } + + .form-control { + width: 100%; + padding: 10px 14px; + border: 1px solid $cBorder; + border-radius: 8px; + font-size: 14px; + color: $cTextDark; + background: $cWhite; + transition: border-color 0.2s; + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; + + &:focus { + outline: none; + border-color: $cPrimary; + box-shadow: 0 0 0 3px rgba($cPrimary, 0.1); + } + } + + .filter-with-action { + display: flex; + gap: 8px; + + .form-control { + flex: 1; + } + + .btn-icon { + flex-shrink: 0; + width: 42px; + height: 42px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + border: none; + cursor: pointer; + font-size: 14px; + transition: all 0.2s; + + &.btn-icon-delete { + background: #FFF5F5; + color: $cDanger; + + &:hover { + background: $cDanger; + color: $cWhite; + } } } } } } + .campaigns-chart-wrap { + background: $cWhite; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + padding: 20px; + margin-bottom: 20px; + min-height: 350px; + } + + .campaigns-table-wrap { + background: $cWhite; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; + + .table { + margin: 0; + width: 100% !important; + + thead th { + background: #F8FAFC; + border-bottom: 2px solid $cBorder; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + padding: 12px 16px; + white-space: nowrap; + } + + tbody td { + padding: 10px 16px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; + font-size: 13px; + } + + tbody tr:hover { + background: #F8FAFC; + } + } + + // DataTables 2.x overrides + .dt-layout-row { + padding: 14px 20px; + margin: 0 !important; + border-top: 1px solid #F1F5F9; + + // Ukryj wiersz z search/length jeśli pusty + &:first-child { + display: none; + } + } + + .dt-info { + font-size: 13px; + color: #8899A6; + } + + .dt-paging { + .pagination { + margin: 0; + padding: 0; + list-style: none; + display: flex; + align-items: center; + gap: 6px; + + .page-item { + .page-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + width: fit-content; + height: 36px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + border: 1px solid $cBorder; + background: $cWhite; + color: $cText; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + line-height: 1; + white-space: nowrap; + + &:hover { + background: #EEF2FF; + color: $cPrimary; + border-color: $cPrimary; + } + } + + &.active .page-link { + background: $cPrimary; + color: $cWhite; + border-color: $cPrimary; + font-weight: 600; + } + + &.disabled .page-link { + opacity: 0.35; + cursor: default; + pointer-events: none; + } + } + } + } + + .dt-processing { + background: rgba($cWhite, 0.9); + color: $cText; + font-size: 14px; + } + } + + .delete-history-entry { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #FFF5F5; + color: $cDanger; + transition: all 0.2s; + + &:hover { + background: $cDanger; + color: $cWhite; + } + } +} + +// =========================== +// PRODUCTS PAGE +// =========================== +.products-page { + .products-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + + h2 { + margin: 0; + font-size: 20px; + font-weight: 600; + color: $cTextDark; + + i { + color: $cPrimary; + margin-right: 8px; + } + } + } + + .products-filters { + display: flex; + gap: 20px; + margin-bottom: 16px; + + .filter-group { + label { + display: block; + font-size: 12px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #8899A6; + margin-bottom: 6px; + + i { margin-right: 4px; } + } + + .form-control { + width: 100%; + padding: 10px 14px; + border: 1px solid $cBorder; + border-radius: 8px; + font-size: 14px; + color: $cTextDark; + background: $cWhite; + transition: border-color 0.2s; + + &:focus { + outline: none; + border-color: $cPrimary; + box-shadow: 0 0 0 3px rgba($cPrimary, 0.1); + } + } + + select.form-control { + appearance: none; + -webkit-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + padding-right: 32px; + } + + &.filter-group-client { flex: 1; } + &.filter-group-roas { flex: 0 0 200px; } + } + } + + .products-actions { + margin-bottom: 12px; + + .btn-danger { + padding: 7px 14px; + font-size: 13px; + border-radius: 6px; + border: none; + cursor: pointer; + transition: all 0.2s; + + &:disabled { + opacity: 0.4; + cursor: default; + } + } + } + + .products-table-wrap { + background: $cWhite; + border-radius: 10px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); + overflow: hidden; + + .table { + margin: 0; + width: 100% !important; + + thead th { + background: #F8FAFC; + border-bottom: 2px solid $cBorder; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.3px; + color: #8899A6; + padding: 10px 8px; + white-space: nowrap; + } + + tbody td { + padding: 6px 8px; + vertical-align: middle; + border-bottom: 1px solid #F1F5F9; + font-size: 12px; + } + + tbody tr:hover { + background: #F8FAFC; + } + + // Kompaktowe inputy w tabeli + input.min_roas, + input.form-control-sm, + select.custom_label_4, + select.form-control-sm { + padding: 3px 6px; + font-size: 12px; + border: 1px solid $cBorder; + border-radius: 4px; + background: $cWhite; + } + } + + // DataTables 2.x overrides (identyczne z campaigns) + .dt-layout-row { + padding: 14px 20px; + margin: 0 !important; + border-top: 1px solid #F1F5F9; + + &:first-child { + display: none; + } + } + + .dt-info { + font-size: 13px; + color: #8899A6; + } + + .dt-paging { + .pagination { + margin: 0; + padding: 0; + list-style: none; + display: flex; + align-items: center; + gap: 6px; + + .page-item { + .page-link { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 36px; + height: 36px; + padding: 0 14px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + border: 1px solid $cBorder; + background: $cWhite; + color: $cText; + cursor: pointer; + transition: all 0.2s; + text-decoration: none; + line-height: 1; + white-space: nowrap; + + &:hover { + background: #EEF2FF; + color: $cPrimary; + border-color: $cPrimary; + } + } + + &.active .page-link { + background: $cPrimary; + color: $cWhite; + border-color: $cPrimary; + font-weight: 600; + } + + &.disabled .page-link { + opacity: 0.35; + cursor: default; + pointer-events: none; + } + } + } + } + + .dt-processing { + background: rgba($cWhite, 0.9); + color: $cText; + font-size: 14px; + } + } + + // Przycisk usuwania w wierszu + .delete-product { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #FFF5F5; + color: $cDanger; + transition: all 0.2s; + + &:hover { + background: $cDanger; + color: $cWhite; + } + } + + // Przycisk edycji w wierszu + .edit-product-title { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 6px; + border: none; + cursor: pointer; + font-size: 12px; + background: #EEF2FF; + color: $cPrimary; + transition: all 0.2s; + + &:hover { + background: $cPrimary; + color: $cWhite; + } + } +} + +// --- Popup edycji produktu: AI suggest --- +.desc-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 4px; + + label { margin: 0; } +} + +.desc-tabs { + display: flex; + gap: 2px; + background: #eee; + border-radius: 6px; + padding: 2px; +} + +.desc-tab { + border: none; + background: transparent; + padding: 4px 12px; + font-size: 12px; + border-radius: 4px; + cursor: pointer; + color: #666; + transition: all .15s ease; + + i { margin-right: 4px; } + + &.active { + background: #fff; + color: #333; + box-shadow: 0 1px 3px rgba(0,0,0,.12); + font-weight: 500; + } + + &:hover:not(.active) { + color: #333; + } +} + +.desc-wrap { + flex: 1; + min-width: 0; +} + +.desc-preview { + ul, ol { margin: 6px 0; padding-left: 20px; } + li { margin-bottom: 3px; } + b, strong { font-weight: 600; } +} + +.input-with-ai { + display: flex; + gap: 8px; + align-items: flex-start; + + .form-control { + flex: 1; + } +} + +.btn-ai-suggest { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 6px 12px; + border-radius: 8px; + border: 1px solid #C084FC; + background: linear-gradient(135deg, #F3E8FF, #EDE9FE); + color: #7C3AED; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + white-space: nowrap; + min-height: 38px; + + i { + font-size: 13px; + } + &:hover { - .context-menu-container { - display: block; + background: linear-gradient(135deg, #7C3AED, #6D28D9); + color: #FFF; + border-color: #6D28D9; + } + + &:disabled { + opacity: 0.7; + cursor: wait; + } +} + +// --- Form container --- +.form_container { + background: $cWhite; + padding: 25px; + max-width: 1300px; + border-radius: 8px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06); + + &.full { + max-width: 100%; + } + + .form_group { + margin-bottom: 12px; + display: flex; + + >.label { + width: 300px; + display: inline-flex; + align-items: flex-start; + justify-content: right; + padding-right: 12px; + } + + .input { + width: calc(100% - 300px); } } } +// --- Default popup --- +.default_popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.45); + display: none; + z-index: 2000; + + .popup_content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: $cWhite; + padding: 25px; + border-radius: 10px; + max-width: 1140px; + width: 95%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); + + .popup_header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + + .title { + font-size: 18px; + font-weight: 600; + } + } + + .close { + cursor: pointer; + color: #A0AEC0; + font-size: 18px; + padding: 4px; + + &:hover { + color: $cDanger; + } + } + } +} + +// --- DataTables override --- .dt-layout-table { - margin-bottom: 25px; + margin-bottom: 20px; } .pagination { button { - border: 1px solid #eee; - background: #FFF; + border: 1px solid $cBorder; + background: $cWhite; display: inline-flex; - height: 30px; - width: 30px; + height: 32px; + width: 32px; align-items: center; justify-content: center; margin: 0 2px; - transition: all 0.3s ease; + border-radius: 4px; + transition: all 0.2s; + cursor: pointer; &:hover { - background: #eee; + background: $cContentBg; + border-color: $cPrimary; } } } -table { - &#products { - .table-product-title { - display: flex; - justify-content: space-between; - } +// =========================== +// PRODUCTS specific +// =========================== +table#products { + a { + color: inherit; + text-decoration: none; + } - .edit-product-title { - display: flex; - height: 25px; - align-items: center; - justify-content: center; - width: 25px; - cursor: pointer; - background: #fff; - border: 1px solid #9b9b9b; - color: #9b9b9b; + .table-product-title { + display: flex; + justify-content: space-between; + } - &:hover { - background: #9b9b9b; - color: #fff; - } - } + .edit-product-title { + display: flex; + height: 25px; + align-items: center; + justify-content: center; + width: 25px; + cursor: pointer; + background: $cWhite; + border: 1px solid #CBD5E0; + color: #CBD5E0; + border-radius: 4px; - a { - &.custom_name { - color: $cGreenLight !important; - } + &:hover { + background: #CBD5E0; + color: $cWhite; } } + + a.custom_name { + color: $cGreenLight !important; + } } +// --- Chart with form --- .chart-with-form { display: flex; gap: 20px; @@ -1288,122 +1806,121 @@ table { .comment-form { width: 360px; - /* stała, wygodna szerokość prawej kolumny */ flex: 0 0 360px; + + .form-group { + margin-bottom: 12px; + } + + label { + display: block; + font-weight: 600; + margin-bottom: 6px; + font-size: 13px; + } + + input[type="date"], + textarea { + width: 100%; + border: 1px solid $cBorder; + border-radius: 6px; + padding: 8px 12px; + font-size: 14px; + font-family: "Open Sans", sans-serif; + } + + textarea { + min-height: 120px; + resize: vertical; + } + + .btn { + padding: 8px 16px; + } + + .btn[disabled] { + opacity: 0.6; + cursor: not-allowed; + } + + .hint { + font-size: 12px; + color: #718096; + } } -.comment-form .form-group { - margin-bottom: 12px; -} - -.comment-form label { - display: block; - font-weight: 600; - margin-bottom: 6px; -} - -.comment-form input[type="date"], -.comment-form textarea { - width: 100%; - border: 1px solid #ccc; - border-radius: 4px; - padding: 0 8px; - font-size: 14px; -} - -.comment-form textarea { - min-height: 120px; - resize: vertical; -} - -.comment-form .btn { - display: inline-block; - padding: 8px 14px; - border-radius: 4px; - border: 0; - background: #337ab7; - color: #fff; - font-weight: 600; - cursor: pointer; -} - -.comment-form .btn[disabled] { - opacity: .6; - cursor: not-allowed; -} - -.comment-form .hint { - font-size: 12px; - color: #666; -} - -/* === Select2 w modalu "Edytuj produkt" === */ - -/* pełna szerokość i taki sam odstęp jak inne pola */ +// --- Select2 w modalu --- .jconfirm-box .form-group .select2-container { width: 100% !important; margin-top: 8px; } -/* wygląd "inputa" */ .jconfirm-box .select2-container--default .select2-selection--single { - background-color: #fff; - border: 1px solid #ced4da; - /* jak bootstrapowe inputy */ - border-radius: 3px; + background-color: $cWhite; + border: 1px solid $cBorder; + border-radius: 6px; min-height: 42px; - /* wysokość podobna do pola tytułu/opisu */ display: flex; align-items: center; padding: 4px 12px; box-shadow: none; - transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out; + transition: border-color 0.2s, box-shadow 0.2s; font-size: 14px; } -/* tekst w środku */ .jconfirm-box .select2-container--default .select2-selection--single .select2-selection__rendered { padding-left: 0; line-height: 1.4; color: #495057; } -/* placeholder */ .jconfirm-box .select2-container--default .select2-selection--single .select2-selection__placeholder { - color: #adb5bd; + color: #CBD5E0; } -/* strzałka po prawej – wyrównanie */ .jconfirm-box .select2-container--default .select2-selection--single .select2-selection__arrow { height: 100%; right: 8px; } -/* efekt hover/focus – jak na form-control */ .jconfirm-box .select2-container--default.select2-container--focus .select2-selection--single, .jconfirm-box .select2-container--default .select2-selection--single:hover { - border-color: #80bdff; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, .25); + border-color: $cPrimary; + box-shadow: 0 0 0 3px rgba($cPrimary, 0.1); outline: 0; } -/* dropdown (lista kategorii) */ .jconfirm-box .select2-container .select2-dropdown { - border-color: #ced4da; - border-radius: 0 0 3px 3px; + border-color: $cBorder; + border-radius: 0 0 6px 6px; font-size: 14px; } -/* pole wyszukiwania w dropdownie */ .jconfirm-box .select2-container .select2-search--dropdown .select2-search__field { padding: 6px 10px; - border-radius: 3px; - border: 1px solid #ced4da; + border-radius: 4px; + border: 1px solid $cBorder; font-size: 14px; } -/* podświetlenie zaznaczonej pozycji */ .jconfirm-box .select2-container--default .select2-results__option--highlighted[aria-selected] { - background-color: #007bff; - color: #fff; + background-color: $cPrimary; + color: $cWhite; +} + +// =========================== +// RESPONSYWNOŚĆ +// =========================== +@media (max-width: 992px) { + .sidebar { + transform: translateX(-100%); + + &.mobile-open { + transform: translateX(0); + } + } + + .main-wrapper { + margin-left: 0 !important; + } } \ No newline at end of file diff --git a/migrations/001_google_ads_settings.sql b/migrations/001_google_ads_settings.sql new file mode 100644 index 0000000..5f414fd --- /dev/null +++ b/migrations/001_google_ads_settings.sql @@ -0,0 +1,18 @@ +-- Migracja: Tabela settings + kolumna google_ads_customer_id +-- Data: 2026-02-15 +-- Opis: Dodaje globalną tabelę ustawień key-value oraz kolumnę Google Ads Customer ID do tabeli clients + +-- 1. Tabela settings (globalne ustawienia aplikacji) +CREATE TABLE IF NOT EXISTS `settings` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `setting_key` VARCHAR(100) NOT NULL, + `setting_value` TEXT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_setting_key` (`setting_key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- 2. Kolumna google_ads_customer_id w tabeli clients +ALTER TABLE `clients` ADD COLUMN `google_ads_customer_id` VARCHAR(20) NULL DEFAULT NULL AFTER `name`; + +-- 3. Kolumna google_ads_start_date w tabeli clients (data od kiedy pobierać dane z Google Ads API) +ALTER TABLE `clients` ADD COLUMN `google_ads_start_date` DATE NULL DEFAULT NULL AFTER `google_ads_customer_id`; diff --git a/migrations/002_products_data_url.sql b/migrations/002_products_data_url.sql new file mode 100644 index 0000000..bb4cea5 --- /dev/null +++ b/migrations/002_products_data_url.sql @@ -0,0 +1 @@ +ALTER TABLE `products_data` ADD COLUMN `product_url` VARCHAR(500) NULL DEFAULT NULL; diff --git a/migrations/demo_data.sql b/migrations/demo_data.sql new file mode 100644 index 0000000..58e2b6c --- /dev/null +++ b/migrations/demo_data.sql @@ -0,0 +1,361 @@ +-- ============================================================ +-- DANE DEMO dla adsPRO +-- Klient: pomysloweprezenty.pl (client_id = 2) +-- Data generacji: 2026-02-15 +-- ============================================================ +-- UWAGA: Uruchom ten skrypt TYLKO na bazie testowej/deweloperskiej! +-- Zakłada że klient o id=2 istnieje z google_ads_customer_id = '941-605-1782' +-- ============================================================ + +-- ============================================================ +-- 1. KAMPANIE +-- ============================================================ + +INSERT INTO `campaigns` (`client_id`, `campaign_id`, `campaign_name`) VALUES +(2, 20845671001, 'PMAX | Prezenty personalizowane'), +(2, 20845671002, 'PMAX | Bestsellery'), +(2, 20845671003, 'PMAX | Walentynki 2026'), +(2, 20845671004, 'Shopping | Wszystkie produkty'), +(2, 20845671005, 'Shopping | Prezenty do 50 zł'), +(2, 20845671006, 'Shopping | Prezenty premium'), +(2, 20845671007, 'PMAX | Nowości'), +(2, 20845671008, 'Shopping | Bestsellery high ROAS'); + +-- Zapamiętaj ID kampanii (zakładam auto_increment od 1) +SET @camp1 = (SELECT id FROM campaigns WHERE campaign_id = 20845671001 AND client_id = 2); +SET @camp2 = (SELECT id FROM campaigns WHERE campaign_id = 20845671002 AND client_id = 2); +SET @camp3 = (SELECT id FROM campaigns WHERE campaign_id = 20845671003 AND client_id = 2); +SET @camp4 = (SELECT id FROM campaigns WHERE campaign_id = 20845671004 AND client_id = 2); +SET @camp5 = (SELECT id FROM campaigns WHERE campaign_id = 20845671005 AND client_id = 2); +SET @camp6 = (SELECT id FROM campaigns WHERE campaign_id = 20845671006 AND client_id = 2); +SET @camp7 = (SELECT id FROM campaigns WHERE campaign_id = 20845671007 AND client_id = 2); +SET @camp8 = (SELECT id FROM campaigns WHERE campaign_id = 20845671008 AND client_id = 2); + +-- ============================================================ +-- 2. HISTORIA KAMPANII (30 dni: 2026-01-16 do 2026-02-14) +-- ============================================================ + +-- Generujemy dane dzienne za pomocą procedury +DELIMITER // +DROP PROCEDURE IF EXISTS generate_campaign_history// +CREATE PROCEDURE generate_campaign_history() +BEGIN + DECLARE i INT DEFAULT 0; + DECLARE cur_date DATE; + DECLARE day_factor FLOAT; + + WHILE i < 30 DO + SET cur_date = DATE_SUB('2026-02-14', INTERVAL i DAY); + -- Weekendy mają wyższe wydatki (factor 1.2-1.4), dni powszednie normalne + SET day_factor = CASE DAYOFWEEK(cur_date) + WHEN 1 THEN 1.3 -- niedziela + WHEN 7 THEN 1.4 -- sobota + WHEN 6 THEN 1.1 -- piątek + ELSE 1.0 + END; + + -- Walentynki boost (7-14 lutego) + IF cur_date BETWEEN '2026-02-07' AND '2026-02-14' THEN + SET day_factor = day_factor * 1.5; + END IF; + + -- PMAX | Prezenty personalizowane (wysoki ROAS, duży budżet) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp1, cur_date, + ROUND(450 + RAND() * 150, 2), + ROUND(520 + RAND() * 80, 2), + 150.00, + ROUND((130 + RAND() * 40) * day_factor, 2), + ROUND((600 + RAND() * 300) * day_factor, 2), + 'Docelowy ROAS | docelowy ROAS: 400%'); + + -- PMAX | Bestsellery (bardzo wysoki ROAS) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp2, cur_date, + ROUND(650 + RAND() * 200, 2), + ROUND(700 + RAND() * 100, 2), + 200.00, + ROUND((170 + RAND() * 60) * day_factor, 2), + ROUND((1200 + RAND() * 500) * day_factor, 2), + 'Docelowy ROAS | docelowy ROAS: 600%'); + + -- PMAX | Walentynki 2026 (sezonowa, wysoki wydatek w lutym) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp3, cur_date, + ROUND(350 + RAND() * 200 + IF(cur_date >= '2026-02-01', 100, 0), 2), + ROUND(380 + RAND() * 120, 2), + ROUND(IF(cur_date >= '2026-02-01', 250, 80), 2), + ROUND((IF(cur_date >= '2026-02-01', 200, 60) + RAND() * 50) * day_factor, 2), + ROUND((IF(cur_date >= '2026-02-01', 800, 200) + RAND() * 400) * day_factor, 2), + 'Maksymalizacja wartosci konwersji'); + + -- Shopping | Wszystkie produkty (umiarkowany ROAS, duży budżet) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp4, cur_date, + ROUND(300 + RAND() * 100, 2), + ROUND(350 + RAND() * 60, 2), + 100.00, + ROUND((85 + RAND() * 30) * day_factor, 2), + ROUND((280 + RAND() * 120) * day_factor, 2), + 'Docelowy ROAS | docelowy ROAS: 300%'); + + -- Shopping | Prezenty do 50 zł (niski CPC, dobry ROAS) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp5, cur_date, + ROUND(380 + RAND() * 120, 2), + ROUND(400 + RAND() * 80, 2), + 50.00, + ROUND((40 + RAND() * 15) * day_factor, 2), + ROUND((170 + RAND() * 80) * day_factor, 2), + 'Docelowy ROAS | docelowy ROAS: 350%'); + + -- Shopping | Prezenty premium (niski ROAS, wysoki AOV) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp6, cur_date, + ROUND(200 + RAND() * 100, 2), + ROUND(250 + RAND() * 80, 2), + 80.00, + ROUND((65 + RAND() * 25) * day_factor, 2), + ROUND((150 + RAND() * 100) * day_factor, 2), + 'Maksymalizacja wartosci konwersji'); + + -- PMAX | Nowości (nowa kampania, niski ROAS na start) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp7, cur_date, + ROUND(180 + RAND() * 100 + i * 3, 2), + ROUND(200 + RAND() * 80, 2), + 60.00, + ROUND((50 + RAND() * 20) * day_factor, 2), + ROUND((100 + RAND() * 80 + i * 5) * day_factor, 2), + 'Maksymalizacja liczby konwersji'); + + -- Shopping | Bestsellery high ROAS (najlepsza kampania) + INSERT INTO campaigns_history (campaign_id, date_add, roas_30_days, roas_all_time, budget, money_spent, conversion_value, bidding_strategy) VALUES + (@camp8, cur_date, + ROUND(800 + RAND() * 300, 2), + ROUND(850 + RAND() * 150, 2), + 120.00, + ROUND((100 + RAND() * 35) * day_factor, 2), + ROUND((900 + RAND() * 400) * day_factor, 2), + 'Docelowy ROAS | docelowy ROAS: 700%'); + + SET i = i + 1; + END WHILE; +END// +DELIMITER ; + +CALL generate_campaign_history(); +DROP PROCEDURE IF EXISTS generate_campaign_history; + + +-- ============================================================ +-- 3. PRODUKTY (25 produktów - realistyczne nazwy sklepu z prezentami) +-- ============================================================ + +INSERT INTO `products` (`client_id`, `offer_id`, `name`) VALUES +(2, 'shopify_PL_8901001', 'Kubek personalizowany ze zdjęciem - Biały 330ml'), +(2, 'shopify_PL_8901002', 'Poduszka z własnym nadrukiem 40x40cm'), +(2, 'shopify_PL_8901003', 'Brelok LED z grawerem - Serce'), +(2, 'shopify_PL_8901004', 'Ramka na zdjęcie z dedykacją - Drewniana A4'), +(2, 'shopify_PL_8901005', 'Puzzle ze zdjęciem 500 elementów'), +(2, 'shopify_PL_8901006', 'Koszulka z nadrukiem - Dla Najlepszego Taty'), +(2, 'shopify_PL_8901007', 'Skarpetki personalizowane - Zestaw 3 par'), +(2, 'shopify_PL_8901008', 'Kubek magiczny zmieniający kolor ze zdjęciem'), +(2, 'shopify_PL_8901009', 'Plakat personalizowany - Mapa Gwiazd A3'), +(2, 'shopify_PL_8901010', 'Biżuteria - Naszyjnik z grawerem Serce'), +(2, 'shopify_PL_8901011', 'Etui na telefon z własnym zdjęciem'), +(2, 'shopify_PL_8901012', 'Torba bawełniana z nadrukiem'), +(2, 'shopify_PL_8901013', 'Kalendarz ze zdjęciami 2026 - Ścienny A3'), +(2, 'shopify_PL_8901014', 'Podkładka pod mysz z własnym zdjęciem'), +(2, 'shopify_PL_8901015', 'Zestaw upominkowy - Kubek + Podkładka + Brelok'), +(2, 'shopify_PL_8901016', 'Obraz na płótnie Canvas 60x40cm ze zdjęciem'), +(2, 'shopify_PL_8901017', 'Otwieracz do butelek z grawerem'), +(2, 'shopify_PL_8901018', 'Fotokolaż na płótnie 50x70cm'), +(2, 'shopify_PL_8901019', 'Koc z nadrukiem polarowy 150x200cm'), +(2, 'shopify_PL_8901020', 'Piersiówka z grawerem 200ml'), +(2, 'shopify_PL_8901021', 'Zegar ścienny ze zdjęciem - Okrągły 30cm'), +(2, 'shopify_PL_8901022', 'Ręcznik z haftem imienia 70x140cm'), +(2, 'shopify_PL_8901023', 'Walentynkowy zestaw - Kubek + Czekolada + Kartka'), +(2, 'shopify_PL_8901024', 'Magnes na lodówkę z własnym zdjęciem'), +(2, 'shopify_PL_8901025', 'Szkatułka drewniana z grawerem'); + +-- ============================================================ +-- 4. HISTORIA PRODUKTÓW (30 dni dziennych danych) +-- ============================================================ + +DELIMITER // +DROP PROCEDURE IF EXISTS generate_product_history// +CREATE PROCEDURE generate_product_history() +BEGIN + DECLARE i INT DEFAULT 0; + DECLARE j INT DEFAULT 1; + DECLARE cur_date DATE; + DECLARE prod_id INT; + DECLARE base_imp INT; + DECLARE base_clicks INT; + DECLARE base_cost DECIMAL(10,2); + DECLARE base_conv INT; + DECLARE base_conv_val DECIMAL(10,2); + DECLARE day_imp INT; + DECLARE day_clicks INT; + DECLARE day_cost DECIMAL(10,2); + DECLARE day_conv INT; + DECLARE day_conv_val DECIMAL(10,2); + DECLARE day_ctr DECIMAL(6,4); + DECLARE day_factor FLOAT; + DECLARE product_count INT; + + SET product_count = 25; + + -- Dla każdego produktu + SET j = 1; + WHILE j <= product_count DO + -- Pobierz ID produktu + SET prod_id = (SELECT id FROM products WHERE client_id = 2 AND offer_id = CONCAT('shopify_PL_890100', j) LIMIT 1); + IF prod_id IS NULL AND j >= 10 THEN + SET prod_id = (SELECT id FROM products WHERE client_id = 2 AND offer_id = CONCAT('shopify_PL_89010', j) LIMIT 1); + END IF; + + -- Bazowe metryki różne per produkt (symulacja różnej popularności) + CASE j + WHEN 1 THEN SET base_imp = 850, base_clicks = 45, base_cost = 12.50, base_conv = 3, base_conv_val = 89.70; -- Kubek - bestseller + WHEN 2 THEN SET base_imp = 620, base_clicks = 32, base_cost = 9.80, base_conv = 2, base_conv_val = 79.80; -- Poduszka + WHEN 3 THEN SET base_imp = 1200, base_clicks = 68, base_cost = 15.20, base_conv = 5, base_conv_val = 74.75; -- Brelok LED - top impressions + WHEN 4 THEN SET base_imp = 380, base_clicks = 18, base_cost = 7.20, base_conv = 1, base_conv_val = 59.90; -- Ramka + WHEN 5 THEN SET base_imp = 520, base_clicks = 28, base_cost = 11.00, base_conv = 2, base_conv_val = 99.80; -- Puzzle + WHEN 6 THEN SET base_imp = 780, base_clicks = 42, base_cost = 13.50, base_conv = 3, base_conv_val = 89.70; -- Koszulka + WHEN 7 THEN SET base_imp = 450, base_clicks = 22, base_cost = 6.80, base_conv = 2, base_conv_val = 49.90; -- Skarpetki + WHEN 8 THEN SET base_imp = 680, base_clicks = 38, base_cost = 10.50, base_conv = 2, base_conv_val = 69.90; -- Kubek magiczny + WHEN 9 THEN SET base_imp = 920, base_clicks = 55, base_cost = 18.00, base_conv = 4, base_conv_val = 159.60; -- Plakat mapa gwiazd - wysoki AOV + WHEN 10 THEN SET base_imp = 1100, base_clicks = 72, base_cost = 22.00, base_conv = 5, base_conv_val = 349.50; -- Naszyjnik - top conversion value + WHEN 11 THEN SET base_imp = 550, base_clicks = 30, base_cost = 8.50, base_conv = 1, base_conv_val = 39.90; -- Etui + WHEN 12 THEN SET base_imp = 320, base_clicks = 14, base_cost = 4.20, base_conv = 1, base_conv_val = 29.90; -- Torba + WHEN 13 THEN SET base_imp = 280, base_clicks = 12, base_cost = 5.00, base_conv = 0, base_conv_val = 0.00; -- Kalendarz - słaby (po sezonie) + WHEN 14 THEN SET base_imp = 180, base_clicks = 8, base_cost = 2.50, base_conv = 0, base_conv_val = 0.00; -- Podkładka - zombie + WHEN 15 THEN SET base_imp = 740, base_clicks = 40, base_cost = 14.00, base_conv = 3, base_conv_val = 179.70; -- Zestaw upominkowy + WHEN 16 THEN SET base_imp = 480, base_clicks = 25, base_cost = 16.00, base_conv = 1, base_conv_val = 149.00; -- Canvas + WHEN 17 THEN SET base_imp = 350, base_clicks = 18, base_cost = 5.50, base_conv = 1, base_conv_val = 34.90; -- Otwieracz + WHEN 18 THEN SET base_imp = 410, base_clicks = 22, base_cost = 14.00, base_conv = 1, base_conv_val = 189.00; -- Fotokolaż + WHEN 19 THEN SET base_imp = 290, base_clicks = 15, base_cost = 9.00, base_conv = 1, base_conv_val = 119.00; -- Koc + WHEN 20 THEN SET base_imp = 420, base_clicks = 24, base_cost = 7.00, base_conv = 1, base_conv_val = 69.90; -- Piersiówka + WHEN 21 THEN SET base_imp = 260, base_clicks = 11, base_cost = 4.50, base_conv = 0, base_conv_val = 0.00; -- Zegar - słaby + WHEN 22 THEN SET base_imp = 340, base_clicks = 16, base_cost = 6.00, base_conv = 1, base_conv_val = 59.90; -- Ręcznik + WHEN 23 THEN SET base_imp = 1500, base_clicks = 95, base_cost = 28.00, base_conv = 8, base_conv_val = 319.20; -- Walentynkowy zestaw - HIT + WHEN 24 THEN SET base_imp = 150, base_clicks = 6, base_cost = 1.80, base_conv = 0, base_conv_val = 0.00; -- Magnes - zombie + WHEN 25 THEN SET base_imp = 380, base_clicks = 20, base_cost = 8.00, base_conv = 1, base_conv_val = 89.90; -- Szkatułka + ELSE SET base_imp = 300, base_clicks = 15, base_cost = 5.00, base_conv = 1, base_conv_val = 50.00; + END CASE; + + -- 30 dni historii + SET i = 0; + WHILE i < 30 DO + SET cur_date = DATE_SUB('2026-02-14', INTERVAL i DAY); + + SET day_factor = CASE DAYOFWEEK(cur_date) + WHEN 1 THEN 1.25 + WHEN 7 THEN 1.35 + WHEN 6 THEN 1.1 + ELSE 1.0 + END; + + -- Walentynki boost (szczególnie produkty walentynkowe: j=23, j=10, j=9) + IF cur_date BETWEEN '2026-02-07' AND '2026-02-14' THEN + IF j IN (23, 10, 9, 3, 1) THEN + SET day_factor = day_factor * 2.0; + ELSE + SET day_factor = day_factor * 1.3; + END IF; + END IF; + + SET day_imp = ROUND(base_imp * day_factor * (0.8 + RAND() * 0.4)); + SET day_clicks = ROUND(base_clicks * day_factor * (0.7 + RAND() * 0.6)); + SET day_cost = ROUND(base_cost * day_factor * (0.8 + RAND() * 0.4), 2); + -- Konwersje: losowe z prawdopodobieństwem bazowym + SET day_conv = FLOOR(base_conv * day_factor * (0.3 + RAND() * 1.4)); + SET day_conv_val = ROUND(IF(day_conv > 0, day_conv * (base_conv_val / GREATEST(base_conv, 1)) * (0.9 + RAND() * 0.2), 0), 2); + SET day_ctr = IF(day_imp > 0, ROUND(day_clicks / day_imp, 4) * 100, 0); + + IF prod_id IS NOT NULL THEN + INSERT INTO products_history (product_id, date_add, impressions, clicks, ctr, cost, conversions, conversions_value, updated) VALUES + (prod_id, cur_date, day_imp, day_clicks, day_ctr, day_cost, day_conv, day_conv_val, 0); + END IF; + + SET i = i + 1; + END WHILE; + + SET j = j + 1; + END WHILE; +END// +DELIMITER ; + +CALL generate_product_history(); +DROP PROCEDURE IF EXISTS generate_product_history; + + +-- ============================================================ +-- 5. PRODUCTS_TEMP (zagregowane dane - jak po cron_products) +-- ============================================================ + +INSERT INTO products_temp (product_id, name, impressions, impressions_30, clicks, clicks_30, ctr, cost, conversions, conversions_value, cpc, roas) +SELECT + p.id, + p.name, + COALESCE(SUM(ph.impressions), 0), + COALESCE(SUM(ph.impressions), 0), + COALESCE(SUM(ph.clicks), 0), + COALESCE(SUM(ph.clicks), 0), + CASE WHEN SUM(ph.impressions) > 0 THEN ROUND(SUM(ph.clicks) / SUM(ph.impressions) * 100, 2) ELSE 0 END, + COALESCE(SUM(ph.cost), 0), + COALESCE(SUM(ph.conversions), 0), + COALESCE(SUM(ph.conversions_value), 0), + CASE WHEN SUM(ph.clicks) > 0 THEN ROUND(SUM(ph.cost) / SUM(ph.clicks), 6) ELSE 0 END, + CASE WHEN SUM(ph.conversions) > 0 AND SUM(ph.cost) > 0 THEN ROUND(SUM(ph.conversions_value) / SUM(ph.cost) * 100, 2) ELSE 0 END +FROM products p +LEFT JOIN products_history ph ON p.id = ph.product_id +WHERE p.client_id = 2 +GROUP BY p.id, p.name; + + +-- ============================================================ +-- 6. PRODUCTS_DATA (custom labels) +-- ============================================================ + +-- Bestsellery (wysoki ROAS + dużo konwersji) +INSERT INTO products_data (product_id, custom_label_4) VALUES +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901001' AND client_id = 2), 'bestseller'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901003' AND client_id = 2), 'bestseller'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901010' AND client_id = 2), 'bestseller'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901023' AND client_id = 2), 'bestseller'); + +-- Produkty PLA (w kampaniach Shopping) +INSERT INTO products_data (product_id, custom_label_4) VALUES +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901005' AND client_id = 2), 'pla'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901006' AND client_id = 2), 'pla'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901015' AND client_id = 2), 'pla'); + +-- Zombie (bardzo niskie wyświetlenia) +INSERT INTO products_data (product_id, custom_label_4) VALUES +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901014' AND client_id = 2), 'zombie'), +((SELECT id FROM products WHERE offer_id = 'shopify_PL_8901024' AND client_id = 2), 'zombie'); + + +-- ============================================================ +-- 7. MIN ROAS per klient (bestseller threshold) +-- ============================================================ + +-- Ustaw min ROAS dla wybranych produktów +UPDATE products SET min_roas = 500 WHERE offer_id = 'shopify_PL_8901001' AND client_id = 2; +UPDATE products SET min_roas = 400 WHERE offer_id = 'shopify_PL_8901003' AND client_id = 2; +UPDATE products SET min_roas = 600 WHERE offer_id = 'shopify_PL_8901010' AND client_id = 2; +UPDATE products SET min_roas = 350 WHERE offer_id = 'shopify_PL_8901023' AND client_id = 2; + + +-- ============================================================ +-- GOTOWE! +-- Po wykonaniu tego skryptu: +-- 1. Wejdź na /campaigns → wybierz klienta pomysloweprezenty.pl +-- → zobaczysz 8 kampanii z 30-dniową historią +-- 2. Wejdź na /products → wybierz klienta +-- → zobaczysz 25 produktów z metrykami +-- 3. Kliknij dowolny produkt → zobaczysz historię i wykres +-- ============================================================ diff --git a/templates/campaigns/main_view.php b/templates/campaigns/main_view.php index cafe3a7..0846ae9 100644 --- a/templates/campaigns/main_view.php +++ b/templates/campaigns/main_view.php @@ -1,361 +1,336 @@ -
-
-
-
-
- -
-
-
-
- -
-
- -
-
-
+
+
+

Kampanie

+
+ + +
+
+ + +
+
+ +
+ +
-
-
-
-
-
-
-
-
+ +
+
+
+ + +
+ + + + + + + + + + + + + + + + +
DataROAS (30 dni)ROAS (all time)Wartość konwersji (30 dni)Wydatki (30 dni)KomentarzStrategia ustalania stawekBudżetAkcje
-
-
-
- - - - - - - - - - - - - - - - - -
DataROAS (30 dni)ROAS (all time)Wartość konwersji (30 dni)Wydatki (30 dni)KomentarzStrategia ustalania stawekBudżetAkcje
-
-
-
\ No newline at end of file + + // Usuwanie kampanii + $( 'body' ).on( 'click', '#delete_campaign', function() + { + var campaign_id = $( '#campaign_id' ).val(); + var campaign_name = $( '#campaign_id option:selected' ).text(); + + if ( !campaign_id ) + { + $.alert({ + title: 'Uwaga', + content: 'Najpierw wybierz kampanię do usunięcia.', + type: 'orange' + }); + return; + } + + $.confirm({ + title: 'Potwierdzenie usunięcia', + content: 'Czy na pewno chcesz usunąć kampanię ' + campaign_name + '?

Ta operacja jest nieodwracalna i usunie również całą historię kampanii.', + type: 'red', + buttons: { + confirm: { + text: 'Usuń', + btnClass: 'btn-red', + keys: ['enter'], + action: function() + { + $.ajax({ + url: '/campaigns/delete_campaign/campaign_id=' + campaign_id, + type: 'POST', + success: function( response ) + { + var data = JSON.parse( response ); + if ( data.success ) + { + $.alert({ + title: 'Sukces', + content: 'Kampania została usunięta.', + type: 'green', + autoClose: 'ok|2000' + }); + $( '#client_id' ).trigger( 'change' ); + } + else + { + $.alert({ + title: 'Błąd', + content: data.message || 'Nie udało się usunąć kampanii.', + type: 'red' + }); + } + } + }); + } + }, + cancel: { text: 'Anuluj' } + } + }); + }); + + // Usuwanie wpisu historii + $( 'body' ).on( 'click', '.delete-history-entry', function() + { + var btn = $( this ); + var history_id = btn.data( 'id' ); + var date = btn.data( 'date' ); + + $.confirm({ + title: 'Potwierdzenie usunięcia', + content: 'Czy na pewno chcesz usunąć wpis z dnia ' + date + '?', + type: 'red', + buttons: { + confirm: { + text: 'Usuń', + btnClass: 'btn-red', + keys: ['enter'], + action: function() + { + $.ajax({ + url: '/campaigns/delete_history_entry/history_id=' + history_id, + type: 'POST', + success: function( response ) + { + var data = JSON.parse( response ); + if ( data.success ) + { + $.alert({ + title: 'Sukces', + content: 'Wpis został usunięty.', + type: 'green', + autoClose: 'ok|2000' + }); + $( '#products' ).DataTable().ajax.reload( null, false ); + reloadChart(); + } + else + { + $.alert({ + title: 'Błąd', + content: data.message || 'Nie udało się usunąć wpisu.', + type: 'red' + }); + } + } + }); + } + }, + cancel: { text: 'Anuluj' } + } + }); + }); + + // Załaduj dane po wyborze kampanii + $( 'body' ).on( 'change', '#campaign_id', function() + { + var campaign_id = $( this ).val(); + + table = $( '#products' ).DataTable(); + table.destroy(); + + new DataTable( '#products', { + ajax: { + type: 'POST', + url: '/campaigns/get_campaign_history_data_table/campaign_id=' + campaign_id, + }, + processing: true, + serverSide: true, + searching: false, + lengthChange: false, + pageLength: 15, + columns: [ + { width: '130px', name: 'date', orderable: false, className: "nowrap" }, + { width: '120px', name: 'roas30', orderable: false, className: "dt-type-numeric" }, + { width: '120px', name: 'roas_all_time', orderable: false, className: "dt-type-numeric" }, + { width: '180px', name: 'conversion_value', orderable: false, className: "dt-type-numeric" }, + { width: '140px', name: 'spend30', orderable: false, className: "dt-type-numeric" }, + { width: 'auto', name: 'comment', orderable: false }, + { width: 'auto', name: 'bidding_strategy', orderable: false }, + { width: '100px', name: 'budget', orderable: false, className: "dt-type-numeric" }, + { width: '60px', name: 'actions', orderable: false, className: "dt-center" } + ], + language: { + processing: 'Ładowanie...', + emptyTable: 'Brak danych do wyświetlenia', + info: 'Wpisy _START_ - _END_ z _TOTAL_', + infoEmpty: '', + lengthMenu: 'Pokaż _MENU_ wpisów', + paginate: { + first: 'Pierwsza', + last: 'Ostatnia', + next: 'Dalej', + previous: 'Wstecz' + } + } + }); + + reloadChart(); + }); +}); + diff --git a/templates/clients/main_view.php b/templates/clients/main_view.php new file mode 100644 index 0000000..62c4aa5 --- /dev/null +++ b/templates/clients/main_view.php @@ -0,0 +1,158 @@ +
+
+

Klienci

+ +
+ +
+ + + + + + + + + + + + clients ): ?> + clients as $client ): ?> + + + + + + + + + + + + + + +
#IDNazwa klientaGoogle Ads Customer IDDane odAkcje
+ + + + — brak — + + + + + + — brak — + + + + +
+ +

Brak klientów. Dodaj pierwszego klienta.

+
+
+
+ + +
+ +
+ + diff --git a/templates/products/main_view.php b/templates/products/main_view.php index b5f55b9..cc04ae2 100644 --- a/templates/products/main_view.php +++ b/templates/products/main_view.php @@ -1,540 +1,563 @@ -
-
-
-
-
- -
-
- -
-
- -
-
+
+
+

Produkty

+
+ + +
+
+ +
+
+ + +
+
+ + +
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
IdId ofertyNazwa produktuWyśw.Wyśw. (30d)Klik.Klik. (30d)CTRKosztCPCKonw.Wart. konw.ROASMin. ROASCL3CL4Akcje
-
-
-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
IdId ofertyNazwa produktuWyśw.Wyśw. (30 dni)Klik.Klik. (30 dni)CTRKosztCPCKonw.Wartość konw.ROASMin. ROASCL3CL4Akcje
-
-
-
\ No newline at end of file + + // Zapis min ROAS klienta (bestseller) + $( 'body' ).on( 'blur', '#bestseller_min_roas', function() + { + var min_roas = $( this ).val(); + var client_id = $( '#client_id' ).val(); + + $.ajax({ + url: '/products/save_client_bestseller_min_roas/', + type: 'POST', + data: { client_id: client_id, min_roas: min_roas }, + success: function( response ) { + data = JSON.parse( response ); + if ( data.status == 'ok' ) { + $.alert({ title: 'Zapisano', content: 'Minimalny ROAS bestsellerów został zapisany.', type: 'green', autoClose: 'ok|2000', buttons: { ok: function() {} } }); + } + } + }); + }); + + // Checkbox: zaznacz/odznacz wszystkie + function updateSelectedCount() { + var count = $( '.product-checkbox:checked' ).length; + $( '#selected-count' ).text( count ); + $( '#delete-selected-products' ).prop( 'disabled', count === 0 ); + } + + $( 'body' ).on( 'change', '#select-all-products', function() { + $( '.product-checkbox' ).prop( 'checked', $( this ).is( ':checked' ) ); + updateSelectedCount(); + }); + + $( 'body' ).on( 'change', '.product-checkbox', function() { + updateSelectedCount(); + var allChecked = $( '.product-checkbox' ).length === $( '.product-checkbox:checked' ).length; + $( '#select-all-products' ).prop( 'checked', allChecked ); + }); + + $( '#products' ).on( 'draw.dt', function() { + $( '#select-all-products' ).prop( 'checked', false ); + updateSelectedCount(); + }); + + // Usuwanie zaznaczonych produktów + $( 'body' ).on( 'click', '#delete-selected-products', function() + { + var selectedIds = []; + $( '.product-checkbox:checked' ).each( function() { selectedIds.push( $( this ).val() ); }); + + if ( selectedIds.length === 0 ) { + $.alert( 'Nie zaznaczono żadnych produktów.' ); + return; + } + + $.confirm({ + title: 'Potwierdzenie', + content: 'Czy na pewno chcesz usunąć ' + selectedIds.length + ' zaznaczonych produktów?', + type: 'red', + buttons: { + confirm: { + text: 'Usuń', + btnClass: 'btn-red', + keys: ['enter'], + action: function() { + $.ajax({ + url: '/products/delete_products/', + type: 'POST', + data: { product_ids: selectedIds }, + success: function( response ) { + var data = JSON.parse( response ); + if ( data.status == 'ok' ) { + $.alert({ + title: 'Sukces', + content: 'Usunięto ' + selectedIds.length + ' produktów.', + type: 'green', + autoClose: 'ok|2000', + buttons: { ok: function() {} } + }); + $( '#products' ).DataTable().ajax.reload( null, false ); + } + } + }); + } + }, + cancel: { text: 'Anuluj' } + } + }); + }); +}); + diff --git a/templates/site/layout-logged.php b/templates/site/layout-logged.php index c5c2568..ca06b05 100644 --- a/templates/site/layout-logged.php +++ b/templates/site/layout-logged.php @@ -4,14 +4,13 @@ - crmPro - - - + adsPRO + + @@ -26,11 +25,9 @@ - - + - - + @@ -38,71 +35,146 @@ -
- -
-
- user[ 'email' ];?> + current_module; + ?> + + + + +
+
+ +
+ 'Kampanie', + 'products' => 'Produkty', + 'clients' => 'Klienci', + 'allegro' => 'Allegro import', + 'users' => 'Ustawienia', + ]; + echo $breadcrumbs[$module] ?? 'adsPRO'; + ?> +
+
+
+ alert ): ?> +
alert; ?>
+ + content; ?> +
- -
- alert ):?> -
alert;?>
- - content;?> -
+ +
- diff --git a/templates/site/layout-unlogged.php b/templates/site/layout-unlogged.php index 0110132..0faee04 100644 --- a/templates/site/layout-unlogged.php +++ b/templates/site/layout-unlogged.php @@ -4,18 +4,41 @@ - adsPRO - - - + adsPRO - Logowanie + + - content;?> + - \ No newline at end of file + diff --git a/templates/users/login-form.php b/templates/users/login-form.php index 7e45ffc..fe5b448 100644 --- a/templates/users/login-form.php +++ b/templates/users/login-form.php @@ -1,101 +1,93 @@ -