diff --git a/app/build.gradle b/app/build.gradle index affaa30..27ac40b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,9 @@ apply plugin: 'com.android.application' +project.extensions.react = [ + root: "../" +] + apply from: "../node_modules/react-native/react.gradle" apply from: "../node_modules/react-native-vector-icons/fonts.gradle" @@ -13,17 +17,33 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + ndk { + abiFilters "armeabi-v7a", "x86" + } + } + signingConfigs { + release { + if (project.hasProperty('BUKA_ANALYTICS_STORE_FILE')) { + storeFile file(BUKA_ANALYTICS_STORE_FILE) + storePassword BUKA_ANALYTICS_STORE_PASSWORD + keyAlias BUKA_ANALYTICS_KEY_ALIAS + keyPassword BUKA_ANALYTICS_RELEASE_KEY_PASSWORD + } + } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } } - configurations.all { resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0' } + lintOptions { + checkReleaseBuilds false + } } dependencies { @@ -31,6 +51,7 @@ dependencies { androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) + compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'com.koushikdutta.ion:ion:2.+' compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support:support-v4:25.3.1' diff --git a/app/bukaanalytics.keystore b/app/bukaanalytics.keystore new file mode 100644 index 0000000..eb5f476 Binary files /dev/null and b/app/bukaanalytics.keystore differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce3ff34..b17084c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,10 +19,13 @@ + + + - + + value_string; + public static ArrayList tableheadernames; + public static ArrayList emptytablecolumnnames; + public static boolean isEmpty; + public static boolean isCustomQuery; + } + +// all global variables + + //in the below line Change the text 'yourCustomSqlHelper' with your custom sqlitehelper class name. + //Do not change the variable name dbm + BukaAnalyticsSqliteOpenHelper dbm; + TableLayout tableLayout; + TableRow.LayoutParams tableRowParams; + HorizontalScrollView hsv; + ScrollView mainscrollview; + LinearLayout mainLayout; + TextView tvmessage; + Button previous; + Button next; + Spinner select_table; + TextView tv; + + indexInfo info = new indexInfo(); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + + //in the below line Change the text 'yourCustomSqlHelper' with your custom sqlitehelper class name + dbm = new BukaAnalyticsSqliteOpenHelper(AndroidDatabaseManager.this); + + mainscrollview = new ScrollView(AndroidDatabaseManager.this); + + //the main linear layout to which all tables spinners etc will be added.In this activity every element is created dynamically to avoid using xml file + mainLayout = new LinearLayout(AndroidDatabaseManager.this); + mainLayout.setOrientation(LinearLayout.VERTICAL); + mainLayout.setBackgroundColor(Color.WHITE); + mainLayout.setScrollContainer(true); + mainscrollview.addView(mainLayout); + + //all required layouts are created dynamically and added to the main scrollview + setContentView(mainscrollview); + + //the first row of layout which has a text view and spinner + final LinearLayout firstrow = new LinearLayout(AndroidDatabaseManager.this); + firstrow.setPadding(0,10,0,20); + LinearLayout.LayoutParams firstrowlp = new LinearLayout.LayoutParams(0, 150); + firstrowlp.weight = 1; + + TextView maintext = new TextView(AndroidDatabaseManager.this); + maintext.setText("Select Table"); + maintext.setTextSize(22); + maintext.setLayoutParams(firstrowlp); + select_table=new Spinner(AndroidDatabaseManager.this); + select_table.setLayoutParams(firstrowlp); + + firstrow.addView(maintext); + firstrow.addView(select_table); + mainLayout.addView(firstrow); + + ArrayList alc ; + + //the horizontal scroll view for table if the table content doesnot fit into screen + hsv = new HorizontalScrollView(AndroidDatabaseManager.this); + + //the main table layout where the content of the sql tables will be displayed when user selects a table + tableLayout = new TableLayout(AndroidDatabaseManager.this); + tableLayout.setHorizontalScrollBarEnabled(true); + hsv.addView(tableLayout); + + //the second row of the layout which shows number of records in the table selected by user + final LinearLayout secondrow = new LinearLayout(AndroidDatabaseManager.this); + secondrow.setPadding(0,20,0,10); + LinearLayout.LayoutParams secondrowlp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + secondrowlp.weight = 1; + TextView secondrowtext = new TextView(AndroidDatabaseManager.this); + secondrowtext.setText("No. Of Records : "); + secondrowtext.setTextSize(20); + secondrowtext.setLayoutParams(secondrowlp); + tv =new TextView(AndroidDatabaseManager.this); + tv.setTextSize(20); + tv.setLayoutParams(secondrowlp); + secondrow.addView(secondrowtext); + secondrow.addView(tv); + mainLayout.addView(secondrow); + //A button which generates a text view from which user can write custome queries + final EditText customquerytext = new EditText(this); + customquerytext.setVisibility(View.GONE); + customquerytext.setHint("Enter Your Query here and Click on Submit Query Button .Results will be displayed below"); + mainLayout.addView(customquerytext); + + final Button submitQuery = new Button(AndroidDatabaseManager.this); + submitQuery.setVisibility(View.GONE); + submitQuery.setText("Submit Query"); + + submitQuery.setBackgroundColor(Color.parseColor("#BAE7F6")); + mainLayout.addView(submitQuery); + + final TextView help = new TextView(AndroidDatabaseManager.this); + help.setText("Click on the row below to update values or delete the tuple"); + help.setPadding(0,5,0,5); + + // the spinner which gives user a option to add new row , drop or delete table + final Spinner spinnertable =new Spinner(AndroidDatabaseManager.this); + mainLayout.addView(spinnertable); + mainLayout.addView(help); + hsv.setPadding(0,10,0,10); + hsv.setScrollbarFadingEnabled(false); + hsv.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET); + mainLayout.addView(hsv); + //the third layout which has buttons for the pagination of content from database + final LinearLayout thirdrow = new LinearLayout(AndroidDatabaseManager.this); + previous = new Button(AndroidDatabaseManager.this); + previous.setText("Previous"); + + previous.setBackgroundColor(Color.parseColor("#BAE7F6")); + previous.setLayoutParams(secondrowlp); + next = new Button(AndroidDatabaseManager.this); + next.setText("Next"); + next.setBackgroundColor(Color.parseColor("#BAE7F6")); + next.setLayoutParams(secondrowlp); + TextView tvblank = new TextView(this); + tvblank.setLayoutParams(secondrowlp); + thirdrow.setPadding(0,10,0,10); + thirdrow.addView(previous); + thirdrow.addView(tvblank); + thirdrow.addView(next); + mainLayout.addView(thirdrow); + + //the text view at the bottom of the screen which displays error or success messages after a query is executed + tvmessage =new TextView(AndroidDatabaseManager.this); + + tvmessage.setText("Error Messages will be displayed here"); + String Query = "SELECT name _id FROM sqlite_master WHERE type ='table'"; + tvmessage.setTextSize(18); + mainLayout.addView(tvmessage); + + final Button customQuery = new Button(AndroidDatabaseManager.this); + customQuery.setText("Custom Query"); + customQuery.setBackgroundColor(Color.parseColor("#BAE7F6")); + mainLayout.addView(customQuery); + customQuery.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + //set drop down to custom Query + indexInfo.isCustomQuery=true; + secondrow.setVisibility(View.GONE); + spinnertable.setVisibility(View.GONE); + help.setVisibility(View.GONE); + customquerytext.setVisibility(View.VISIBLE); + submitQuery.setVisibility(View.VISIBLE); + select_table.setSelection(0); + customQuery.setVisibility(View.GONE); + } + }); + + + //when user enter a custom query in text view and clicks on submit query button + //display results in tablelayout + submitQuery.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + + tableLayout.removeAllViews(); + customQuery.setVisibility(View.GONE); + + ArrayList alc2; + String Query10=customquerytext.getText().toString(); + Log.d("query",Query10); + //pass the query to getdata method and get results + alc2 = dbm.getData(Query10); + final Cursor c4=alc2.get(0); + Cursor Message2 =alc2.get(1); + Message2.moveToLast(); + + //if the query returns results display the results in table layout + if(Message2.getString(0).equalsIgnoreCase("Success")) + { + + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + if(c4!=null){ + tvmessage.setText("Queru Executed successfully.Number of rows returned :"+c4.getCount()); + if(c4.getCount()>0) + { + indexInfo.maincursor=c4; + refreshTable(1); + } + }else{ + tvmessage.setText("Queru Executed successfully"); + refreshTable(1); + } + + } + else + { + //if there is any error we displayed the error message at the bottom of the screen + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+Message2.getString(0)); + + } + } + }); + //layout parameters for each row in the table + tableRowParams = new TableRow.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT); + tableRowParams.setMargins(0, 0, 2, 0); + + // a query which returns a cursor with the list of tables in the database.We use this cursor to populate spinner in the first row + alc = dbm.getData(Query); + + //the first cursor has reults of the query + final Cursor c=alc.get(0); + + //the second cursor has error messages + Cursor Message =alc.get(1); + + Message.moveToLast(); + String msg = Message.getString(0); + Log.d("Message from sql = ",msg); + + ArrayList tablenames = new ArrayList(); + + if(c!=null) + { + + c.moveToFirst(); + tablenames.add("click here"); + do{ + //add names of the table to tablenames array list + tablenames.add(c.getString(0)); + }while(c.moveToNext()); + } + //an array adapter with above created arraylist + ArrayAdapter tablenamesadapter = new ArrayAdapter(AndroidDatabaseManager.this, + android.R.layout.simple_spinner_item, tablenames) { + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + TextView adap =(TextView)v; + adap.setTextSize(20); + + return adap; + } + + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + View v =super.getDropDownView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + + return v; + } + }; + + tablenamesadapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + if(tablenamesadapter!=null) + { + //set the adpater to select_table spinner + select_table.setAdapter(tablenamesadapter); + } + + // when a table names is selecte display the table contents + select_table.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, + View view, int pos, long id) { + if(pos==0&&!indexInfo.isCustomQuery) + { + secondrow.setVisibility(View.GONE); + hsv.setVisibility(View.GONE); + thirdrow.setVisibility(View.GONE); + spinnertable.setVisibility(View.GONE); + help.setVisibility(View.GONE); + tvmessage.setVisibility(View.GONE); + customquerytext.setVisibility(View.GONE); + submitQuery.setVisibility(View.GONE); + customQuery.setVisibility(View.GONE); + } + if(pos!=0){ + secondrow.setVisibility(View.VISIBLE); + spinnertable.setVisibility(View.VISIBLE); + help.setVisibility(View.VISIBLE); + customquerytext.setVisibility(View.GONE); + submitQuery.setVisibility(View.GONE); + customQuery.setVisibility(View.VISIBLE); + hsv.setVisibility(View.VISIBLE); + + tvmessage.setVisibility(View.VISIBLE); + + thirdrow.setVisibility(View.VISIBLE); + c.moveToPosition(pos-1); + indexInfo.cursorpostion=pos-1; + //displaying the content of the table which is selected in the select_table spinner + Log.d("selected table name is",""+c.getString(0)); + indexInfo.table_name=c.getString(0); + tvmessage.setText("Error Messages will be displayed here"); + tvmessage.setBackgroundColor(Color.WHITE); + + //removes any data if present in the table layout + tableLayout.removeAllViews(); + ArrayList spinnertablevalues = new ArrayList(); + spinnertablevalues.add("Click here to change this table"); + spinnertablevalues.add("Add row to this table"); + spinnertablevalues.add("Delete this table"); + spinnertablevalues.add("Drop this table"); + ArrayAdapter spinnerArrayAdapter = new ArrayAdapter(getApplicationContext(), android.R.layout.simple_spinner_dropdown_item, spinnertablevalues); + spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_item); + + // a array adapter which add values to the spinner which helps in user making changes to the table + ArrayAdapter adapter = new ArrayAdapter(AndroidDatabaseManager.this, + android.R.layout.simple_spinner_item, spinnertablevalues) { + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + TextView adap =(TextView)v; + adap.setTextSize(20); + + return adap; + } + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + View v =super.getDropDownView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + + return v; + } + }; + + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnertable.setAdapter(adapter); + String Query2 ="select * from "+c.getString(0); + Log.d("",""+Query2); + + //getting contents of the table which user selected from the select_table spinner + ArrayList alc2=dbm.getData(Query2); + final Cursor c2=alc2.get(0); + //saving cursor to the static indexinfo class which can be resued by the other functions + indexInfo.maincursor=c2; + + // if the cursor returned form the database is not null we display the data in table layout + if(c2!=null) + { + int counts = c2.getCount(); + indexInfo.isEmpty=false; + Log.d("counts",""+counts); + tv.setText(""+counts); + + + //the spinnertable has the 3 items to drop , delete , add row to the table selected by the user + //here we handle the 3 operations. + spinnertable.setOnItemSelectedListener((new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parentView, View selectedItemView, int position, long id) { + + + ((TextView)parentView.getChildAt(0)).setTextColor(Color.rgb(0,0,0)); + //when user selects to drop the table the below code in if block will be executed + if(spinnertable.getSelectedItem().toString().equals("Drop this table")) + { + // an alert dialog to confirm user selection + runOnUiThread(new Runnable() { + @Override + public void run() { + if(!isFinishing()){ + + new AlertDialog.Builder(AndroidDatabaseManager.this) + .setTitle("Are you sure ?") + .setMessage("Pressing yes will remove "+indexInfo.table_name+" table from database") + .setPositiveButton("yes", + new DialogInterface.OnClickListener() { + // when user confirms by clicking on yes we drop the table by executing drop table query + public void onClick(DialogInterface dialog, int which) { + + String Query6 = "Drop table "+indexInfo.table_name; + ArrayList aldropt=dbm.getData(Query6); + Cursor tempc=aldropt.get(1); + tempc.moveToLast(); + Log.d("Drop table Mesage",tempc.getString(0)); + if(tempc.getString(0).equalsIgnoreCase("Success")) + { + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + tvmessage.setText(indexInfo.table_name+"Dropped successfully"); + refreshactivity(); + } + else + { + //if there is any error we displayd the error message at the bottom of the screen + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+tempc.getString(0)); + spinnertable.setSelection(0); + } + }}) + .setNegativeButton("No", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + spinnertable.setSelection(0); + } + }) + .create().show(); + } + } + }); + + } + //when user selects to drop the table the below code in if block will be executed + if(spinnertable.getSelectedItem().toString().equals("Delete this table")) + { // an alert dialog to confirm user selection + runOnUiThread(new Runnable() { + @Override + public void run() { + if(!isFinishing()){ + + new AlertDialog.Builder(AndroidDatabaseManager.this) + .setTitle("Are you sure?") + .setMessage("Clicking on yes will delete all the contents of "+indexInfo.table_name+" table from database") + .setPositiveButton("yes", + new DialogInterface.OnClickListener() { + + // when user confirms by clicking on yes we drop the table by executing delete table query + public void onClick(DialogInterface dialog, int which) { + String Query7 = "Delete from "+indexInfo.table_name; + Log.d("delete table query",Query7); + ArrayList aldeletet=dbm.getData(Query7); + Cursor tempc=aldeletet.get(1); + tempc.moveToLast(); + Log.d("Delete table Mesage",tempc.getString(0)); + if(tempc.getString(0).equalsIgnoreCase("Success")) + { + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + tvmessage.setText(indexInfo.table_name+" table content deleted successfully"); + indexInfo.isEmpty=true; + refreshTable(0); + } + else + { + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+tempc.getString(0)); + spinnertable.setSelection(0); + } + }}) + .setNegativeButton("No", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + spinnertable.setSelection(0); + } + }) + .create().show(); + } + } + }); + + } + + //when user selects to add row to the table the below code in if block will be executed + if(spinnertable.getSelectedItem().toString().equals("Add row to this table")) + { + //we create a layout which has textviews with column names of the table and edittexts where + //user can enter value which will be inserted into the datbase. + final LinkedList addnewrownames = new LinkedList(); + final LinkedList addnewrowvalues = new LinkedList(); + final ScrollView addrowsv =new ScrollView(AndroidDatabaseManager.this); + Cursor c4 = indexInfo.maincursor; + if(indexInfo.isEmpty) + { + getcolumnnames(); + for(int i=0;i altc=dbm.getData(Query4); + Cursor tempc=altc.get(1); + tempc.moveToLast(); + Log.d("Add New Row",tempc.getString(0)); + if(tempc.getString(0).equalsIgnoreCase("Success")) + { + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + tvmessage.setText("New Row added succesfully to "+indexInfo.table_name); + refreshTable(0); + } + else + { + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+tempc.getString(0)); + spinnertable.setSelection(0); + } + + } + }) + .setNegativeButton("close", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + spinnertable.setSelection(0); + } + }) + .create().show(); + } + } + }); + } + } + public void onNothingSelected(AdapterView arg0) { } + })); + + //display the first row of the table with column names of the table selected by the user + TableRow tableheader = new TableRow(getApplicationContext()); + + tableheader.setBackgroundColor(Color.BLACK); + tableheader.setPadding(0, 2, 0, 2); + for(int k=0;k arg0) { + } + }); + } + + //get columnnames of the empty tables and save them in a array list + public void getcolumnnames() + { + ArrayList alc3=dbm.getData("PRAGMA table_info("+indexInfo.table_name+")"); + Cursor c5=alc3.get(0); + indexInfo.isEmpty=true; + if(c5!=null) + { + indexInfo.isEmpty=true; + + ArrayList emptytablecolumnnames= new ArrayList(); + c5.moveToFirst(); + do + { + emptytablecolumnnames.add(c5.getString(1)); + }while(c5.moveToNext()); + indexInfo.emptytablecolumnnames=emptytablecolumnnames; + } + + + + } + //displays alert dialog from which use can update or delete a row + public void updateDeletePopup(int row) + { + Cursor c2=indexInfo.maincursor; + // a spinner which gives options to update or delete the row which user has selected + ArrayList spinnerArray = new ArrayList(); + spinnerArray.add("Click Here to Change this row"); + spinnerArray.add("Update this row"); + spinnerArray.add("Delete this row"); + + //create a layout with text values which has the column names and + //edit texts which has the values of the row which user has selected + final ArrayList value_string = indexInfo.value_string; + final LinkedList columnames = new LinkedList(); + final LinkedList columvalues = new LinkedList(); + + for(int i=0;i crudadapter = new ArrayAdapter(AndroidDatabaseManager.this, + android.R.layout.simple_spinner_item, spinnerArray) { + + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + TextView adap =(TextView)v; + adap.setTextSize(20); + + return adap; + } + + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + View v =super.getDropDownView(position, convertView, parent); + + v.setBackgroundColor(Color.WHITE); + + return v; + } + }; + + + crudadapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + + crud_dropdown.setAdapter(crudadapter); + lcrud.setId(299); + lcrud.addView(crud_dropdown, paramcrudtext); + + RelativeLayout.LayoutParams rlcrudparam = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + rlcrudparam.addRule(RelativeLayout.BELOW,lastrid); + + lp.addView(lcrud, rlcrudparam); + for(int i=0;i aluc=dbm.getData(Query3); + Cursor tempc=aluc.get(1); + tempc.moveToLast(); + Log.d("Update Mesage",tempc.getString(0)); + + if(tempc.getString(0).equalsIgnoreCase("Success")) + { + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + tvmessage.setText(indexInfo.table_name+" table Updated Successfully"); + refreshTable(0); + } + else + { + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+tempc.getString(0)); + } + } + //it he spinner value is delete this row get the values from + //edit text fields generate a delete query and execute it + + if(spinner_value.equalsIgnoreCase("Delete this row")) + { + + indexInfo.index = 10; + String Query5="DELETE FROM "+indexInfo.table_name+" WHERE "; + + for(int i=0;i aldc=dbm.getData(Query5); + Cursor tempc=aldc.get(1); + tempc.moveToLast(); + Log.d("Update Mesage",tempc.getString(0)); + + if(tempc.getString(0).equalsIgnoreCase("Success")) + { + tvmessage.setBackgroundColor(Color.parseColor("#2ecc71")); + tvmessage.setText("Row deleted from "+indexInfo.table_name+" table"); + refreshTable(0); + } + else + { + tvmessage.setBackgroundColor(Color.parseColor("#e74c3c")); + tvmessage.setText("Error:"+tempc.getString(0)); + } + } + } + + }) + .setNegativeButton("close", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + + } + }) + .create().show(); + } + } + }); + } + + public void refreshactivity() + { + + finish(); + startActivity(getIntent()); + } + + public void refreshTable(int d ) + { + Cursor c3=null; + tableLayout.removeAllViews(); + if(d==0) + { + String Query8 = "select * from "+indexInfo.table_name; + ArrayList alc3=dbm.getData(Query8); + c3=alc3.get(0); + //saving cursor to the static indexinfo class which can be resued by the other functions + indexInfo.maincursor=c3; + } + if(d==1) + { + c3=indexInfo.maincursor; + } + // if the cursor returened form tha database is not null we display the data in table layout + if(c3!=null) + { + int counts = c3.getCount(); + + Log.d("counts",""+counts); + tv.setText(""+counts); + TableRow tableheader = new TableRow(getApplicationContext()); + + tableheader.setBackgroundColor(Color.BLACK); + tableheader.setPadding(0, 2, 0, 2); + for(int k=0;k value_string = new ArrayList(); + for(int i=0;i=indexInfo.numberofpages) + { + Toast.makeText(getApplicationContext(), "This is the last page", Toast.LENGTH_LONG).show(); + } + else + { + indexInfo.currentpage=indexInfo.currentpage+1; + boolean decider=true; + + + for(int i=1;i arg0, View arg1, int arg2, long arg3) { + // TODO Auto-generated method stub + + } + +} diff --git a/app/src/main/java/com/github/bukaanalytics/MainActivity.java b/app/src/main/java/com/github/bukaanalytics/MainActivity.java index 12ccd82..ebeb07d 100644 --- a/app/src/main/java/com/github/bukaanalytics/MainActivity.java +++ b/app/src/main/java/com/github/bukaanalytics/MainActivity.java @@ -158,7 +158,7 @@ private void scheduleAlarm () { // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis, - AlarmManager.INTERVAL_HALF_HOUR, pIntent); + AlarmManager.INTERVAL_DAY, pIntent); } public void cancelAlarm() { diff --git a/app/src/main/java/com/github/bukaanalytics/common/BukaAnalyticsIntentService.java b/app/src/main/java/com/github/bukaanalytics/common/BukaAnalyticsIntentService.java index 2f6e4c8..5e64833 100644 --- a/app/src/main/java/com/github/bukaanalytics/common/BukaAnalyticsIntentService.java +++ b/app/src/main/java/com/github/bukaanalytics/common/BukaAnalyticsIntentService.java @@ -6,11 +6,11 @@ import android.support.v4.content.WakefulBroadcastReceiver; import com.github.bukaanalytics.common.model.BukaAnalyticsSqliteOpenHelper; -import com.github.bukaanalytics.common.model.Post; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import com.koushikdutta.async.future.FutureCallback; -import com.koushikdutta.ion.Ion; +import com.github.bukaanalytics.common.model.Product; +import com.github.bukaanalytics.common.model.Stat; + +import java.util.ArrayList; +import java.util.List; /** * Created by fawwaz.muhammad on 05/05/17. @@ -21,41 +21,47 @@ public BukaAnalyticsIntentService() { super("BukaAnalytics"); } - private void findTopStories() { - Ion.with(getApplicationContext()) - .load("https://hacker-news.firebaseio.com/v0/topstories.json") - .asJsonArray() - .setCallback(new FutureCallback() { - @Override - public void onCompleted(Exception e, JsonArray result) { - String topStoryId = result.get(0).getAsString(); - getStory(topStoryId); - } - }); - } + private void fetchData() { + final int activeUserId = 9925909; //TODO get active userId from login + final HTTPRequestHelper HTTPHelper = new HTTPRequestHelper(); + final BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(getApplicationContext()); - private void getStory(String storyId){ - Ion.with(getApplicationContext()) - .load("https://hacker-news.firebaseio.com/v0/item/"+storyId+".json") - .asJsonObject() - .setCallback(new FutureCallback() { - @Override - public void onCompleted(Exception e, JsonObject result) { - String title = result.get("title").getAsString(); - - Post hnpost = new Post(); - hnpost.text = title; + //Get product list from cloud database + HTTPHelper.fetchProducts(activeUserId, getApplicationContext(), new HTTPRequestHelper.ProductsCallback() { + @Override + public void onCompleted(Exception e, List productsResult) { + if((productsResult != null) && (productsResult.size() > 0)) { + //Insert products into local database + db.addProducts(productsResult); + } - BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(getApplicationContext()); - db.addPost(hnpost); + ArrayList productIds= new ArrayList<>(); + //get all productIds + for (int i = 0; i < productsResult.size(); i++) { + productIds.add(productsResult.get(i).id); + } + final String[] productIdsArray = productIds.toArray(new String[0]); + //Get stat list from cloud database, for all product ids + HTTPHelper.fetchStats(productIdsArray, getApplicationContext(), new HTTPRequestHelper.StatsCallback() { + @Override + public void onCompleted(Exception e, List statsResult) { + if((statsResult != null) && (statsResult.size() > 0)) { + //Current solution for 'Stats' table without unique column + db.deleteStats(productIdsArray); + //Insert stats into local database + db.addStats(statsResult); + } } }); + } + }); + } @Override protected void onHandleIntent(@Nullable Intent intent) { - findTopStories(); + fetchData(); WakefulBroadcastReceiver.completeWakefulIntent(intent); } } diff --git a/app/src/main/java/com/github/bukaanalytics/common/HTTPRequestHelper.java b/app/src/main/java/com/github/bukaanalytics/common/HTTPRequestHelper.java new file mode 100644 index 0000000..86facb9 --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/common/HTTPRequestHelper.java @@ -0,0 +1,172 @@ +package com.github.bukaanalytics.common; + +import android.content.Context; +import android.util.Base64; +import android.util.Log; + +import com.github.bukaanalytics.common.model.BukaAnalyticsSqliteOpenHelper; +import com.github.bukaanalytics.common.model.Product; +import com.github.bukaanalytics.common.model.Stat; +import com.github.bukaanalytics.common.model.Token; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.koushikdutta.async.future.FutureCallback; +import com.koushikdutta.ion.Ion; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by touchtenmac-19 on 5/11/17. + */ + +public class HTTPRequestHelper { + + private String TAG = "HTTPRequestHelper"; + private static final String MLAB_API_KEY = "8wDpSrJX4XU4tX_ff56Y39I98Tnn4xb0"; + + public interface JSONArrayCallback { + void onCompleted(Exception e, JsonArray result); + } + + public interface JSONObjectCallback { + void onCompleted(Exception e, JsonObject result); + } + + public interface ProductsCallback { + void onCompleted(Exception e, List productsResult); + } + + public interface StatsCallback { + void onCompleted(Exception e, List statsResult); + } + + private JSONArrayCallback jsonArrayCallback; + private JSONObjectCallback jsonObjectCallback; + private ProductsCallback productsCallback; + private StatsCallback statsCallback; + + public HTTPRequestHelper() { + this.jsonArrayCallback = null; + this.productsCallback = null; + this.statsCallback = null; + } + + public void getAsJSONArray(String URL, final Context context, final JSONArrayCallback callback) { + this.jsonArrayCallback = callback; + + Ion.with(context) + .load(URL) + .asJsonArray() + .setCallback(new FutureCallback() { + @Override + public void onCompleted(Exception e, JsonArray result) { + Log.d(TAG, "onCompleted: "+result); + jsonArrayCallback.onCompleted(e, result); + } + }); + + } + + public void getAsJSONObject(String URL, final Context context, final JSONObjectCallback callback) { + this.jsonObjectCallback = callback; + + // ini perlu di refactor + final BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(context.getApplicationContext()); + Token tokenData = db.getTokenData(); + String appended = String.valueOf(tokenData.user_id) + ":" + tokenData.token; + String auth= Base64.encodeToString(appended.getBytes(), Base64.NO_WRAP); + + Ion.with(context) + .load(URL) + .setHeader("Authorization", "Basic " + auth) + .asJsonObject() + .setCallback(new FutureCallback() { + @Override + public void onCompleted(Exception e, JsonObject result) { + // next perlu cek http status code nya 200 atau engga + if (e == null) { + Log.d(TAG, "onCompleted: "+result); + jsonObjectCallback.onCompleted(e, result); + } + } + }); + + } + + public void fetchProducts(int userId, final Context context, ProductsCallback callback) { + this.productsCallback = callback; + + getAsJSONArray("https://api.mlab.com/api/1/databases/bukaanalytics/collections/products?q={'seller_id':" + userId + "}&apiKey=" + MLAB_API_KEY, + context.getApplicationContext(), new JSONArrayCallback() { + @Override + public void onCompleted(Exception e, JsonArray result) { + ArrayList productList = new ArrayList<>(); + if(result != null) { + for (int i = 0; i < result.size(); i++) { + JsonObject product = result.get(i).getAsJsonObject(); + String id = product.get("product_id").getAsString(); + String name = product.get("name").getAsString(); + int price = product.get("price").getAsInt(); + int sellerId = product.get("seller_id").getAsInt(); + Product newProduct = new Product(id, name, price, sellerId); + + productList.add(newProduct); + } + } + productsCallback.onCompleted(e, productList); + } + }); + } + + public void fetchStats(String[] productIds, final Context context, StatsCallback callback) { + this.statsCallback = callback; + + StringBuilder sb = new StringBuilder(); + if(productIds.length > 0){ + sb.append("["); + for (int i = 0; i < productIds.length-1; i++) { + sb.append("\"" + productIds[i] + "\","); + } + sb.append("\"" + productIds[productIds.length-1] + "\"]"); + } + else{ + sb.append("[]"); + } + + getAsJSONArray("https://api.mlab.com/api/1/databases/bukaanalytics/collections/stats?q={'product_id':{'$in':" + sb.toString() + "}}&apiKey=" + MLAB_API_KEY, + context.getApplicationContext(), new JSONArrayCallback() { + @Override + public void onCompleted(Exception e, JsonArray result) { + ArrayList statList = new ArrayList<>(); + if(result != null) { + for (int i = 0; i < result.size(); i++) { + JsonObject stat = result.get(i).getAsJsonObject(); + String date = stat.get("date").getAsString(); + int dateEpoch = stat.get("date_epoch").getAsInt(); + String dayName = stat.get("day_name").getAsString(); + String productId = stat.get("product_id").getAsString(); + int viewCount = stat.get("view_count").getAsInt(); + int viewTotal = stat.get("view_total").getAsInt(); + int soldCount = stat.get("sold_count").getAsInt(); + int soldTotal = stat.get("sold_total").getAsInt(); + int interestCount = stat.get("interest_count").getAsInt(); + int interestTotal = stat.get("interest_total").getAsInt(); + + int marketViewCount = stat.get("market_view_count").getAsInt(); + int marketViewTotal = stat.get("market_view_total").getAsInt(); + int marketSoldCount = stat.get("market_sold_count").getAsInt(); + int marketSoldTotal = stat.get("market_sold_total").getAsInt(); + int marketInterestCount = stat.get("market_interest_count").getAsInt(); + int marketInterestTotal = stat.get("market_interest_total").getAsInt(); + + Stat newStat = new Stat(date, dateEpoch,dayName, productId, viewCount, viewTotal, soldCount, soldTotal, interestCount, interestTotal, marketViewCount, marketViewTotal, marketSoldCount, marketSoldTotal, marketInterestCount, marketInterestTotal); + + statList.add(newStat); + } + } + statsCallback.onCompleted(e, statList); + } + }); + } +} diff --git a/app/src/main/java/com/github/bukaanalytics/common/model/BukaAnalyticsSqliteOpenHelper.java b/app/src/main/java/com/github/bukaanalytics/common/model/BukaAnalyticsSqliteOpenHelper.java index 1d73893..b7ed7b7 100644 --- a/app/src/main/java/com/github/bukaanalytics/common/model/BukaAnalyticsSqliteOpenHelper.java +++ b/app/src/main/java/com/github/bukaanalytics/common/model/BukaAnalyticsSqliteOpenHelper.java @@ -3,10 +3,17 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; +import android.database.MatrixCursor; +import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.text.TextUtils; import android.util.Log; +import com.github.bukaanalytics.common.HTTPRequestHelper; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + import java.util.ArrayList; import java.util.List; @@ -16,41 +23,124 @@ public class BukaAnalyticsSqliteOpenHelper extends SQLiteOpenHelper { // debugging only - private String TAG = "BukaAnalyticsSqliteOpenHelper"; + private String TAG = "BASqliteOpenHelper"; // Database Info private static final String DATABASE_NAME = "baDatabase"; private static final int DATABASE_VERSION = 1; // Table Names - private static final String TABLE_POSTS = "posts"; + private static final String TABLE_USERS = "users"; + private static final String TABLE_PRODUCTS = "products"; + private static final String TABLE_STATS = "stats"; + private static final String TABLE_TOKENS = "tokens"; + + //User Table Columns + private static final String KEY_USER_ID = "seller_id"; + private static final String KEY_USER_NAME = "name"; + + // Product Table Columns + private static final String KEY_PRODUCT_ID = "product_id"; + private static final String KEY_PRODUCT_NAME = "name"; + private static final String KEY_PRODUCT_PRICE = "price"; + private static final String KEY_PRODUCT_SELLERID = "seller_id"; + + // Stat Table Columns + private static final String KEY_STAT_ID = "id"; + private static final String KEY_STAT_DATE = "date"; + private static final String KEY_STAT_DATEEPOCH = "date_epoch"; + private static final String KEY_STAT_DAYNAME = "day_name"; + private static final String KEY_STAT_PRODUCTID = "product_id"; + private static final String KEY_STAT_VIEWCOUNT = "view_count"; + private static final String KEY_STAT_VIEWTOTAL = "view_total"; + private static final String KEY_STAT_SOLDCOUNT = "sold_count"; + private static final String KEY_STAT_SOLDTOTAL = "sold_total"; + private static final String KEY_STAT_INTERESTCOUNT = "interest_count"; + private static final String KEY_STAT_INTERESTTOTAL = "interest_total"; - // Post Table Columns - private static final String KEY_POST_ID = "id"; - private static final String KEY_POST_TEXT = "text"; + private static final String KEY_STAT_MARKETVIEWCOUNT = "market_view_count"; + private static final String KEY_STAT_MARKETVIEWTOTAL = "market_view_total"; + private static final String KEY_STAT_MARKETSOLDCOUNT = "market_sold_count"; + private static final String KEY_STAT_MARKETSOLDTOTAL = "market_sold_total"; + private static final String KEY_STAT_MARKETINTERESTCOUNT = "market_interest_count"; + private static final String KEY_STAT_MARKETINTERESTTOTAL = "market_interest_total"; + + // Tokens Table Columns + private static final String KEY_TOKEN_ID = "id"; + private static final String KEY_TOKEN_USER_ID = "user_id"; + private static final String KEY_TOKEN_TOKEN = "token"; + + private static final String MLAB_API_KEY = "8wDpSrJX4XU4tX_ff56Y39I98Tnn4xb0"; // Instance private static BukaAnalyticsSqliteOpenHelper sInstance; - private BukaAnalyticsSqliteOpenHelper(Context context) { + public BukaAnalyticsSqliteOpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { - String CREATE_POSTS_TABLE = "CREATE TABLE " + TABLE_POSTS + + Log.d(TAG, "onCreate: mau exec CREATE TABLE"); + + String CREATE_USERS_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_USERS + + "(" + + KEY_USER_ID + " INTEGER PRIMARY KEY," + // Define a primary key + KEY_USER_NAME + " TEXT" + + ")"; + + String CREATE_PRODUCTS_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_PRODUCTS + + "(" + + KEY_PRODUCT_ID + " TEXT PRIMARY KEY," + + KEY_PRODUCT_NAME + " TEXT," + + KEY_PRODUCT_PRICE + " INTEGER," + + KEY_PRODUCT_SELLERID + " INTEGER," + + "FOREIGN KEY("+KEY_PRODUCT_SELLERID+") REFERENCES "+TABLE_USERS+"("+KEY_USER_ID+")"+ + ")"; + + String CREATE_STAT_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_STATS + "(" + - KEY_POST_ID + " INTEGER PRIMARY KEY," + // Define a primary key - KEY_POST_TEXT + " TEXT" + + KEY_STAT_ID + " INTEGER PRIMARY KEY," + + KEY_STAT_DATE + " TEXT," + + KEY_STAT_DATEEPOCH + " INTEGER," + + KEY_STAT_DAYNAME + " TEXT," + + KEY_STAT_PRODUCTID + " TEXT," + + KEY_STAT_VIEWCOUNT + " INTEGER," + + KEY_STAT_VIEWTOTAL + " INTEGER," + + KEY_STAT_SOLDCOUNT + " INTEGER," + + KEY_STAT_SOLDTOTAL + " INTEGER," + + KEY_STAT_INTERESTCOUNT + " INTEGER," + + KEY_STAT_INTERESTTOTAL + " INTEGER," + + KEY_STAT_MARKETVIEWCOUNT + " INTEGER," + + KEY_STAT_MARKETVIEWTOTAL + " INTEGER," + + KEY_STAT_MARKETSOLDCOUNT + " INTEGER," + + KEY_STAT_MARKETSOLDTOTAL + " INTEGER," + + KEY_STAT_MARKETINTERESTCOUNT + " INTEGER," + + KEY_STAT_MARKETINTERESTTOTAL + " INTEGER," + + "FOREIGN KEY("+KEY_STAT_PRODUCTID+") REFERENCES "+TABLE_PRODUCTS+"("+KEY_PRODUCT_ID+")"+ ")"; - db.execSQL(CREATE_POSTS_TABLE); + + String CREATE_TOKENS_TABLE = "CREATE TABLE IF NOT EXISTS " + TABLE_TOKENS + + "(" + + KEY_TOKEN_ID + " INTEGER PRIMARY KEY," + + KEY_TOKEN_USER_ID + " INTEGER," + + KEY_TOKEN_TOKEN + " TEXT" + + ")"; + + db.execSQL(CREATE_USERS_TABLE); + db.execSQL(CREATE_PRODUCTS_TABLE); + db.execSQL(CREATE_STAT_TABLE); + db.execSQL(CREATE_TOKENS_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion != newVersion) { // Simplest implementation is to drop all old tables and recreate them - db.execSQL("DROP TABLE IF EXISTS " + TABLE_POSTS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_USERS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PRODUCTS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATS); + db.execSQL("DROP TABLE IF EXISTS " + TABLE_TOKENS); onCreate(db); } } @@ -65,57 +155,311 @@ public static synchronized BukaAnalyticsSqliteOpenHelper getInstance(Context con return sInstance; } - public void addPost(Post post){ + public void addStats(List stats){ + SQLiteDatabase db = getWritableDatabase(); + + // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures + // consistency of the database. + db.beginTransaction(); + try { + for (int i = 0; i < stats.size(); i++) { + Stat stat = stats.get(i); + ContentValues values = new ContentValues(); + values.put(KEY_STAT_DATE, stat.date); + values.put(KEY_STAT_DATEEPOCH, stat.dateEpoch); + values.put(KEY_STAT_DAYNAME, stat.dayName); + values.put(KEY_STAT_PRODUCTID, stat.productId); + values.put(KEY_STAT_VIEWCOUNT, stat.viewCount); + values.put(KEY_STAT_VIEWTOTAL, stat.totalViewCount); + values.put(KEY_STAT_SOLDCOUNT, stat.soldCount); + values.put(KEY_STAT_SOLDTOTAL, stat.totalSoldCount); + values.put(KEY_STAT_INTERESTCOUNT, stat.interestCount); + values.put(KEY_STAT_INTERESTTOTAL, stat.totalInterestCount); + + values.put(KEY_STAT_MARKETVIEWCOUNT, stat.marketViewCount); + values.put(KEY_STAT_MARKETVIEWTOTAL, stat.marketTotalViewCount); + values.put(KEY_STAT_MARKETSOLDCOUNT, stat.marketSoldCount); + values.put(KEY_STAT_MARKETSOLDTOTAL, stat.marketTotalSoldCount); + values.put(KEY_STAT_MARKETINTERESTCOUNT, stat.marketInterestCount); + values.put(KEY_STAT_MARKETINTERESTTOTAL, stat.marketTotalInterestCount); + + // Notice how we haven't specified the primary key. SQLite auto increments the primary key column. + db.insertOrThrow(TABLE_STATS, null, values); + } + db.setTransactionSuccessful(); + } + catch (SQLException e) { + Log.d(TAG, "addStats(), SQLException: " + e.getMessage()); + } + catch (Exception e) { + Log.d(TAG, "addStats() Error: " + e.getMessage()); + } finally { + db.endTransaction(); + } + } + + public void addProducts(List products){ SQLiteDatabase db = getWritableDatabase(); // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures // consistency of the database. db.beginTransaction(); try { - // The user might already exist in the database (i.e. the same user created multiple posts). + for (int i = 0; i < products.size(); i++) { + Product product = products.get(i); + ContentValues values = new ContentValues(); + values.put(KEY_PRODUCT_ID, product.id); + values.put(KEY_PRODUCT_NAME, product.name); + values.put(KEY_PRODUCT_PRICE, product.price); + values.put(KEY_PRODUCT_SELLERID, product.sellerId); + + // Notice how we haven't specified the primary key. SQLite auto increments the primary key column. + db.insertOrThrow(TABLE_PRODUCTS, null, values); + } + db.setTransactionSuccessful(); + } + catch (SQLException e) { + Log.d(TAG, "addProducts(), SQLException: " + e.getMessage()); + } + catch (Exception e) { + Log.d(TAG, "addProducts() Error: " + e.getMessage()); + } finally { + db.endTransaction(); + } + } + public void addUser(User user){ + SQLiteDatabase db = getWritableDatabase(); + + // It's a good idea to wrap our insert in a transaction. This helps with performance and ensures + // consistency of the database. + db.beginTransaction(); + try { ContentValues values = new ContentValues(); - values.put(KEY_POST_TEXT, post.text); + values.put(KEY_USER_ID, user.id); + values.put(KEY_USER_NAME, user.name); // Notice how we haven't specified the primary key. SQLite auto increments the primary key column. - db.insertOrThrow(TABLE_POSTS, null, values); + db.insertOrThrow(TABLE_USERS, null, values); db.setTransactionSuccessful(); - } catch (Exception e) { - Log.d(TAG, "Error while trying to add post to database"); + } + catch (SQLException e) { + Log.d(TAG, "addUser(), SQLException: " + e.getMessage()); + } + catch (Exception e) { + Log.d(TAG, "addUser Error: " + e.getMessage()); } finally { db.endTransaction(); } } - public List getAllPosts() { - List posts = new ArrayList<>(); + public List getProducts(int userId) { + List products = new ArrayList<>(); // SELECT * FROM POSTS // LEFT OUTER JOIN USERS // ON POSTS.KEY_POST_USER_ID_FK = USERS.KEY_USER_ID - String POSTS_SELECT_QUERY = - String.format("SELECT * FROM %s ", TABLE_POSTS); + String PRODUCTS_SELECT_QUERY = + String.format("SELECT * FROM %s WHERE %s = %d", TABLE_PRODUCTS, KEY_PRODUCT_SELLERID, userId); // "getReadableDatabase()" and "getWriteableDatabase()" return the same object (except under low // disk space scenarios) SQLiteDatabase db = getReadableDatabase(); - Cursor cursor = db.rawQuery(POSTS_SELECT_QUERY, null); + Cursor cursor = db.rawQuery(PRODUCTS_SELECT_QUERY, null); try { - if (cursor.moveToFirst()) { - do { - Post newPost = new Post(); - newPost.text = cursor.getString(cursor.getColumnIndex(KEY_POST_TEXT)); - posts.add(newPost); - } while(cursor.moveToNext()); + if(cursor != null && cursor.getCount() > 0) { + if (cursor.moveToFirst()) { + do { + Product newProduct = new Product(cursor.getString(cursor.getColumnIndex(KEY_PRODUCT_ID)), + cursor.getString(cursor.getColumnIndex(KEY_PRODUCT_NAME)), + cursor.getInt(cursor.getColumnIndex(KEY_PRODUCT_PRICE)), + cursor.getInt(cursor.getColumnIndex(KEY_PRODUCT_SELLERID))); + products.add(newProduct); + } while(cursor.moveToNext()); + } } } catch (Exception e) { - Log.d(TAG, "Error while trying to get posts from database"); + Log.d(TAG, "getProducts() Error: " + e.getMessage()); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } - return posts; + return products; + } + + public Token getTokenData() { + Token tokenData = new Token(0,0,""); + String GET_ACTIVE_TOKEN = String.format("SELECT * FROM %s ORDER BY %s DESC LIMIT 1", TABLE_TOKENS, KEY_TOKEN_ID); + + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery(GET_ACTIVE_TOKEN, null); + try{ + if (cursor != null && cursor.getCount() > 0) { + if (cursor.moveToFirst()) { + tokenData = new Token(cursor.getInt(cursor.getColumnIndex(KEY_TOKEN_ID)), + cursor.getInt(cursor.getColumnIndex(KEY_TOKEN_USER_ID)), + cursor.getString(cursor.getColumnIndex(KEY_TOKEN_TOKEN))); + } + } + } catch (Exception e) { + Log.d(TAG, "getTokenData() Error: " + e.getMessage()); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + + return tokenData; + } + + public List getStats(String productId) { + List stats = new ArrayList<>(); + + String STATS_SELECT_QUERY = + String.format("SELECT * FROM %s WHERE %s = '%s'", TABLE_STATS, KEY_STAT_PRODUCTID, productId); + + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.rawQuery(STATS_SELECT_QUERY, null); + try { + if(cursor != null && cursor.getCount() > 0) { + if (cursor.moveToFirst()) { + do { + Stat newStat = new Stat(cursor.getString(cursor.getColumnIndex(KEY_STAT_DATE)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_DATEEPOCH)), + cursor.getString(cursor.getColumnIndex(KEY_STAT_DAYNAME)), + cursor.getString(cursor.getColumnIndex(KEY_STAT_PRODUCTID)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_VIEWCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_VIEWTOTAL)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_SOLDCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_SOLDTOTAL)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_INTERESTCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_INTERESTTOTAL)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETVIEWCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETVIEWTOTAL)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETSOLDCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETSOLDTOTAL)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETINTERESTCOUNT)), + cursor.getInt(cursor.getColumnIndex(KEY_STAT_MARKETINTERESTTOTAL))); + stats.add(newStat); + } while(cursor.moveToNext()); + } + } + } catch (Exception e) { + Log.d(TAG, "getStats() Error: " + e.getMessage()); + } finally { + if (cursor != null && !cursor.isClosed()) { + cursor.close(); + } + } + return stats; + } + + public void deleteStats(String[] productIds) { + if(productIds.length > 0){ + SQLiteDatabase db = getWritableDatabase(); + + db.beginTransaction(); + try { + String joined = TextUtils.join("\",\"",productIds); + String args = "\"" + joined + "\""; + String query = String.format("DELETE FROM %s WHERE %s IN (%s)", TABLE_STATS, KEY_STAT_PRODUCTID, args); + db.execSQL(query); + + db.setTransactionSuccessful(); + } + catch (SQLException e) { + Log.d(TAG, "deleteStats(), SQLException: " + e.getMessage()); + } + catch (Exception e) { + Log.d(TAG, "deleteStats Error: " + e.getMessage()); + } finally { + db.endTransaction(); + } + } + } + + public void fetchUsers(final Context context) { + + HTTPRequestHelper httpRequestHelper = new HTTPRequestHelper(); + httpRequestHelper.getAsJSONArray("https://api.mlab.com/api/1/databases/bukaanalytics/collections/users?apiKey=" + MLAB_API_KEY, + context.getApplicationContext(), new HTTPRequestHelper.JSONArrayCallback() { + @Override + public void onCompleted(Exception e, JsonArray result) { + BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(context); + for (int i = 0; i < result.size(); i++) { + JsonObject product = result.get(i).getAsJsonObject(); + int id = product.get("seller_id").getAsInt(); + String name = product.get("name").getAsString(); + User newUser = new User(id, name); + + db.addUser(newUser); + } + } + }); + } + + public void fetchUser(int userId, final Context context) { + + HTTPRequestHelper httpRequestHelper = new HTTPRequestHelper(); + httpRequestHelper.getAsJSONArray("https://api.mlab.com/api/1/databases/bukaanalytics/collections/users?q={'seller_id':" + userId + "}&apiKey=" + MLAB_API_KEY, + context.getApplicationContext(), new HTTPRequestHelper.JSONArrayCallback() { + @Override + public void onCompleted(Exception e, JsonArray result) { + BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(context); + + JsonObject product = result.get(0).getAsJsonObject(); + int id = product.get("seller_id").getAsInt(); + String name = product.get("name").getAsString(); + User newUser = new User(id, name); + + db.addUser(newUser); + } + }); + } + + public ArrayList getData(String Query){ + //get writable database + SQLiteDatabase sqlDB = this.getWritableDatabase(); + String[] columns = new String[] { "message" }; + //an array list of cursor to save two cursors one has results from the query + //other cursor stores error message if any errors are triggered + ArrayList alc = new ArrayList(2); + MatrixCursor Cursor2= new MatrixCursor(columns); + alc.add(null); + alc.add(null); + + try{ + String maxQuery = Query ; + //execute the query results will be save in Cursor c + Cursor c = sqlDB.rawQuery(maxQuery, null); + + //add value to cursor2 + Cursor2.addRow(new Object[] { "Success" }); + + alc.set(1,Cursor2); + if (null != c && c.getCount() > 0) { + + alc.set(0,c); + c.moveToFirst(); + + return alc ; + } + return alc; + } catch(SQLException sqlEx){ + Log.d("printing exception", sqlEx.getMessage()); + //if any exceptions are triggered save the error message to cursor an return the arraylist + Cursor2.addRow(new Object[] { ""+sqlEx.getMessage() }); + alc.set(1,Cursor2); + return alc; + } catch(Exception ex){ + Log.d("printing exception", ex.getMessage()); + + //if any exceptions are triggered save the error message to cursor an return the arraylist + Cursor2.addRow(new Object[] { ""+ex.getMessage() }); + alc.set(1,Cursor2); + return alc; + } } } diff --git a/app/src/main/java/com/github/bukaanalytics/common/model/Product.java b/app/src/main/java/com/github/bukaanalytics/common/model/Product.java new file mode 100644 index 0000000..86be47a --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/common/model/Product.java @@ -0,0 +1,19 @@ +package com.github.bukaanalytics.common.model; + +/** + * Created by touchtenmac-19 on 5/6/17. + */ + +public class Product { + public String id; + public String name; + public int price; + public int sellerId; + + public Product(String _id, String _name, int _price, int _sellerId){ + id = _id; + name = _name; + price = _price; + sellerId = _sellerId; + } +} diff --git a/app/src/main/java/com/github/bukaanalytics/common/model/Stat.java b/app/src/main/java/com/github/bukaanalytics/common/model/Stat.java new file mode 100644 index 0000000..bbcd7ca --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/common/model/Stat.java @@ -0,0 +1,43 @@ +package com.github.bukaanalytics.common.model; + +/** + * Created by touchtenmac-19 on 5/6/17. + */ + +public class Stat { + public String date; + public int dateEpoch; + public String dayName; + public String productId; + public int viewCount; + public int totalViewCount; + public int soldCount; + public int totalSoldCount; + public int interestCount; + public int totalInterestCount; + public int marketViewCount; + public int marketTotalViewCount; + public int marketSoldCount; + public int marketTotalSoldCount; + public int marketInterestCount; + public int marketTotalInterestCount; + + public Stat(String date, int dateEpoch, String dayName, String productId, int viewCount, int totalViewCount, int soldCount, int totalSoldCount, int interestCount, int totalInterestCount, int marketViewCount, int marketTotalViewCount, int marketSoldCount, int marketTotalSoldCount, int marketInterestCount, int marketTotalInterestCount) { + this.date = date; + this.dateEpoch = dateEpoch; + this.dayName = dayName; + this.productId = productId; + this.viewCount = viewCount; + this.totalViewCount = totalViewCount; + this.soldCount = soldCount; + this.totalSoldCount = totalSoldCount; + this.interestCount = interestCount; + this.totalInterestCount = totalInterestCount; + this.marketViewCount = marketViewCount; + this.marketTotalViewCount = marketTotalViewCount; + this.marketSoldCount = marketSoldCount; + this.marketTotalSoldCount = marketTotalSoldCount; + this.marketInterestCount = marketInterestCount; + this.marketTotalInterestCount = marketTotalInterestCount; + } +} diff --git a/app/src/main/java/com/github/bukaanalytics/common/model/Token.java b/app/src/main/java/com/github/bukaanalytics/common/model/Token.java new file mode 100644 index 0000000..6b161f3 --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/common/model/Token.java @@ -0,0 +1,17 @@ +package com.github.bukaanalytics.common.model; + +/** + * Created by habibridho on 5/28/17. + */ + +public class Token { + public int id; + public int user_id; + public String token; + + public Token(int _id, int _user_id, String _token) { + id = _id; + user_id = _user_id; + token = _token; + } +} diff --git a/app/src/main/java/com/github/bukaanalytics/common/model/User.java b/app/src/main/java/com/github/bukaanalytics/common/model/User.java new file mode 100644 index 0000000..3e4d523 --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/common/model/User.java @@ -0,0 +1,15 @@ +package com.github.bukaanalytics.common.model; + +/** + * Created by touchtenmac-19 on 5/11/17. + */ + +public class User { + public int id; + public String name; + + public User(int _id, String _name){ + id = _id; + name = _name; + } +} diff --git a/app/src/main/java/com/github/bukaanalytics/home/FragmentHome.java b/app/src/main/java/com/github/bukaanalytics/home/FragmentHome.java index 3932603..aebb2c2 100644 --- a/app/src/main/java/com/github/bukaanalytics/home/FragmentHome.java +++ b/app/src/main/java/com/github/bukaanalytics/home/FragmentHome.java @@ -5,14 +5,18 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.text.method.ScrollingMovementMethod; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.github.bukaanalytics.R; -import com.github.bukaanalytics.common.model.BukaAnalyticsSqliteOpenHelper; +import com.github.bukaanalytics.common.HTTPRequestHelper; import com.github.bukaanalytics.common.model.Post; +import com.github.bukaanalytics.common.model.BukaAnalyticsSqliteOpenHelper; +import com.github.bukaanalytics.common.model.Product; +import com.github.bukaanalytics.common.model.Stat; import java.util.List; @@ -97,12 +101,13 @@ public void onAttach(Context context) { @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { + final int activeUserId = 9925909; //TODO get active userId from login BukaAnalyticsSqliteOpenHelper helper = BukaAnalyticsSqliteOpenHelper.getInstance(getContext()); - List posts = helper.getAllPosts(); + List products = helper.getProducts(activeUserId); StringBuffer sb = new StringBuffer(); - for (Post p : posts) { - sb.append(p.text + "\n"); + for (Product p : products) { + sb.append(p.name + "\n"); } TextView tv = (TextView) getView().findViewById(R.id.text_home); @@ -117,16 +122,59 @@ public void onDetach() { mListener = null; } - /** - * This interface must be implemented by activities that contain this - * fragment to allow an interaction in this fragment to be communicated - * to the activity and potentially other fragments contained in that - * activity. - *

- * See the Android Training lesson Communicating with Other Fragments for more information. - */ + void displayData(int userId) { + final BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(getContext()); + + List products = db.getProducts(userId); + + //Construct the string + StringBuffer sb = new StringBuffer(); + + if(products.size() > 3) { + List stats0 = db.getStats(products.get(0).id); + List stats1 = db.getStats(products.get(1).id); + List stats2 = db.getStats(products.get(2).id); + + if(stats0 != null && stats0.size() > 0) { + sb.append(products.get(0).id + "\n"); + for (Stat s : stats0) { + sb.append(String.format("%s %d %d %d %d %d %d \n", s.date, s.viewCount, s.totalViewCount, s.soldCount, s.totalSoldCount, s.interestCount, s.totalInterestCount)); + } + sb.append("\n"); + } + + if(stats1 != null && stats1.size() > 0) { + sb.append(products.get(1).id + "\n"); + for (Stat s : stats1) { + sb.append(String.format("%s %d %d %d %d %d %d \n", s.date, s.viewCount, s.totalViewCount, s.soldCount, s.totalSoldCount, s.interestCount, s.totalInterestCount)); + } + sb.append("\n"); + } + + if(stats2 != null && stats2.size() > 0) { + sb.append(products.get(2).id + "\n"); + for (Stat s : stats2) { + sb.append(String.format("%s %d %d %d %d %d %d \n", s.date, s.viewCount, s.totalViewCount, s.soldCount, s.totalSoldCount, s.interestCount, s.totalInterestCount)); + } + sb.append("\n"); + } + } + + TextView tv = (TextView) getView().findViewById(R.id.text_home); + tv.setText(sb.toString()); + tv.setMovementMethod(new ScrollingMovementMethod()); + } + + /** + * This interface must be implemented by activities that contain this + * fragment to allow an interaction in this fragment to be communicated + * to the activity and potentially other fragments contained in that + * activity. + *

+ * See the Android Training lesson Communicating with Other Fragments for more information. + */ public interface OnFragmentInteractionListener { // TODO: Update argument type and name void onFragmentInteraction(Uri uri); diff --git a/app/src/main/java/com/github/bukaanalytics/widget/BukaAnalyticsAppWidgetProvider.java b/app/src/main/java/com/github/bukaanalytics/widget/BukaAnalyticsAppWidgetProvider.java index 9a0ba69..fe6de8d 100644 --- a/app/src/main/java/com/github/bukaanalytics/widget/BukaAnalyticsAppWidgetProvider.java +++ b/app/src/main/java/com/github/bukaanalytics/widget/BukaAnalyticsAppWidgetProvider.java @@ -3,12 +3,23 @@ import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.view.View; import android.widget.RemoteViews; import com.github.bukaanalytics.R; +import com.github.bukaanalytics.common.HTTPRequestHelper; +import com.github.bukaanalytics.common.model.BukaAnalyticsSqliteOpenHelper; +import com.github.bukaanalytics.common.model.Token; +import com.github.bukaanalytics.widget.widgetalarm.WidgetAlarm; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import org.json.JSONObject; + +import java.util.ArrayList; import java.util.Random; /** @@ -16,16 +27,30 @@ */ public class BukaAnalyticsAppWidgetProvider extends AppWidgetProvider { + + public static final String ACTION_AUTO_UPDATE = "AUTO_UPDATE"; + @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + final BukaAnalyticsSqliteOpenHelper db = BukaAnalyticsSqliteOpenHelper.getInstance(context.getApplicationContext()); final int count = appWidgetIds.length; for (int i = 0; i < count; i++) { int widgetId = appWidgetIds[i]; - String number = String.format("%03d", (new Random().nextInt(900) + 100)); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); - remoteViews.setTextViewText(R.id.textView_content_unread_msg, number); + + Token tokenData = db.getTokenData(); + if (tokenData.id == 0) { + remoteViews.setViewVisibility(R.id.textView_not_login, View.VISIBLE); + remoteViews.setViewVisibility(R.id.layout_stat, View.GONE); + } else { + remoteViews.setViewVisibility(R.id.textView_not_login, View.GONE); + remoteViews.setViewVisibility(R.id.layout_stat, View.VISIBLE); + getUnread(context, appWidgetManager, remoteViews, widgetId); + getNotifByType("report", context, appWidgetManager, remoteViews, widgetId); + getNotifByType("nego", context, appWidgetManager, remoteViews, widgetId); + } Intent intent = new Intent(context, BukaAnalyticsAppWidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); @@ -33,8 +58,66 @@ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] a PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + remoteViews.setOnClickPendingIntent(R.id.layout_container, pendingIntent); appWidgetManager.updateAppWidget(widgetId, remoteViews); } } + + @Override + public void onReceive(Context context, Intent intent) { + super.onReceive(context, intent); + if(intent.getAction().equals(ACTION_AUTO_UPDATE)) { + AppWidgetManager.getInstance(context).updateAppWidget(new ComponentName(context, BukaAnalyticsAppWidgetProvider.class), new RemoteViews(context.getPackageName(), R.layout.widget)); + } + } + + @Override + public void onEnabled(Context context) { + WidgetAlarm widgetAlarm = new WidgetAlarm(context); + widgetAlarm.startAlarm(); + } + + @Override + public void onDisabled(Context context) { + WidgetAlarm widgetAlarm = new WidgetAlarm(context); + widgetAlarm.stopAlarm(); + } + + private void getUnread(Context context, final AppWidgetManager appWidgetManager, final RemoteViews remoteViews, final int widgetId) { + HTTPRequestHelper helper = new HTTPRequestHelper(); + helper.getAsJSONObject("https://api.bukalapak.com/v2/notifications/unreads.json", context.getApplicationContext(), new HTTPRequestHelper.JSONObjectCallback() { + @Override + public void onCompleted(Exception e, JsonObject result) { + String needAction = result.get("transactions_need_action_as_seller").getAsString(); + String unreadMsg = result.get("messages").getAsString(); + + remoteViews.setTextViewText(R.id.textView_content_need_action, needAction); + remoteViews.setTextViewText(R.id.textView_content_unread_msg, unreadMsg); + + appWidgetManager.updateAppWidget(widgetId, remoteViews); + } + }); + } + + private void getNotifByType(final String type, Context context, final AppWidgetManager appWidgetManager, final RemoteViews remoteViews, final int widgetId) { + HTTPRequestHelper helper = new HTTPRequestHelper(); + String url = "https://api.bukalapak.com/v2/notifications/list.json?type[]=" + type; + String url2 = "https://api.bukalapak.com/v2/notifications/list.json?type[]=nego"; + helper.getAsJSONObject(url2, context.getApplicationContext(), new HTTPRequestHelper.JSONObjectCallback() { + @Override + public void onCompleted(Exception e, JsonObject result) { + JsonArray data = result.get("data").getAsJsonArray(); + String statStr = String.valueOf(data.size()); + + if (type.equals("report")) { + remoteViews.setTextViewText(R.id.textView_content_complaint, statStr); + } else if (type.equals("nego")) { + remoteViews.setTextViewText(R.id.textView_content_nego, statStr); + } + + appWidgetManager.updateAppWidget(widgetId, remoteViews); + } + }); + } } diff --git a/app/src/main/java/com/github/bukaanalytics/widget/widgetalarm/WidgetAlarm.java b/app/src/main/java/com/github/bukaanalytics/widget/widgetalarm/WidgetAlarm.java new file mode 100644 index 0000000..f45f04d --- /dev/null +++ b/app/src/main/java/com/github/bukaanalytics/widget/widgetalarm/WidgetAlarm.java @@ -0,0 +1,53 @@ +package com.github.bukaanalytics.widget.widgetalarm; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import com.github.bukaanalytics.common.BukaAnalyticsBroadcastReceiver; +import com.github.bukaanalytics.widget.BukaAnalyticsAppWidgetProvider; + +/** + * Created by touchtenmac-19 on 5/26/17. + */ + +public class WidgetAlarm { + private final int ALARM_ID = 0; + private final int INTERVAL_MILLIS = 10000; + + private Context mContext; + + + public WidgetAlarm(Context context) + { + mContext = context; + } + + + public void startAlarm() + { + // Construct an intent that will execute the AlarmReceiver + Intent intent = new Intent(BukaAnalyticsAppWidgetProvider.ACTION_AUTO_UPDATE); + // Create a PendingIntent to be triggered when the alarm goes off + final PendingIntent pIntent = PendingIntent.getBroadcast( + mContext, ALARM_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT); + // Setup periodic alarm every every half hour from this point onwards + long firstMillis = System.currentTimeMillis(); // alarm is set right away + AlarmManager alarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + // First parameter is the type: ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC_WAKEUP + // Interval can be INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_DAY + alarm.setInexactRepeating(AlarmManager.RTC_WAKEUP, firstMillis, + 1000 * 60 * 5, pIntent); + } + + + public void stopAlarm() + { + Intent alarmIntent = new Intent(BukaAnalyticsAppWidgetProvider.ACTION_AUTO_UPDATE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, ALARM_ID, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); + + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + alarmManager.cancel(pendingIntent); + } +} diff --git a/app/src/main/res/drawable/border.xml b/app/src/main/res/drawable/border.xml new file mode 100644 index 0000000..54b609b --- /dev/null +++ b/app/src/main/res/drawable/border.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 1c29215..0d9eea1 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -10,9 +10,10 @@ diff --git a/app/src/main/res/layout/widget.xml b/app/src/main/res/layout/widget.xml index 2a18d78..0b7b6d5 100644 --- a/app/src/main/res/layout/widget.xml +++ b/app/src/main/res/layout/widget.xml @@ -1,22 +1,152 @@ > + android:layout_height="wrap_content" + android:background="#55D71149"> + android:textSize="12sp" + android:textColor="#FFFFFF" + android:text="Please login to get your stats" /> + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index dd3e2a1..1c8c224 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -14,6 +14,10 @@ android:id="@+id/nav_bidding" android:icon="@drawable/ic_bukaanalytics_bidding" android:title="Bidding" /> + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index cde69bc..c5e9476 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index c133a0c..4597f6c 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index bfa42f0..488ea49 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 324e72c..4e12aa3 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index aee44e1..5594d84 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/xml/widget_info.xml b/app/src/main/res/xml/widget_info.xml index 597decd..edd698b 100644 --- a/app/src/main/res/xml/widget_info.xml +++ b/app/src/main/res/xml/widget_info.xml @@ -1,7 +1,7 @@ { + const locals = super.getLocals(); + const { config: { elements, propForQuery } } = locals; + const query = this.props.value; + this.setState({ elements, propForQuery, query }); + locals.onChange(query); + }); + } + + getLocals() { + return super.getLocals(); + } + + getTemplate() { + return function (locals) { + const { query } = this.state; + const elements = this.findElement(query); + const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim(); + + const stylesheet = locals.stylesheet; + const formGroupStyle = stylesheet.formGroup.normal; + const controlLabelStyle = stylesheet.controlLabel.normal; + const textboxStyle = stylesheet.textbox.normal; + + const label = locals.label ? {locals.label} : null; + return ( + + {label} + + { + this.setState({ query: text }); + locals.onChange(text); + }} + renderItem={( item ) => ( + { + this.setState({ query: item }); + locals.onChange(item); + }} + > + {item} + + )} + /> + + + {elements.length > 0 ? ( + + ) : ( + + + + )} + + + ); + }.bind(this); + } + + findElement(query) { + if (query === '' || !query) { + return []; + } + + const { elements } = this.state; + const regex = new RegExp(`${query.trim()}`, 'i'); + return elements.filter(element => element.search(regex) >= 0).slice(0, 4); + } +} + +// const styles = { +// container: { +// +// // backgroundColor: '#F5FCFF', +// }, +// autocompleteContainer: { +// flex: 1, +// left: 0, +// position: 'absolute', +// right: 0, +// top: 0, +// zIndex: 1 +// }, +// inputContainerStyle: { +// borderRadius: 4, +// borderColor: '#cccccc', +// borderWidth: 1, +// marginBottom: 18, +// }, +// itemText: { +// fontSize: 15, +// margin: 2, +// }, +// }; +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingTop: 25, + }, + autocompleteContainer: { + flex: 1, + left: AppSizes.screen.widthThird, + position: 'absolute', + right: 0, + top: 0, + zIndex: 1, + }, + itemText: { + fontSize: 15, + margin: 2, + }, + descriptionContainer: { + // `backgroundColor` needs to be set otherwise the + // autocomplete input will disappear on text input. + marginTop: 25, + }, + infoText: { + textAlign: 'center', + }, + titleText: { + fontSize: 18, + fontWeight: '500', + marginBottom: 10, + marginTop: 10, + textAlign: 'center', + }, + directorText: { + color: 'grey', + fontSize: 12, + marginBottom: 10, + textAlign: 'center', + }, + openingText: { + textAlign: 'center', + }, + inputContainerStyle: { + borderRadius: 0, + borderColor: '#cccccc', + borderWidth: 0, + marginBottom: 5, + height: 36, + width: AppSizes.screen.widthTwoThirds, + }, +}); + +AutoInput.transformer = { + format: value => { + return value; + }, + parse: value => { + return value; + }, +}; + +export default AutoInput; diff --git a/js-src/components/ui/FormInput.js b/js-src/components/ui/FormInput.js index 71db4bb..70c378b 100644 --- a/js-src/components/ui/FormInput.js +++ b/js-src/components/ui/FormInput.js @@ -45,7 +45,7 @@ class CustomFormInput extends Component { inputStyle: [{ color: AppColors.textPrimary, fontFamily: AppFonts.base.family, - paddingHorizontal: 10, + paddingHorizontal: 0, paddingVertical: 3, }], }; diff --git a/js-src/index.js b/js-src/index.js index 22d58b2..d513386 100644 --- a/js-src/index.js +++ b/js-src/index.js @@ -1,9 +1,10 @@ import React, { Component } from 'react' -import { StyleSheet, Text, View } from 'react-native' +import { AsyncStorage, StyleSheet, Text, View } from 'react-native' import { applyMiddleware, compose, createStore } from 'redux'; import { connect, Provider } from 'react-redux'; import { composeWithDevTools } from 'remote-redux-devtools'; import { createLogger } from 'redux-logger'; +import { persistStore, autoRehydrate } from 'redux-persist' import thunk from 'redux-thunk'; // All redux reducers (rolled into one mega-reducer) @@ -33,7 +34,10 @@ class Root extends Component { const composeEnhancers = composeWithDevTools({ realtime: true, port: 8000 }); this.store = composeEnhancers( applyMiddleware(...middleware), + autoRehydrate() )(createStore)(rootReducer) + + persistStore(this.store, { storage: AsyncStorage }) } render() { diff --git a/js-src/lib/BLApi.js b/js-src/lib/BLApi.js new file mode 100644 index 0000000..232c723 --- /dev/null +++ b/js-src/lib/BLApi.js @@ -0,0 +1,246 @@ +import axios from 'axios'; +import { ToastAndroid } from 'react-native' + +const API_PRODUCTS = 'https://api.bukalapak.com/v2/products.json'; +const API_BASE_URL = 'https://api.bukalapak.com/v2' + +class BLApi { + // ============================= + // Api call related functions + // ============================= + static sendApiRequest(config, onSuccess, onError) { + return axios(config).then(res => { + console.log(res) + switch (res.status) { + case 200: + return res.data + break; + case 410: // Gone – Consider updating your client application. + throw Error("Data unavailable due to unupdated application") + break; + case 500: + throw Error("Internal Server Error – We had a problem with our server. Try again later.") + break; + default: + throw Error("Unknown Error") + break; + } + }) + .then(data => { + if (data.status != 'OK') throw Error(data.message) + else onSuccess(data) + }) + .catch(err => { + onError(err) + ToastAndroid.show(err.message, ToastAndroid.LONG) + }) + } + + static authenticateUser(data, successCallback, errorCallback) { + return this.sendApiRequest({ + method: 'post', + url: API_BASE_URL + '/authenticate.json', + auth: { username: data.username, password: data.password }, + }, resData => successCallback(resData), err => errorCallback(err)) + } + + static getTransactions(params, successCallback, errorCallback) { + let { userId, token, perPage, page, since } = params + + return this.sendApiRequest({ + method: 'get', + url: 'https://api.bukalapak.com/v2/transactions.json', + auth: { + username: userId, + password: token + }, + params: { + page: page, + per_page: perPage, + seller: 1, + status: 5, + created_since: since + } + }, resData => { + successCallback(resData.transactions) + + if (resData.transactions.length == perPage) { + let newParam = { + userId, token, perPage, since, + page: page+1 + } + this.getTransactions(newParam, successCallback, errorCallback) + } + }, err => errorCallback(err)) + } + + static getProducts(page, keyword, filter = {}) { + console.log("selected filter are : " , filter); + return axios.get(API_PRODUCTS, { + params: { + page: page || 1, + per_page: 24, + keywords: keyword || 'murah', + category_id: filter.category_id || 159, + nego: filter.nego || 1, + harga_pas: filter.harga_pas || 1, + province: filter.province || 'DKI Jakarta', + top_seller: filter.top_seller || 0, + city: filter.city || 'Jakarta Pusat', + price_min: filter.price_min || 0, + price_max: filter.price_max || 999999999, + sort_by: filter.sort_by || 'Termurah', // Termahal, Terbaru, Acak + }, + }); + } + + // ============================= + // Static functions + // ============================= + + static mathAnalysis(result) { + if ( result != null && result.length > 0 ) { + const prices = result.sort((a, b) => { + return a.price - b.price; + }); + + const max_price = prices[prices.length - 1].price; + const min_price = prices[0].price; + let avg_price = 0; + + const num_class_interval = Math.round(1 + 3.3 * Math.log10(prices.length)); + const range = max_price - min_price; + console.log(this); + const summary = this.initSummary(num_class_interval); + + for (let i = prices.length - 1; i >= 0; i--) { + avg_price = avg_price + prices[i].price; + prices[i].class = this.checkClassRange(prices[i].price, min_price, range, num_class_interval); + + const text = `_${prices[i].class}`; + + summary[text].avg_price = summary[text].avg_price + prices[i].price; + summary[text].avg_sold = summary[text].avg_sold + prices[i].sold; + summary[text].count = summary[text].count + 1; + } + + const profit = {}; + for (let i = num_class_interval - 1; i >= 0; i--) { + const text = `_${i + 1}`; + // safe division by zero.. not to trigger NaN + summary[text].avg_price = (summary[text].count > 0 ? summary[text].avg_price / summary[text].count : 0); + summary[text].avg_sold = (summary[text].count > 0 ? summary[text].avg_sold / summary[text].count : 0); + + profit[text] = summary[text].avg_price * summary[text].avg_sold; + } + + // find best price + const best_price_index = this.findBestPrice(profit); + const best_price = summary[best_price_index].avg_price; + + //generategraphdata + const graph = this.generateGraphData(summary, num_class_interval); + + // calculate avg_price + avg_price = avg_price / prices.length; + + const retval = { + min_price, + max_price, + avg_price, + best_price, + summary, + graph, + }; + return retval; + + }else{ + + const retval = { + min_price : 0, + max_price : 0, + avg_price : 0, + best_price : 0, + summary : null, + graph: [], + }; + return retval; + } + } + + // ============================= + // Private functions + // ============================= + static parsePrice(succ) { + const prices = []; + if (succ.products) { + const products = succ.products; + for (let i = products.length - 1; i >= 0; i--) { + prices.push({ + price: products[i].price, + sold: products[i].sold_count, + class: '', + }); + } + } + return prices; + } + + static checkClassRange(value, min_value, range_max_min, num_class) { + const percentage = (value - min_value) / range_max_min; + for (let i = num_class - 1; i >= 0; i--) { + const j = i + 1; + const lower_limit = i / num_class; + const upper_limit = j / num_class; + if (lower_limit <= percentage && percentage < upper_limit) { + return j; + } + } + return num_class; + } + + static initSummary(num_class) { + const summary = {}; + for (let i = 1; i < num_class + 1; i++) { + const text = `_${i}`; + summary[text] = { + avg_price: 0, + avg_sold: 0, + count: 0, + }; + } + return summary; + } + + static findBestPrice(profit) { + let first_time = true; + let max_key = null; + for (const key in profit) { + if (profit.hasOwnProperty(key)) { + if (first_time) { + max_key = key; + first_time = false; + } + + if (profit[key] > profit[max_key]) { + max_key = key; + } + } + } + return max_key; + } + + static generateGraphData(summary, num_class) { + const graph = []; + for (let i = 0; i < num_class; i++) { + const text = `_${i + 1}`; + graph.push({ + name: text, + v: summary[text].count, + }); + } + return graph; + } +} + +export default BLApi; diff --git a/js-src/lib/BLSqlite.js b/js-src/lib/BLSqlite.js new file mode 100644 index 0000000..7b4d663 --- /dev/null +++ b/js-src/lib/BLSqlite.js @@ -0,0 +1,339 @@ +import SQLite from 'react-native-sqlite-storage'; + +const config = { + dbname: 'baDatabase', + dbversion: 1, + tables: { + users: 'users', + products: 'products', + stats: 'stats', + tokens: 'tokens' + }, + col: { + users: { + id: 'seller_id', + name: 'name', + }, + products: { + id: 'product_id', + name: 'name', + price: 'price', + seller_id: 'seller_id', + }, + stats: { + id: 'id', + date: 'date', + day_name: 'day_name', + product_id: 'product_id', + view_count: 'view_count', + view_total: 'view_total', + sold_count: 'sold_count', + sold_total: 'sold_total', + interest_count: 'interest_count', + interest_total: 'interest_total', + }, + tokens: { + id: 'id', + user_id: 'user_id', + token: 'token' + } + }, +}; + + +class BLSqlite { + + constructor() { + this.openDatabase(); + this.createTablesIfNotExists(); + } + + openDatabase() { + this.db = SQLite.openDatabase({ + name: config.dbname, + }, null); + } + + getWeeklyView(param, callback) { + console.log("Weekly view is "); + console.log(param); + this.db.executeSql(` + SELECT + ${config.col.stats.day_name} as day_name, + SUM(${config.col.stats.view_count}) as daily_view + FROM ${config.tables.stats} + WHERE ${config.col.stats.date} BETWEEN ? AND ? + GROUP BY ${config.col.stats.day_name} + `, [param.start_date, param.end_date], results => { + console.log("results inside executeSql"); + console.log(results); + callback(this.parseWeeklyViewResult(results)); + }); + } + + getWeeklyRevenueAttribution(param, callback) { + this.db.executeSql(` + SELECT name, + ( terjual * price ) AS revenue + FROM (SELECT product_id, + SUM(sold_count) AS terjual + FROM stats + WHERE DATE BETWEEN ? AND ? + GROUP BY product_id) AS A + JOIN products AS B + ON A.product_id = B.product_id + ORDER BY revenue DESC + LIMIT 5 + `, [param.start_date, param.end_date], results => { + callback(this.parseWeeklyRevenueAttribution(results)); + }); + } + + getWeeklyTopViewedProduct(param, callback) { + this.db.executeSql(` + SELECT name, + view + FROM (SELECT product_id, + SUM(view_count) AS view + FROM stats + WHERE DATE BETWEEN ? AND ? + GROUP BY product_id) AS A + JOIN products AS B + ON A.product_id = B.product_id + ORDER BY view DESC + LIMIT 5 + `, [param.start_date, param.end_date], results => { + callback(this.parseWeeklyViewProduct(results)); + }); + } + + getWeeklyLeastViewedProduct(param, callback) { + this.db.executeSql(` + SELECT name, + view + FROM (SELECT product_id, + SUM(view_count) AS view + FROM stats + WHERE DATE BETWEEN ? AND ? + GROUP BY product_id) AS A + JOIN products AS B + ON A.product_id = B.product_id + ORDER BY view ASC + LIMIT 5 + `, [param.start_date, param.end_date], results => { + callback(this.parseWeeklyViewProduct(results)); + }); + } + + getWeeklyConvertionRate(param, callback) { + this.db.executeSql(` + SELECT SUM(${config.col.stats.sold_count}) AS sold_accumulated, + SUM(${config.col.stats.view_count}) AS view_accumulated + FROM ${config.tables.stats} + WHERE DATE BETWEEN ? AND ? + `, [param.start_date, param.end_date], results => { + callback(this.parseWeeklyConvertionRate(results)); + }); + } + + getBidSuggestion(param, callback) { + console.log('param is :',param ); + this.db.executeSql(` + SELECT name, + ( ratio * price * 0.1 ) AS bid_suggestion + FROM (SELECT with_ads.product_id, + ( CASE + WHEN with_ads.view_iklan <= without_ads.VIEW THEN -1 + WHEN with_ads.sold_iklan <= without_ads.sold THEN -1 + ELSE ( ( with_ads.sold_iklan - without_ads.sold ) / + ( with_ads.view_iklan - + without_ads.VIEW ) ) + END ) AS ratio + FROM (SELECT Avg(view_count) AS view_iklan, + Avg(sold_count) AS sold_iklan, + product_id + FROM stats + WHERE date BETWEEN ? AND ? + GROUP BY product_id) AS with_ads + JOIN (SELECT Avg(view_count) AS VIEW, + Avg(sold_count) AS sold, + product_id + FROM stats + WHERE date BETWEEN ? AND ? + GROUP BY product_id) AS without_ads + ON with_ads.product_id = without_ads.product_id + WHERE ratio > 0) AS A + JOIN products AS B + ON A.product_id = B.product_id + `, [param.start_ads, param.end_ads, param.start_no_ads, param.end_no_ads], results => { + callback(this.parseBidSuggestion(results)); + }); + } + + getActiveToken(callback) { + this.db.executeSql(` + SELECT * from tokens + ORDER BY id DESC + LIMIT 1 + `, [], result => { + callback(this.parseActiveToken(result)) + }) + } + + createTablesIfNotExists() { + this.db.executeSql(` + CREATE TABLE IF NOT EXISTS users ( + seller_id INTEGER PRIMARY KEY, + name TEXT + )`); + this.db.executeSql(` + CREATE TABLE IF NOT EXISTS products( + product_id TEXT PRIMARY KEY, + name TEXT, + price INTEGER, + seller_id INTEGER, + FOREIGN KEY(seller_id) REFERENCES users(seller_id) + )`); + this.db.executeSql(` + CREATE TABLE IF NOT EXISTS products( + id INTEGER PRIMARY KEY, + date TEXT, + day_name TEXT, + product_id TEXT, + view_count INTEGER, + view_total INTEGER, + sold_count INTEGER, + sold_total INTEGER, + interest_count INTEGER, + interest_total INTEGER + )`); + this.db.executeSql(` + CREATE TABLE IF NOT EXISTS tokens( + id INTEGER PRIMARY KEY, + user_id INTEGER, + token TEXT + )`); + } + + insertUser(param) { + this.db.executeSql(` + INSERT INTO users ( + seller_id, + name + ) VALUES (?, ?) + `, [param.user_id, param.name]); + } + + insertProducts(param) { + this.db.executeSql(` + INSERT INTO users ( + product_id, + name, + price, + seller_id + ) VALUES (?, ?, ?, ?) + `, [param.product_id, param.name, param.price, param.seller_id]); + } + + insertStats(param) { + this.db.executeSql(` + INSERT INTO users ( + date, + day_name, + product_id, + view_count, + view_total, + sold_count, + sold_total, + interest_count, + interest_total + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, [param.date, param.day_name, param.product_id, param.view_count, param.view_total, param.sold_count, param.sold_total, param.interest_count, param.interest_total]); + } + + insertToken(param) { + this.db.executeSql(` + INSERT INTO ${config.tables.tokens} ( + ${config.col.tokens.user_id}, + ${config.col.tokens.token} + ) VALUES (?, ?) + `, [param.userId, param.token]); + } + + // ================== + // Private Functions + // ================== + parseWeeklyViewResult(results) { + const retval = {}; + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval[`${row.day_name}`] = row.daily_view; + } + return retval; + } + + parseWeeklyRevenueAttribution(results) { + const retval = []; + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval.push({ + name: row.name, + attribution: row.revenue, + }); + } + return retval; + } + + parseWeeklyViewProduct(results) { + const retval = []; + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval.push({ + item: row.name, + view: row.view, + }); + } + return retval; + } + + parseWeeklyConvertionRate(results) { + const retval = {}; + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval.view_accumulated = row.view_accumulated; + retval.sold_accumulated = row.sold_accumulated; + } + return retval; + } + + parseBidSuggestion(results) { + const retval = []; + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval.push({ + item: row.name, + bid_suggestion: row.bid_suggestion, + }); + } + return retval; + } + + parseActiveToken(result) { + const retval = {} + const result_length = results.rows.length; + for (let i = 0; i < result_length; i++) { + const row = results.rows.item(i); + retval.userId = row.user_id; + retval.token = row.token; + } + return retval; + } + +} + +export const Sqlite = new BLSqlite(); diff --git a/js-src/navigation/NavOptions.js b/js-src/navigation/NavOptions.js index 5298c7b..cb9215b 100644 --- a/js-src/navigation/NavOptions.js +++ b/js-src/navigation/NavOptions.js @@ -11,7 +11,7 @@ const navOptions = ({navigation}) => ({ navigation.navigate('DrawerOpen')} style={{justifyContent: 'center', alignItems: 'center', padding: 8, paddingTop: 12}}> - + ) }) diff --git a/js-src/navigation/PricingAnalysisStack.js b/js-src/navigation/PricingAnalysisStack.js index 3292a5d..43381c0 100644 --- a/js-src/navigation/PricingAnalysisStack.js +++ b/js-src/navigation/PricingAnalysisStack.js @@ -1,13 +1,17 @@ -import { StackNavigator } from 'react-navigation' -import PricingAnalysisScreen from '@screen/PricingAnalysis/PricingAnalysisContainer' -import navOptions from '@nav/NavOptions' +import { StackNavigator } from 'react-navigation'; +import PricingAnalysisScreen from '@screen/PricingAnalysis/PricingAnalysisContainer'; +import PricingFilterScreen from '@screen/PricingFilter/PricingFilterContainer'; +import navOptions from '@nav/NavOptions'; +import { AppColors } from '@theme'; const PricingAnalysisStack = StackNavigator({ PricingAnalysis: { - screen: PricingAnalysisScreen - } -}, { - navigationOptions: navOptions -}) + screen: PricingAnalysisScreen, + navigationOptions: navOptions, + }, + FilterScreen: { + screen: PricingFilterScreen, + }, +}); -export default PricingAnalysisStack \ No newline at end of file +export default PricingAnalysisStack; diff --git a/js-src/redux/bidding/actions.js b/js-src/redux/bidding/actions.js new file mode 100644 index 0000000..f59d6c2 --- /dev/null +++ b/js-src/redux/bidding/actions.js @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { Sqlite } from '../../lib/BLSqlite'; +import { + BIDDING_ACTION_SET, +} from './constant'; + +// Sementara ini asumsi no_ads = 2 minggu lalu, with ads = 1 minggu lalu +export function refreshBiddingData() { + return (dispatch, getState) => { + const latestDate = getState().dashboard.latestDate; + // diganti dulu ya .. sebelumnya ada kontrol + const momentObj = moment(); //moment(latestDate, 'X'); + + const end_ads = moment(momentObj, 'X').format('YYYY-MM-DD'); + const start_ads = moment(momentObj, 'X').day(1).format('YYYY-MM-DD'); + const end_no_ads = moment(momentObj, 'X').day(0).format('YYYY-MM-DD'); + const start_no_ads = moment(momentObj, 'X').day(-6).format('YYYY-MM-DD'); + + Sqlite.getBidSuggestion({ + start_ads: start_ads, + end_ads: end_ads, + start_no_ads: start_no_ads, + end_no_ads: end_no_ads, + }, (res) => { + dispatch(setData('bid_suggestion', res)); + }); + }; +} + +function setData(key, value) { + return { + type: BIDDING_ACTION_SET, + key, + value, + }; +} diff --git a/js-src/redux/bidding/constant.js b/js-src/redux/bidding/constant.js new file mode 100644 index 0000000..4b8ab4c --- /dev/null +++ b/js-src/redux/bidding/constant.js @@ -0,0 +1 @@ +export const BIDDING_ACTION_SET = 'BIDDING_ACTION_SET'; diff --git a/js-src/redux/bidding/reducer.js b/js-src/redux/bidding/reducer.js new file mode 100644 index 0000000..8d8ac9c --- /dev/null +++ b/js-src/redux/bidding/reducer.js @@ -0,0 +1,20 @@ +import { + BIDDING_ACTION_SET, +} from './constant'; +import moment from 'moment' + +const initialState = { + bid_suggestion: [], +}; + +export default function biddingReducer(state = initialState, action) { + switch (action.type) { + case BIDDING_ACTION_SET: + return { + ...state, + [action.key]: action.value, + }; + default: + return state; + } +} diff --git a/js-src/redux/dashboard/actions.js b/js-src/redux/dashboard/actions.js new file mode 100644 index 0000000..06c9afc --- /dev/null +++ b/js-src/redux/dashboard/actions.js @@ -0,0 +1,168 @@ +import BLApi from '@lib/BLApi' +import moment from 'moment'; +import { Sqlite } from '../../lib/BLSqlite'; +export const DASHBOARD = { + TO_NEXT_WEEK: 'DASHBOARD_TO_NEXT_WEEK', + TO_PREV_WEEK: 'DASHBOARD_TO_PREV_WEEK', + GET_DASHBOARD_DATA: 'DASHBOARD_GET_DATA', + SET_DASHBOARD_DATA: 'DASHBOARD_SET_DATA', + SET_TRANSACTION_DATA: 'DASHBOARD_SET_TRANSACTION_DATA', + RESET_TRANSACTION_DATA: 'DASHBOARD_RESET_TRANSACTION_DATA', + FETCHING_TRANSACTION_DATA: 'DASHBOARD_FETCHING_TRANSACTION_DATA', + FETCH_TRANSACTION_COMPLETED: 'DASHBOARD_FETCH_TRANSACTION_COMPLETED', + FETCHING_SQLITE_DATA: 'DASHBOARD_FETCHING_SQLITE_DATA', +}; + +export function refreshData(refreshType) { + let perPage = 1 + + return function(dispatch, getState) { + dispatch(fetchingTransactionData()) + dispatch(resetTransactionData()) + + if (refreshType == 'next') { + dispatch(setDateToNextWeek()); + } else if (refreshType == 'prev') { + dispatch(setDateToPrevWeek()); + } + + let latestDate = getState().dashboard.latestDate + let since = moment(latestDate, 'X').subtract(13, 'day').format('YYYY-MM-DD') + + dispatch(getSqliteData(latestDate)); + console.log(Sqlite); + + let userData = getState().user + + return BLApi.getTransactions({ + userId: userData.userId, + token: userData.token, + perPage: perPage, + page: 1, + since: since + }, transactions => { + dispatch(setTransactionData(transactions)) + if (transactions.length < perPage) dispatch(fetchTransactionCompleted()) + }, err => { + console.log(err) + }) + + } +} + +function fetchingTransactionData() { + return { + type: DASHBOARD.FETCHING_TRANSACTION_DATA + } +} + +function fetchTransactionCompleted() { + return { + type: DASHBOARD.FETCH_TRANSACTION_COMPLETED + } +} + +function resetTransactionData() { + return { + type: DASHBOARD.RESET_TRANSACTION_DATA + } +} + +function setTransactionData(transactions) { + return { + type: DASHBOARD.SET_TRANSACTION_DATA, + payload: { transactions } + } +} + +function setDateToNextWeek() { + return { + type: DASHBOARD.TO_NEXT_WEEK + } +} + +function setDateToPrevWeek() { + return { + type: DASHBOARD.TO_PREV_WEEK + } +} + +function setData(key, value) { + return { + type: DASHBOARD.SET_DASHBOARD_DATA, + key, + value, + }; +} + +function fetchSqliteData(flag) { + return { + type: DASHBOARD.FETCHING_SQLITE_DATA, + flag, + }; +} + +function getSqliteData(latestDate) { + const momentObj = moment(latestDate, 'X'); + const end_date = momentObj.format('YYYY-MM-DD'); + const start_date = momentObj.day(-6).format('YYYY-MM-DD'); + + const end_prev_week = momentObj.subtract(1, 'day').format('YYYY-MM-DD'); + const start_prev_week = momentObj.subtract(6, 'day').format('YYYY-MM-DD'); + console.log("end_prev_week", end_prev_week, "start_next week", start_prev_week); + return dispatch => { + dispatch(fetchSqliteData(true)); + Sqlite.getWeeklyView({ + start_date: start_date, + end_date: end_date, + }, (res) => { + dispatch(setData('weekly_view', res)); + }); + + Sqlite.getWeeklyLeastViewedProduct({ + start_date: start_date, + end_date: end_date, + }, (res) => { + dispatch(setData('least_viewed', res)); + }); + + Sqlite.getWeeklyTopViewedProduct({ + start_date: start_date, + end_date: end_date, + }, (res) => { + dispatch(setData('most_viewed', res)); + }); + + Sqlite.getWeeklyConvertionRate({ + start_date: start_date, + end_date: end_date, + }, (res) => { + dispatch(setData('convertion_rate', res)); + }); + + Sqlite.getWeeklyConvertionRate({ + start_date: start_prev_week, + end_date: end_prev_week, + }, (res) => { + dispatch(setData('prev_convertion_rate', res)); + }); + + Sqlite.getWeeklyRevenueAttribution({ + start_date: start_date, + end_date: end_date, + }, (res) => { + dispatch(setData('revenue_attribution', res)); + }); + + dispatch(fetchSqliteData(false)); + }; +} + +function getDateRange(getState) { + const latestDate = getState().dashboard.latestDate; + const momentObj = moment(latestDate, 'X'); + + const sundayDateStr = momentObj.format('YYYY-MM-DD'); + const mondayDateStr = momentObj.day(-6).format('YYYY-MM-DD'); + return { sundayDateStr, mondayDateStr }; +} diff --git a/js-src/redux/dashboard/reducer.js b/js-src/redux/dashboard/reducer.js new file mode 100644 index 0000000..f477d83 --- /dev/null +++ b/js-src/redux/dashboard/reducer.js @@ -0,0 +1,100 @@ +import { DASHBOARD } from '@redux/dashboard/actions' +import moment from 'moment' + +const initialState = { + latestDate: moment().day(7).unix(), + rawTransactions: [], + fetchingTransaction: false, + currentRevenue: 0, + previousRevenue: 0, + weekly_view: {}, + least_viewed: [], + most_viewed: [], + convertion_rate: 0, + prev_convertion_rate: 0, + revenue_attribution: [ + { + name: '', + attribution: 1, + }, + ], + isFetchingSqliteData: false, +}; + +export default function dashboardReducer(state = initialState, action) { + let newLatestDate + let newRawTransactions + let revenueObj + + switch (action.type) { + case DASHBOARD.TO_NEXT_WEEK: + newLatestDate = moment(state.latestDate, 'X').add(7, 'day').unix() + return { + ...state, + latestDate: newLatestDate + } + case DASHBOARD.TO_PREV_WEEK: + newLatestDate = moment(state.latestDate, 'X').subtract(7, 'day').unix() + return { + ...state, + latestDate: newLatestDate + } + + case DASHBOARD.SET_TRANSACTION_DATA: + newRawTransactions = state.rawTransactions.concat(action.payload.transactions) + let endOfWeek = moment(state.latestDate, 'X').format() + let startOfWeek = moment(endOfWeek).subtract(6, 'day').format() + let endOfPrevWeek = moment(startOfWeek).subtract(1, 'day').format() + let startOfPrevWeek = moment(endOfPrevWeek).subtract(6, 'day').format() + + revenueObj = action.payload.transactions.reduce((prev, current) => { + let tempCR = prev.currentRevenue + let tempPR = prev.previousRevenue + if (current.state == 'remitted' && moment(current.created_at).isBetween(startOfWeek, endOfWeek, 'day')) tempCR += current.amount + if (current.state == 'remitted' && moment(current.created_at).isBetween(startOfPrevWeek, endOfPrevWeek, 'day')) tempPR += current.amount + + return { currentRevenue: tempCR, previousRevenue: tempPR } + }, { + currentRevenue: state.currentRevenue, + previousRevenue: state.previousRevenue + }) + + return { + ...state, + ...revenueObj, + rawTransactions: newRawTransactions, + } + + case DASHBOARD.RESET_TRANSACTION_DATA: + return { + ...state, + rawTransactions: [], + currentRevenue: 0, + previousRevenue: 0 + } + + case DASHBOARD.FETCHING_TRANSACTION_DATA: + return { + ...state, + fetchingTransaction: true + } + + case DASHBOARD.FETCH_TRANSACTION_COMPLETED: + return { + ...state, + fetchingTransaction: false + } + case DASHBOARD.SET_DASHBOARD_DATA: + return { + ...state, + [action.key]: action.value, + }; + case DASHBOARD.FETCHING_SQLITE_DATA: + return { + ...state, + isFetchingSqliteData: action.flag, + }; + default: + return state; + } +} diff --git a/js-src/redux/index.js b/js-src/redux/index.js index 3f8bb0d..8572ebd 100644 --- a/js-src/redux/index.js +++ b/js-src/redux/index.js @@ -1,12 +1,20 @@ import { combineReducers } from 'redux'; -import nav from '@redux/nav/reducer' -import user from '@redux/user/reducer' +import dashboard from '@redux/dashboard/reducer'; +import nav from '@redux/nav/reducer'; +import user from '@redux/user/reducer'; +import pricing from '@redux/pricing/reducer'; +import pricing_filter from '@redux/pricing_filter/reducer'; +import bidding from '@redux/bidding/reducer'; // Combine all const appReducer = combineReducers({ + dashboard, nav, - user + user, + pricing, + pricing_filter, + bidding, }); // Setup root reducer @@ -15,4 +23,4 @@ const rootReducer = (state, action) => { return appReducer(newState, action); }; -export default rootReducer; \ No newline at end of file +export default rootReducer; diff --git a/js-src/redux/pricing/actions.js b/js-src/redux/pricing/actions.js new file mode 100644 index 0000000..2a243b1 --- /dev/null +++ b/js-src/redux/pricing/actions.js @@ -0,0 +1,83 @@ +import axios from 'axios'; +import BLApi from '../../lib/BLApi'; +import { + PRICING_ACTION_SET, + PRICING_ACTION_FILTER, + PRICING_ACTION_FETCHING, + PRICING_ACTION_CALCULATING, +} from './constant'; +// ini apa bib ? ntar tanyain ya .. pas hari h +// User action types +export const USER = [{ + LOGIN: 'USER_LOGIN', +}]; + +// User action creator +export function setData(data) { + return { + type: PRICING_ACTION_SET, + data, + }; +} + +export function fetching(flag) { + return { + type: PRICING_ACTION_FETCHING, + flag, + }; +} + +export function calculating(flag) { + return { + type: PRICING_ACTION_CALCULATING, + flag, + }; +} + +export function filter(filter) { + return { + type: PRICING_ACTION_FILTER, + filter, + }; +} + +export function getGraph(keyword, filter = null) { + return dispatch => { + const promises = []; + const sampling = 5; + for (let i = 0; i < sampling; i++) { + promises.push(BLApi.getProducts(i, keyword, filter)); + } + + // set loading + dispatch(fetching(true)); + + return axios.all(promises).then(responses => { + dispatch(fetching(false)); + dispatch(calculating(true)); + + // tell ui component that fetching is completed + const tmp_resp = responses.map(r => r.data); + console.log("Return dari server adalah "); + console.log(tmp_resp); + // collecting all datas + let prices = []; + tmp_resp.forEach(t => { + prices = prices.concat(BLApi.parsePrice(t)); + }); + + const result = BLApi.mathAnalysis(prices); + + // tell ui component that calculation is completed + dispatch(calculating(false)); + dispatch(setData(result)); + }) + .catch(error => { + throw (error); + }); + }; +} + +// ================================================ +// Local functions, not exported +// ================================================ diff --git a/js-src/redux/pricing/constant.js b/js-src/redux/pricing/constant.js new file mode 100644 index 0000000..8c28813 --- /dev/null +++ b/js-src/redux/pricing/constant.js @@ -0,0 +1,4 @@ +export const PRICING_ACTION_SET = 'PRICING_ACTION_SET'; +export const PRICING_ACTION_FILTER = 'PRICING_ACTION_FILTER'; +export const PRICING_ACTION_FETCHING = 'PRICING_ACTION_LOADING'; +export const PRICING_ACTION_CALCULATING = 'PRICING_ACTION_CALCULATING'; diff --git a/js-src/redux/pricing/reducer.js b/js-src/redux/pricing/reducer.js new file mode 100644 index 0000000..5912228 --- /dev/null +++ b/js-src/redux/pricing/reducer.js @@ -0,0 +1,34 @@ +import { + PRICING_ACTION_SET, + PRICING_ACTION_FILTER, + PRICING_ACTION_FETCHING, + PRICING_ACTION_CALCULATING, +} from './constant'; + +const initialState = { + graph: [], // array of object, please refer to react-node-pathjs-charts format + max_price: 0, + min_price: 0, + avg_price: 0, + best_price: 0, + filter: {}, // Please refer to superagent passing params to http get. used to store filter + isFetching: false, + isCalculating: false, +}; + +export default function userReducer(state = initialState, action) { + switch (action.type) { + case PRICING_ACTION_SET : { + const { min_price, max_price, avg_price, best_price, graph } = action.data; + return { ...state, max_price, min_price, avg_price, best_price, graph }; + } + case PRICING_ACTION_FILTER : + return state; + case PRICING_ACTION_FETCHING : + return { ...state, isFetching: action.flag }; + case PRICING_ACTION_CALCULATING : + return { ...state, isCalculating: action.flag }; + default: + return state; + } +} diff --git a/js-src/redux/pricing_filter/actions.js b/js-src/redux/pricing_filter/actions.js new file mode 100644 index 0000000..74d8141 --- /dev/null +++ b/js-src/redux/pricing_filter/actions.js @@ -0,0 +1,25 @@ +import { + PRICINGFILTER_ACTION_SET, + PRICINGFILTER_ACTION_CLEAR, +} from './constant'; +// ini apa bib ? ntar tanyain ya .. pas hari h +// User action types + +// User action creator +export function setData(key, value) { + return { + type: PRICINGFILTER_ACTION_SET, + key, + value, + }; +} + +export function resetData() { + return { + type: PRICINGFILTER_ACTION_CLEAR, + }; +} + +// ================================================ +// Local functions, not exported +// ================================================ diff --git a/js-src/redux/pricing_filter/constant.js b/js-src/redux/pricing_filter/constant.js new file mode 100644 index 0000000..2740de6 --- /dev/null +++ b/js-src/redux/pricing_filter/constant.js @@ -0,0 +1,2 @@ +export const PRICINGFILTER_ACTION_SET = 'PRICINGFILTER_ACTION_SET'; +export const PRICINGFILTER_ACTION_CLEAR = 'PRICINGFILTER_ACTION_CLEAR'; diff --git a/js-src/redux/pricing_filter/reducer.js b/js-src/redux/pricing_filter/reducer.js new file mode 100644 index 0000000..c230581 --- /dev/null +++ b/js-src/redux/pricing_filter/reducer.js @@ -0,0 +1,27 @@ +import { + PRICINGFILTER_ACTION_SET, + PRICINGFILTER_ACTION_CLEAR, +} from './constant'; + +const initialState = { + category_id: 159, + nego: 1, // harus dimapping.. + harga_pas: 1, // harus dimapping + top_seller: 0, + conditions: '', // harus dimapping antara new / used sementara disabled + price_min: 0, + price_max: 99999999, + province: 'DKI Jakarta', + city: 'Jakarta Pusat', +}; + +export default function PricingFilterReducer(state = initialState, action) { + switch (action.type) { + case PRICINGFILTER_ACTION_SET : + return { ...state, [action.key]: action.value }; + case PRICINGFILTER_ACTION_CLEAR : + return { ...state, ...initialState }; + default: + return state; + } +} diff --git a/js-src/redux/user/actions.js b/js-src/redux/user/actions.js index a4983cc..1474c2d 100644 --- a/js-src/redux/user/actions.js +++ b/js-src/redux/user/actions.js @@ -1,6 +1,46 @@ +import BLApi from '@lib/BLApi' +import { Sqlite } from '@lib/BLSqlite'; + // User action types -export const USER = [{ - LOGIN: 'USER_LOGIN' -}] +export const USER = { + LOADING: 'USER_LOADING', + LOGIN_SUCCESS: 'USER_LOGIN_SUCCESS', + LOGIN_FAILED: 'USER_LOGIN_FAILED' +} + +// User action creator +export function login(username, password) { + return function(dispatch) { + dispatch(isLoading()) + + let cred = { username, password } + return BLApi.authenticateUser(cred, data => dispatch(loginSuccess(data)), err => dispatch(loginFailed())) + } +} + +function isLoading() { + return { + type: USER.LOADING + } +} + +function loginSuccess(data) { + Sqlite.insertToken({ + userId: data.user_id, + token: data.token + }) + + return { + type: USER.LOGIN_SUCCESS, + payload: { + userId: data.user_id, + token: data.token + } + } +} -// User action creator \ No newline at end of file +function loginFailed() { + return { + type: USER.LOGIN_FAILED + } +} \ No newline at end of file diff --git a/js-src/redux/user/reducer.js b/js-src/redux/user/reducer.js index 45d7ab1..7b7c895 100644 --- a/js-src/redux/user/reducer.js +++ b/js-src/redux/user/reducer.js @@ -1,7 +1,32 @@ -const initialState = {} +import { USER } from '@redux/user/actions' + +const initialState = { + loading: false, + userId: 0, + token: '' +} export default function userReducer(state = initialState, action) { switch(action.type) { + case USER.LOADING: + return { + ...state, + loading: true + } + case USER.LOGIN_SUCCESS: + return { + ...state, + loading: false, + userId: action.payload.userId, + token: action.payload.token + } + case USER.LOGIN_FAILED: + return { + ...state, + loading: false, + userId: 0, + token: '' + } default: return state } diff --git a/js-src/screen/BidAnalysis/BidAnalysisContainer.js b/js-src/screen/BidAnalysis/BidAnalysisContainer.js index 6cd0403..818c1d0 100644 --- a/js-src/screen/BidAnalysis/BidAnalysisContainer.js +++ b/js-src/screen/BidAnalysis/BidAnalysisContainer.js @@ -1,13 +1,19 @@ import { connect } from 'react-redux'; // Actions +import { refreshBiddingData } from '@redux/bidding/actions'; +import { refreshData } from '@redux/dashboard/actions' -import BidAnalysis from './BidAnalysisView' +import BidAnalysis from './BidAnalysisView'; const mapStateToProps = (state) => ({ + bidding: state.bidding, + dashboard: state.dashboard, }); const mapDispatchToProps = { + refreshBiddingData: refreshBiddingData, + refreshData: refreshData, }; -export default connect(mapStateToProps, mapDispatchToProps)(BidAnalysis) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(BidAnalysis); diff --git a/js-src/screen/BidAnalysis/BidAnalysisView.js b/js-src/screen/BidAnalysis/BidAnalysisView.js index 5088cdd..3201625 100644 --- a/js-src/screen/BidAnalysis/BidAnalysisView.js +++ b/js-src/screen/BidAnalysis/BidAnalysisView.js @@ -1,10 +1,13 @@ import React, { Component } from 'react' import { StyleSheet, ScrollView, View, TouchableOpacity, Dimensions } from 'react-native'; import SubHeader from '@ui/SubHeader'; -import { AppStyles } from '@theme/'; +import { AppColors, AppSizes, AppStyles } from '@theme/' import { Alerts, Button, Card, Spacer, Text } from '@components/ui/'; import Icon from 'react-native-vector-icons/MaterialIcons' import Table from 'react-native-simple-table'; +import truncate from 'truncate'; +import numeral from 'numeral'; +import moment from 'moment'; const { width, height } = Dimensions.get("window"); const half_width = width/2; @@ -17,7 +20,7 @@ const columns = [ }, { title: 'Bid', - dataIndex: 'bid', + dataIndex: 'bid_suggestion', width: 70, }, ]; @@ -119,34 +122,33 @@ const dataSource = [ class BidAnalysis extends Component{ - render() { - return ( - - - + getDateRange = () => { + let { latestDate } = this.props.dashboard + let momentObj = moment(latestDate, 'X') - - - - - 22 April - 29 April - 14 April - 21 April - + let sundayDateStr = momentObj.format('D MMM') + let mondayDateStr = momentObj.day(-6).format('D MMM') - - - - - - - - - - + let range = mondayDateStr + ' - ' + sundayDateStr - - + return range + } + + render() { + const data_bid_suggestion = this.props.bidding.bid_suggestion.map(val => { + let _bid_suggestion = val.bid_suggestion; // pembulatan + const _item = truncate(val.item, 25); + if(val.bid_suggestion >= 10000) { + _bid_suggestion = 10000; + } + return { + item: _item, + bid_suggestion: numeral(_bid_suggestion).format('0,0'), + }; + }); + return ( + @@ -154,11 +156,10 @@ class BidAnalysis extends Component{ - Title of post - - seharusnya ini diagram , nanti aing lagi cari librarynya - - + +
@@ -201,6 +202,17 @@ const styles = StyleSheet.create({ marginTop: 10, padding:5, }, + buttonWrapper: { + marginBottom: 10, + }, + dateHeaderContainer: { + ...AppStyles.spreadHorizontalContainer, + ...AppStyles.paddingHorizontal, + backgroundColor: AppColors.brand.lightPrimary, + elevation: 4, + height: 56, + alignItems: 'center', + }, }); export default BidAnalysis diff --git a/js-src/screen/Home/HomeContainer.js b/js-src/screen/Home/HomeContainer.js index dfd9a7c..2a5abc9 100644 --- a/js-src/screen/Home/HomeContainer.js +++ b/js-src/screen/Home/HomeContainer.js @@ -1,14 +1,16 @@ import { connect } from 'react-redux'; // Actions -import * as UserActions from '@redux/user/actions'; +import { refreshData } from '@redux/dashboard/actions' import Home from './HomeView' const mapStateToProps = (state) => ({ + dashboard: state.dashboard }); const mapDispatchToProps = { + refreshData }; -export default connect(mapStateToProps, mapDispatchToProps)(Home) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(Home) diff --git a/js-src/screen/Home/HomeView.js b/js-src/screen/Home/HomeView.js index 4ef94be..bdea074 100644 --- a/js-src/screen/Home/HomeView.js +++ b/js-src/screen/Home/HomeView.js @@ -1,7 +1,12 @@ import React, { Component } from 'react' -import { Dimensions, ScrollView, Text, TouchableOpacity, View } from 'react-native' +import { ActivityIndicator, Dimensions, ScrollView, Text, TouchableOpacity, View } from 'react-native' import { AppColors, AppSizes, AppStyles } from '@theme/' import Icon from 'react-native-vector-icons/MaterialIcons' +import { Pie } from 'react-native-pathjs-charts' +import Table from 'react-native-simple-table'; +import moment from 'moment' +import numeral from 'numeral' +import truncate from 'truncate' const { width: screenWidth, height: screenHeight } = Dimensions.get('window') const viewStatWidth = (screenWidth-40)/7 // -40 karena paddingHorizontal @@ -11,6 +16,42 @@ class Home extends Component { super(props) } + componentDidMount() { + this.props.refreshData() + } + + getDateRange = () => { + let { latestDate } = this.props.dashboard + let momentObj = moment(latestDate, 'X') + + let sundayDateStr = momentObj.format('D MMM') + let mondayDateStr = momentObj.day(-6).format('D MMM') + + let range = mondayDateStr + ' - ' + sundayDateStr + + return range + } + + getPreviousDateRange = () => { + let { latestDate } = this.props.dashboard + let momentObj = moment(latestDate, 'X') + + let sundayDateStr = momentObj.day(-7).format('D MMM') + let mondayDateStr = momentObj.day(-6).format('D MMM') + + let range = mondayDateStr + ' - ' + sundayDateStr + + return range + } + + getRevenue= () => { + let { currentRevenue, fetchingTransaction } = this.props.dashboard + + if (fetchingTransaction) return + + return { numeral(currentRevenue).format('0.0 a') } + } + renderViewStat = (item, index) => { return ( @@ -19,68 +60,262 @@ class Home extends Component { ) } + renderRevenueComparation = () => { + let { currentRevenue, previousRevenue } = this.props.dashboard + let _style = {}; + if(currentRevenue < (0.8 * previousRevenue)){ + _style = styles.statusTagBad; + } else if (currentRevenue < (1.2 * previousRevenue)) { // implicitly imply that the revenue above 0.8 since it fails in above code + _style = styles.statusTagWarning; + } else { + _style = styles.statusTagOk; + } + return ( + Compared to Prev Period + ); + } + + renderConvertionRateComparation = () => { + const { convertion_rate, prev_convertion_rate } = this.props.dashboard; + const curr_sold_accumulated = convertion_rate.sold_accumulated || 0; + const curr_view_accumulated = convertion_rate.view_accumulated || 1; + const prev_sold_accumulated = prev_convertion_rate.sold_accumulated || 0; + const prev_view_accumulated = prev_convertion_rate.view_accumulated || 1; + const conv_rate = curr_sold_accumulated / curr_view_accumulated * 100; + const prev_conv_rate = prev_sold_accumulated / prev_view_accumulated * 100; + console.log("conv rate", conv_rate, " prev conv_rate", prev_conv_rate); + let _style = {}; + if(conv_rate < (0.8 * prev_conv_rate)){ + _style = styles.statusTagBad; + } else if (conv_rate < (1.2 * prev_conv_rate)) { // implicitly imply that the revenue above 0.8 since it fails in above code + _style = styles.statusTagWarning; + } else { + _style = styles.statusTagOk; + } + return ( + Compared to Prev Period + ); + } + + retrieveWeeklyData() { + const { weekly_view } = this.props.dashboard; + const view_stat = []; + view_stat[0] = weekly_view.Monday || 0; + view_stat[1] = weekly_view.Tuesday || 0; + view_stat[2] = weekly_view.Wednesday || 0; + view_stat[3] = weekly_view.Thursday || 0; + view_stat[4] = weekly_view.Friday || 0; + view_stat[5] = weekly_view.Saturday || 0; + view_stat[6] = weekly_view.Sunday || 0; + return view_stat; + } + render() { + console.log("state are: ", this.props.dashboard); + // Retriveing data from sqlite, truncating, and formating the number if needed + viewStat = this.retrieveWeeklyData(); + const { least_viewed, most_viewed, convertion_rate, revenue_attribution, prev_convertion_rate } = this.props.dashboard; + const dataSource_least_viewed = least_viewed.map( d => { + return { + item: truncate(d.item, 25), + view: numeral(d.view).format('0.0 a'), + }; + }); + const dataSource_most_viewed = most_viewed.map( d => { + return { + item: truncate(d.item, 25), + view: numeral(d.view).format('0.0 a'), + }; + }); + console.log(revenue_attribution); + const data_revenue = revenue_attribution.map( d => { + return { + name: truncate(d.name, 25), + attribution: d.attribution, + }; + }); + const conv_rate = numeral(convertion_rate.sold_accumulated / convertion_rate.view_accumulated * 100).format('0.0'); + const prev_conv_rate = numeral(prev_convertion_rate.sold_accumulated / prev_convertion_rate.view_accumulated * 100).format('0.0'); + + let maxViewStat = Math.max(...viewStat) - let viewStatDiameters = viewStat.map((item) => item*80/maxViewStat) + let viewStatDiameters = viewStat.map((item) => item*viewStatWidth/maxViewStat) return ( - + {/* Date Header */} - 22 April - 29 April - 14 April - 21 April + { this.getDateRange() } + { this.getPreviousDateRange() } - + this.props.refreshData('prev')} + > - + this.props.refreshData('next')} + > {/* End of Date Header */} + + Overview + + + Revenue + { this.getRevenue() } + { this.renderRevenueComparation() } + + + Conv. Rate + {conv_rate} % + { this.renderConvertionRateComparation()} + + - Overview - - - Revenue - 400 K - Compared to Prev Period + User Views by Day + + + Senin + Selasa + Rabu + Kamis + Jumat + Sabtu + Minggu + + + { viewStatDiameters.map(this.renderViewStat) } + - - Conv. Rate - 3 % - Compared to Prev Period + + Revenue Attribution + + - - User Views by Day - - - Senin - Selasa - Rabu - Kamis - Jumat - Sabtu - Minggu + Most Viewed Product + +
- - { viewStatDiameters.map(this.renderViewStat) } + + Least Viewed Product + +
- - + + ) } } // To be replaced by dynamic data const viewStat = [12, 15, 20, 8, 22, 30, 45] +const data = [{ + "name": "Washington", + "attribution": 7694980 + }, { + "name": "Oregon", + "attribution": 2584160 + }, { + "name": "Minnesota", + "attribution": 6590667, + "color": {'r':223,'g':154,'b':20} + }, { + "name": "Alaska", + "attribution": 7284698 + }] +const options = { + margin: { + top: 20, + left: 20, + right: 20, + bottom: 20 + }, + width: 350, + height: 350, + color: '#2980B9', + r: 50, + R: 150, + legendPosition: 'topLeft', + animate: { + type: 'oneByOne', + duration: 200, + fillTransition: 3 + }, + label: { + fontFamily: 'Arial', + fontSize: 8, + fontWeight: true, + color: '#ECF0F1' + } + } +const columns = [ + { + title: 'Item', + dataIndex: 'item', + width: AppSizes.widthHalf-40, + }, + { + title: 'View', + dataIndex: 'view', + width: AppSizes.widthHalf-40, + }, + // { + // title: 'Avg. Market View', + // dataIndex: 'avg', + // width: AppSizes.widthThird-40, + // } +]; +const dataSource = [{ + item: 'Item A', + view: 500, + avg: 450 +}, { + item: 'Item B', + view: 400, + avg: 394 +}, { + item: 'Item C', + view: 300, + avg: 145 +}, { + item: 'Item D', + view: 120, + avg: 254 +}] const styles = { centeredH3: { @@ -89,12 +324,15 @@ const styles = { }, centeredStat: { ...AppStyles.textCenterAligned, - fontSize: 56 + fontSize: 48 }, dateHeaderContainer: { ...AppStyles.spreadHorizontalContainer, - ...AppStyles.padding, - backgroundColor: AppColors.brand.lightPrimary + ...AppStyles.paddingHorizontal, + backgroundColor: AppColors.brand.lightPrimary, + elevation: 4, + height: 56, + alignItems: 'center', }, overviewContainer: { ...AppStyles.row, @@ -102,25 +340,41 @@ const styles = { ...AppStyles.paddingVertical, backgroundColor: AppColors.background }, + revenueContainer: { + ...AppStyles.row, + ...AppStyles.paddingVertical, + backgroundColor: AppColors.background + }, sectionTitle: { - ...AppStyles.h1, + ...AppStyles.h2, ...AppStyles.padding }, statContainer: { - ...AppStyles.flex1, - ...AppStyles.paddingHorizontal + ...AppStyles.flex1 }, statusTagOk: { ...AppStyles.textCenterAligned, backgroundColor: AppColors.brand.success, padding: 4, - borderRadius: 16 + borderRadius: 16, + marginHorizontal: 4, + fontSize: 10 + }, + statusTagBad: { + ...AppStyles.textCenterAligned, + backgroundColor: AppColors.brand.lightPrimary, + padding: 4, + borderRadius: 16, + marginHorizontal: 4, + fontSize: 10 }, statusTagWarning: { ...AppStyles.textCenterAligned, backgroundColor: AppColors.brand.accent, padding: 4, - borderRadius: 16 + borderRadius: 16, + marginHorizontal: 4, + fontSize: 10 }, viewStatSectionContainer: { ...AppStyles.paddingHorizontal, @@ -134,8 +388,9 @@ const styles = { }, viewStatDay: { ...AppStyles.textCenterAligned, - width: viewStatWidth // -40 karena paddingHorizontal + width: viewStatWidth, + fontSize: 10 } } -export default Home \ No newline at end of file +export default Home diff --git a/js-src/screen/Login/LoginContainer.js b/js-src/screen/Login/LoginContainer.js index 5595dc0..22a7837 100644 --- a/js-src/screen/Login/LoginContainer.js +++ b/js-src/screen/Login/LoginContainer.js @@ -1,13 +1,16 @@ import { connect } from 'react-redux'; +import { login } from '@redux/user/actions' // Actions import Login from './LoginView' const mapStateToProps = (state) => ({ + user: state.user }); const mapDispatchToProps = { + login }; export default connect(mapStateToProps, mapDispatchToProps)(Login) \ No newline at end of file diff --git a/js-src/screen/Login/LoginView.js b/js-src/screen/Login/LoginView.js index 64bd5f7..c3819cf 100644 --- a/js-src/screen/Login/LoginView.js +++ b/js-src/screen/Login/LoginView.js @@ -1,20 +1,44 @@ import React, { Component } from 'react' -import { Button, Text, View } from 'react-native' +import { Button, Text, ToastAndroid, View, ScrollView } from 'react-native' +import { FormLabel, FormInput } from '@components/ui' +import { AppColors } from '@theme/' class Login extends Component{ + constructor(props) { + super(props) + + this.state = { + username: '', + password: '' + } + } + render() { return ( - - Login Screen + + + Username + this.setState({username})} + /> + Password + this.setState({password})} + /> +